diff --git a/esi.inc b/esi.inc
index 4d8a5e2..2c63e91 100644
--- a/esi.inc
+++ b/esi.inc
@@ -52,6 +52,18 @@ function _esi__get_roles_hash($rids) {
 }
 
 /**
+ * Get the hash for a set of roles.
+ *
+ * @param array $rids
+ *   An array of role-ids
+ */
+function esi_get_user_hash($uid) {
+  $seed = _esi__get_seed_key();
+  $hash = md5($seed . md5($uid));
+  return $hash;
+}
+
+/**
  * Get the themed HTML for a particular block
  *
  * @param $theme
diff --git a/esi.js b/esi.js
index b46da4e..9e76451 100644
--- a/esi.js
+++ b/esi.js
@@ -5,10 +5,37 @@ Drupal.behaviors.esi = function(context) {
     if (Drupal.settings.esi[css_id] == 'undefined') {
       return;
     }
-    var src = Drupal.settings.esi[css_id];
-    $.get(src, function(data) {
+    var web_src = Drupal.settings.esi[css_id];
+
+    // Replace CACHE=ROLE with CACHE=role_hash.
+    var role_hash = esi_get_hash('RSESS');
+    var suffix = '/CACHE=ROLE';
+    if (role_hash.length != 0 && web_src.indexOf(suffix, web_src.length - suffix.length) !== -1) {
+      web_src = web_src.replace(suffix, '/CACHE=' + role_hash);
+    }
+    // Replace CACHE=USER with CACHE=user_hash.
+    var user_hash = esi_get_hash('USESS');
+    var suffix = '/CACHE=USER';
+    if (user_hash.length != 0 && web_src.indexOf(suffix, web_src.length - suffix.length) !== -1) {
+      web_src = web_src.replace(suffix, '/CACHE=' + user_hash);
+    }
+
+    $.get(web_src, function(data) {
       $('#' + css_id).replaceWith(data);
       Drupal.attachBehaviors(data);
     });
   });
 }
+
+function esi_get_hash(prefix) {
+  var all_cookies = document.cookie.split(';');
+  for (var i=0; i < all_cookies.length; i++) {
+    // 'SESS' is hard coded in conf_init().
+    // 'R' is hard coded in the ESI module.
+    if (all_cookies[i].indexOf(prefix) === 1) {
+      var values = all_cookies[i].split('=');
+      return values[1];
+    }
+  }
+  return '';
+}
diff --git a/esi.module b/esi.module
index eaa6ba8..52a7f07 100644
--- a/esi.module
+++ b/esi.module
@@ -114,11 +114,17 @@ function esi_theme_registry_alter(&$theme_registry) {
  * Implementation of hook_init().
  */
 function esi_init() {
+  // Set RSESS/USESS cookie if not set and user is logged in.
+  global $user;
+  if (!empty($user->uid) && (empty($_COOKIE['R' . session_name()]) || empty($_COOKIE['U' . session_name()]))) {
+    $edit = '';
+    esi_user('login', $edit, $user);
+  }
+
   // If ESI or AJAX Fallback is disabled then do not add in the esi.js file.
   if (!variable_get('esi_mode', ESI_MODE) || !variable_get('esi_ajax_fallback', ESI_AJAX_FALLBACK)) {
     return;
   }
-
   drupal_add_js(drupal_get_path('module', 'esi') . '/esi.js');
 }
 
@@ -160,7 +166,7 @@ function esi_menu() {
  */
 function esi_cron() {
   $age = time() - variable_get('esi_seed_key_last_changed', 0);
-  $interval = variable_get('esi_seed_key_rotation_interval', ESI__DEFAULT_SEED_KEY_ROTATION_INTERVAL);
+  $interval = variable_get('esi_seed_key_rotation_interval', ESI_SEED_ROTATION_INTERVAL);
   if ($age > $interval) {
     require_once(drupal_get_path('module', 'esi') . '/esi.inc');
     _esi__rotate_seed_key();
@@ -183,24 +189,35 @@ function esi_user($op, &$edit, &$account, $category = NULL) {
 
   // Drupal session cookies use the name 'SESS' followed by an MD5 hash.
   // The role-cookie is the same, prefixes with the letter 'R'.
-  $cookie = array('name' => 'R' . session_name());
+  $role_cookie = array('name' => 'R' . session_name());
+  $user_cookie = array('name' => 'U' . session_name());
 
   if ($op == 'login') {
     require_once(drupal_get_path('module', 'esi') . '/esi.inc');
-    $hash = _esi__get_roles_hash(array_keys($account->roles));
-    $lifespan = max(variable_get('esi_seed_key_rotation_interval', ESI__DEFAULT_SEED_KEY_ROTATION_INTERVAL), ini_get('session.cookie_lifetime'));
-    $cookie += array(
-      'value' => $hash,
+    $role_hash = _esi__get_roles_hash(array_keys($account->roles));
+    $user_hash = esi_get_user_hash($user->uid);
+    $lifespan = max(variable_get('esi_seed_key_rotation_interval', ESI_SEED_ROTATION_INTERVAL), ini_get('session.cookie_lifetime'));
+    $role_cookie += array(
+      'value' => $role_hash,
+      'expire' => time() + $lifespan,
+    );
+    $user_cookie += array(
+      'value' => $user_hash,
       'expire' => time() + $lifespan,
     );
   }
   else {
-    $cookie += array(
+    $role_cookie += array(
+      'value' => 'deleted',
+      'expire' => 1,
+    );
+    $user_cookie += array(
       'value' => 'deleted',
       'expire' => 1,
     );
   }
-  setcookie($cookie['name'], $cookie['value'], $cookie['expire']);
+  setcookie($role_cookie['name'], $role_cookie['value'], $role_cookie['expire'], ini_get('session.cookie_path'));
+  setcookie($user_cookie['name'], $user_cookie['value'], $user_cookie['expire'], ini_get('session.cookie_path'));
 }
 
 /**
@@ -312,6 +329,10 @@ function esi__block_handler($bid, $page = NULL) {
   require_once(drupal_get_path('module', 'esi')  . '/esi.inc');
   global $custom_theme, $theme_key;
 
+  // Save this page's internal URL.
+  $this_url = explode('/', $_GET['q']);
+  $this_url = array_pop($this_url);
+
   // Block content may change per-page.
   // If this is true for the current block, the origin page url should be
   // provided as an argument.
@@ -342,9 +363,14 @@ function esi__block_handler($bid, $page = NULL) {
   // No-cache is header max age (TTL) controlled.
   // Per-page is passed as a url argument.
   $config = esi_get_settings($module . '_' . $delta);
-  esi_add_cache_headers($config['scope'], $config['max_age']);
 
+  esi_add_cache_headers($config['scope'], $config['max_age']);
   echo $output;
+  // If the page cache is enabled and the URL ends with CACHE=*** then allow for
+  // this response to be cached in the page cache.
+  if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED && strpos($this_url, 'CACHE=') {
+    return NULL;
+  }
   exit;
 }
 
@@ -444,6 +470,10 @@ function esi__panel_pane_handler($bid, $page = NULL, $task_name = NULL, $context
 
   global $custom_theme, $theme_key;
 
+  // Save this page's internal URL.
+  $this_url = explode('/', $_GET['q']);
+  $this_url = array_pop($this_url);
+
   // Set context.
   if (isset($page) && strpos($page, 'CACHE=') !== 0) {
     $q = base64_decode($page);
@@ -500,8 +530,12 @@ function esi__panel_pane_handler($bid, $page = NULL, $task_name = NULL, $context
   }
 
   esi_add_cache_headers($config['settings']['scope'], $config['settings']['max_age']);
-
-  echo $content;
+  echo $output;
+  // If the page cache is enabled and the URL ends with CACHE=*** then allow for
+  // this response to be cached in the page cache.
+  if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED && strpos($this_url, 'CACHE=') {
+    return NULL;
+  }
   exit;
 }
 
@@ -608,7 +642,7 @@ function esi_context_registry_alter(&$registry) {
  * Generate a fast 404
  *
  * @return
- *   Full html page displaying the 404 message.
+ *   Blank page.
  */
 function esi_fast404($msg = '') {
   global $base_path;
@@ -618,14 +652,14 @@ function esi_fast404($msg = '') {
   }
   $output = '';
 
-  $output .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
-  $output .= '<html>';
-  $output .= '<head><title>404 Not Found</title></head>';
-  $output .= '<body><h1>Not Found</h1>';
-  $output .= '<p>The requested URL was not found on this server.</p>';
-  $output .= '<p><a href="' . $base_path . '">Home</a></p>';
-  $output .= '<!-- esi_fast404 -->';
-  $output .= '</body></html>';
+//   $output .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
+//   $output .= '<html>';
+//   $output .= '<head><title>404 Not Found</title></head>';
+//   $output .= '<body><h1>Not Found</h1>';
+//   $output .= '<p>The requested URL was not found on this server.</p>';
+//   $output .= '<p><a href="' . $base_path . '">Home</a></p>';
+//   $output .= '<!-- esi_fast404 -->';
+//   $output .= '</body></html>';
 
   return $output;
 }
diff --git a/plugins/cache/esi.inc b/plugins/cache/esi.inc
index 55e976a..18fcbee 100644
--- a/plugins/cache/esi.inc
+++ b/plugins/cache/esi.inc
@@ -34,6 +34,9 @@ function esi_esi_cache_get_cache($conf, $display, $args, $contexts, $pane = NULL
   if (!$cache) {
     return FALSE;
   }
+  $conf['max_age'] = isset($conf['max_age']) ? $conf['max_age'] : isset($conf['lifetime']) ? $conf['lifetime'] : variable_get('esi_panel_default_max_age', ESI_PANEL_DEFAULT_MAX_AGE);
+  $conf['max_age'] = (int)$conf['max_age'];
+
   if ((time() - $cache->created) > $conf['max_age']) {
     return FALSE;
   }
