diff --git a/core/authorize.php b/core/authorize.php
index ecb7e22..586883a 100644
--- a/core/authorize.php
+++ b/core/authorize.php
@@ -68,6 +68,7 @@ function authorize_access_allowed() {
 require_once __DIR__ . '/includes/file.inc';
 require_once __DIR__ . '/includes/module.inc';
 require_once __DIR__ . '/includes/ajax.inc';
+require_once __DIR__ . '/includes/session.inc';
 
 // We prepare only a minimal bootstrap. This includes the database and
 // variables, however, so we have access to the class autoloader.
@@ -79,6 +80,9 @@ function authorize_access_allowed() {
 // This must go after drupal_bootstrap(), which unsets globals!
 global $conf;
 
+// Initialize the session system.
+drupal_session_initialize();
+
 // We have to enable the user and system modules, even to check access and
 // display errors via the maintenance theme.
 $module_list['system'] = 'core/modules/system/system.module';
diff --git a/core/core.services.yml b/core/core.services.yml
index c369394..b6973e7 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -547,6 +547,34 @@ services:
   date:
     class: Drupal\Core\Datetime\Date
     arguments: ['@entity.manager', '@language_manager']
+  session.storage.handler:
+    class: Drupal\Core\Session\Storage\Handler\DatabaseSessionHandler
+    arguments: ['@database']
+    tags:
+      - { name: persist }
+  session.storage.proxy:
+    class: Drupal\Core\Session\Storage\Proxy\CookieProxy
+    arguments: ['@session.storage.handler', '@settings']
+    calls:
+      - [setRequest, ['@?request']]
+    tags:
+      - { name: persist }
+  session.storage:
+    class: Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage
+    arguments: [[], '@session.storage.proxy']
+    tags:
+      - { name: persist }
+  session:
+    class: Drupal\Core\Session\Session
+    arguments: ['@session.storage']
+    tags:
+      - { name: persist }
+  session.listener:
+    class: Drupal\Core\EventSubscriber\SessionListener
+    arguments: ['@session', '@session.storage.proxy']
+    tags:
+      - { name: event_subscriber }
+    scope: request
   feed.bridge.reader:
     class: Drupal\Component\Bridge\ZfExtensionManagerSfContainer
     calls:
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 32f47fa..9a4f881 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -2747,6 +2747,12 @@ function drupal_classloader($class_loader = NULL) {
       $loader->unregister();
       $apc_loader->register();
     }
+
+    // Fallback to symfony SessionHandlerInterface for PHP < 5.4.0.
+    if (!interface_exists('SessionHandlerInterface', FALSE)) {
+      $loader->add('SessionHandlerInterface', DRUPAL_ROOT . '/core/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Resources/stubs');
+    }
+
   }
   return $loader;
 }
diff --git a/core/includes/common.inc b/core/includes/common.inc
index df552ed..8e1fea1 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -3090,6 +3090,7 @@ function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) {
  * Loads code for subsystems and modules, and registers stream wrappers.
  */
 function _drupal_bootstrap_code() {
+  require_once __DIR__ . '/../../' . settings()->get('session_inc', 'core/includes/session.inc');
   require_once __DIR__ . '/../../' . settings()->get('path_inc', 'core/includes/path.inc');
   require_once __DIR__ . '/module.inc';
   require_once __DIR__ . '/theme.inc';
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 91a241f..c9a441d 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -1770,6 +1770,7 @@ function install_load_profile(&$install_state) {
  *   An array of information about the current installation state.
  */
 function install_bootstrap_full() {
+  drupal_session_initialize();
   drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
   require_once DRUPAL_ROOT . '/' . settings()->get('session_inc', 'core/includes/session.inc');
   drupal_session_initialize();
diff --git a/core/includes/session.inc b/core/includes/session.inc
index 4cc8139..376c3f3 100644
--- a/core/includes/session.inc
+++ b/core/includes/session.inc
@@ -3,295 +3,33 @@
 /**
  * @file
  * User session handling functions.
- *
- * The user-level session storage handlers:
- * - _drupal_session_open()
- * - _drupal_session_close()
- * - _drupal_session_read()
- * - _drupal_session_write()
- * - _drupal_session_destroy()
- * - _drupal_session_garbage_collection()
- * are assigned by session_set_save_handler() in bootstrap.inc and are called
- * automatically by PHP. These functions should not be called directly. Session
- * data should instead be accessed via the $_SESSION superglobal.
- */
-
-use Drupal\Component\Utility\Crypt;
-
-use Drupal\Core\Session\UserSession;
-
-/**
- * Session handler assigned by session_set_save_handler().
- *
- * This function is used to handle any initialization, such as file paths or
- * database connections, that is needed before accessing session data. Drupal
- * does not need to initialize anything in this function.
- *
- * This function should not be called directly.
- *
- * @return
- *   This function will always return TRUE.
- */
-function _drupal_session_open() {
-  return TRUE;
-}
-
-/**
- * Session handler assigned by session_set_save_handler().
- *
- * This function is used to close the current session. Because Drupal stores
- * session data in the database immediately on write, this function does
- * not need to do anything.
- *
- * This function should not be called directly.
- *
- * @return
- *   This function will always return TRUE.
  */
-function _drupal_session_close() {
-  return TRUE;
-}
-
-/**
- * Reads an entire session from the database (internal use only).
- *
- * Also initializes the $user object for the user associated with the session.
- * This function is registered with session_set_save_handler() to support
- * database-backed sessions. It is called on every page load when PHP sets
- * up the $_SESSION superglobal.
- *
- * This function is an internal function and must not be called directly.
- * Doing so may result in logging out the current user, corrupting session data
- * or other unexpected behavior. Session data must always be accessed via the
- * $_SESSION superglobal.
- *
- * @param $sid
- *   The session ID of the session to retrieve.
- *
- * @return
- *   The user's session, or an empty string if no session exists.
- */
-function _drupal_session_read($sid) {
-  global $user;
-
-  // Write and Close handlers are called after destructing objects
-  // since PHP 5.0.5.
-  // Thus destructors can use sessions but session handler can't use objects.
-  // So we are moving session closure before destructing objects.
-  drupal_register_shutdown_function('session_write_close');
-
-  // Handle the case of first time visitors and clients that don't store
-  // cookies (eg. web crawlers).
-  $insecure_session_name = substr(session_name(), 1);
-  if (!isset($_COOKIE[session_name()]) && !isset($_COOKIE[$insecure_session_name])) {
-    $user = new UserSession();
-    return '';
-  }
-
-  // Otherwise, if the session is still active, we have a record of the
-  // client's session in the database. If it's HTTPS then we are either have
-  // a HTTPS session or we are about to log in so we check the sessions table
-  // for an anonymous session with the non-HTTPS-only cookie.
-  if (\Drupal::request()->isSecure()) {
-    $values = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.ssid = :ssid", array(':ssid' => $sid))->fetchAssoc();
-    if (!$values) {
-      if (isset($_COOKIE[$insecure_session_name])) {
-        $values = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid AND s.uid = 0", array(
-        ':sid' => $_COOKIE[$insecure_session_name]))
-        ->fetchAssoc();
-      }
-    }
-  }
-  else {
-    $values = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => $sid))->fetchAssoc();
-  }
-
-  // We found the client's session record and they are an authenticated,
-  // active user.
-  if ($values && $values['uid'] > 0 && $values['status'] == 1) {
-    // Add roles element to $user.
-    $rids = db_query("SELECT ur.rid FROM {users_roles} ur WHERE ur.uid = :uid", array(':uid' => $values['uid']))->fetchCol();
-    $values['roles'] = array_merge(array(DRUPAL_AUTHENTICATED_RID), $rids);
-    $user = new UserSession($values);
-  }
-  elseif ($values) {
-    // The user is anonymous or blocked. Only preserve two fields from the
-    // {sessions} table.
-    $user = new UserSession(array(
-      'session' => $values['session'],
-      'access' => $values['access'],
-    ));
-  }
-  else {
-    // The session has expired.
-    $user = new UserSession();
-  }
-
-  // Store the session that was read for comparison in _drupal_session_write().
-  $last_read = &drupal_static('drupal_session_last_read');
-  $last_read = array(
-    'sid' => $sid,
-    'value' => $user->session,
-  );
-
-  return $user->session;
-}
-
-/**
- * Writes an entire session to the database (internal use only).
- *
- * This function is registered with session_set_save_handler() to support
- * database-backed sessions.
- *
- * This function is an internal function and must not be called directly.
- * Doing so may result in corrupted session data or other unexpected behavior.
- * Session data must always be accessed via the $_SESSION superglobal.
- *
- * @param $sid
- *   The session ID of the session to write to.
- * @param $value
- *   Session data to write as a serialized string.
- *
- * @return
- *   Always returns TRUE.
- */
-function _drupal_session_write($sid, $value) {
-  global $user;
-
-  // The exception handler is not active at this point, so we need to do it
-  // manually.
-  try {
-    if (!drupal_save_session()) {
-      // We don't have anything to do if we are not allowed to save the session.
-      return;
-    }
-
-    // Check whether $_SESSION has been changed in this request.
-    $last_read = &drupal_static('drupal_session_last_read');
-    $is_changed = !isset($last_read) || $last_read['sid'] != $sid || $last_read['value'] !== $value;
-
-    // For performance reasons, do not update the sessions table, unless
-    // $_SESSION has changed or more than 180 has passed since the last update.
-    if ($is_changed || !$user->getLastAccessedTime() || REQUEST_TIME - $user->getLastAccessedTime() > settings()->get('session_write_interval', 180)) {
-      // Either ssid or sid or both will be added from $key below.
-      $fields = array(
-        'uid' => $user->id(),
-        'hostname' => \Drupal::request()->getClientIP(),
-        'session' => $value,
-        'timestamp' => REQUEST_TIME,
-      );
-
-      // Use the session ID as 'sid' and an empty string as 'ssid' by default.
-      // _drupal_session_read() does not allow empty strings so that's a safe
-      // default.
-      $key = array('sid' => $sid, 'ssid' => '');
-      // On HTTPS connections, use the session ID as both 'sid' and 'ssid'.
-      if (\Drupal::request()->isSecure()) {
-        $key['ssid'] = $sid;
-        // The "secure pages" setting allows a site to simultaneously use both
-        // secure and insecure session cookies. If enabled and both cookies are
-        // presented then use both keys.
-        if (settings()->get('mixed_mode_sessions', FALSE)) {
-          $insecure_session_name = substr(session_name(), 1);
-          if (isset($_COOKIE[$insecure_session_name])) {
-            $key['sid'] = $_COOKIE[$insecure_session_name];
-          }
-        }
-      }
-      elseif (settings()->get('mixed_mode_sessions', FALSE)) {
-        unset($key['ssid']);
-      }
-
-      db_merge('sessions')
-        ->key($key)
-        ->fields($fields)
-        ->execute();
-    }
-
-    // Likewise, do not update access time more than once per 180 seconds.
-    if ($user->isAuthenticated() && REQUEST_TIME - $user->getLastAccessedTime() > settings()->get('session_write_interval', 180)) {
-      db_update('users')
-        ->fields(array(
-          'access' => REQUEST_TIME
-        ))
-        ->condition('uid', $user->id())
-        ->execute();
-    }
-
-    return TRUE;
-  }
-  catch (Exception $exception) {
-    require_once __DIR__ . '/errors.inc';
-    // If we are displaying errors, then do so with no possibility of a further
-    // uncaught exception being thrown.
-    if (error_displayable()) {
-      print '<h1>Uncaught exception thrown in session handler.</h1>';
-      print '<p>' . _drupal_render_exception_safe($exception) . '</p><hr />';
-    }
-    return FALSE;
-  }
-}
 
 /**
  * Initializes the session handler, starting a session if needed.
+ *
+ * This is only needed when the session listener is inactive, eg the installer.
  */
 function drupal_session_initialize() {
-  global $user;
-
-  session_set_save_handler('_drupal_session_open', '_drupal_session_close', '_drupal_session_read', '_drupal_session_write', '_drupal_session_destroy', '_drupal_session_garbage_collection');
-
-  $is_https = \Drupal::request()->isSecure();
-  // We use !empty() in the following check to ensure that blank session IDs
-  // are not valid.
-  if (!empty($_COOKIE[session_name()]) || ($is_https && settings()->get('mixed_mode_sessions', FALSE) && !empty($_COOKIE[substr(session_name(), 1)]))) {
-    // If a session cookie exists, initialize the session. Otherwise the
-    // session is only started on demand in drupal_session_commit(), making
-    // anonymous users not use a session cookie unless something is stored in
-    // $_SESSION. This allows HTTP proxies to cache anonymous pageviews.
-    drupal_session_start();
-    if ($user->isAuthenticated() || !empty($_SESSION)) {
-      drupal_page_is_cacheable(FALSE);
-    }
-  }
-  else {
-    // Set a session identifier for this request. This is necessary because
-    // we lazily start sessions at the end of this request, and some
-    // processes (like drupal_get_token()) needs to know the future
-    // session ID in advance.
-    $GLOBALS['lazy_session'] = TRUE;
-    $user = drupal_anonymous_user();
-    // Less random sessions (which are much faster to generate) are used for
-    // anonymous users than are generated in drupal_session_regenerate() when
-    // a user becomes authenticated.
-    session_id(Crypt::hashBase64(uniqid(mt_rand(), TRUE)));
-    if ($is_https && settings()->get('mixed_mode_sessions', FALSE)) {
-      $insecure_session_name = substr(session_name(), 1);
-      $session_id = Crypt::hashBase64(uniqid(mt_rand(), TRUE));
-      $_COOKIE[$insecure_session_name] = $session_id;
-    }
+  $request = \Drupal::request();
+  $session = \Drupal::service('session');
+  if (!$request->hasSession()) {
+    $request->setSession($session);
+    \Drupal::service('session.storage.proxy')->setRequest($request);
+    // Initialize session, so that $user global is functioning correctly.
+    $session->init();
   }
-  date_default_timezone_set(drupal_get_user_timezone());
 }
 
 /**
  * Starts a session forcefully, preserving already set session data.
  *
+ * @deprecated as of Drupal 8.0
+ *
  * @ingroup php_wrappers
  */
 function drupal_session_start() {
-  // Command line clients do not support cookies nor sessions.
-  if (!drupal_session_started() && !drupal_is_cli()) {
-    // Save current session data before starting it, as PHP will destroy it.
-    $session_data = isset($_SESSION) ? $_SESSION : NULL;
-
-    session_start();
-    drupal_session_started(TRUE);
-
-    // Restore session data.
-    if (!empty($session_data)) {
-      $_SESSION += $session_data;
-    }
-  }
+  \Drupal::service('session')->start();
 }
 
 /**
@@ -300,175 +38,40 @@ function drupal_session_start() {
  * If an anonymous user already have an empty session, destroy it.
  */
 function drupal_session_commit() {
+  \Drupal::service('session')->save();
+  // @todo Move this to a user module event listener. It has nothing to do with
+  // sessions.
   global $user;
 
-  if (!drupal_save_session()) {
-    // We don't have anything to do if we are not allowed to save the session.
-    return;
-  }
-
-  if ($user->isAnonymous() && empty($_SESSION)) {
-    // There is no session data to store, destroy the session if it was
-    // previously started.
-    if (drupal_session_started()) {
-      session_destroy();
-    }
-  }
-  else {
-    // There is session data to store. Start the session if it is not already
-    // started.
-    if (!drupal_session_started()) {
-      drupal_session_start();
-      if (\Drupal::request()->isSecure() && settings()->get('mixed_mode_sessions', FALSE)) {
-        $insecure_session_name = substr(session_name(), 1);
-        $params = session_get_cookie_params();
-        $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
-        setcookie($insecure_session_name, $_COOKIE[$insecure_session_name], $expire, $params['path'], $params['domain'], FALSE, $params['httponly']);
-      }
-    }
-    // Write the session data.
-    session_write_close();
+  if ($user->isAuthenticated() && REQUEST_TIME - $user->access > settings()->get('session_write_interval', 180)) {
+    db_update('users')
+      ->fields(array(
+        'access' => REQUEST_TIME
+      ))
+      ->condition('uid', $user->id())
+      ->execute();
   }
 }
 
 /**
  * Returns whether a session has been started.
+ *
+ * @deprecated as of Drupal 8.0
  */
-function drupal_session_started($set = NULL) {
-  static $session_started = FALSE;
-  if (isset($set)) {
-    $session_started = $set;
-  }
-  return $session_started && session_id();
+function drupal_session_started() {
+  return (bool) \Drupal::service('session')->getId();
 }
 
 /**
  * Called when an anonymous user becomes authenticated or vice-versa.
  *
  * @ingroup php_wrappers
- */
-function drupal_session_regenerate() {
-  global $user;
-
-  // Nothing to do if we are not allowed to change the session.
-  if (!drupal_save_session()) {
-    return;
-  }
-
-  $is_https = \Drupal::request()->isSecure();
-
-  if ($is_https && settings()->get('mixed_mode_sessions', FALSE)) {
-    $insecure_session_name = substr(session_name(), 1);
-    if (!isset($GLOBALS['lazy_session']) && isset($_COOKIE[$insecure_session_name])) {
-      $old_insecure_session_id = $_COOKIE[$insecure_session_name];
-    }
-    $params = session_get_cookie_params();
-    $session_id = Crypt::hashBase64(uniqid(mt_rand(), TRUE) . Crypt::randomBytes(55));
-    // If a session cookie lifetime is set, the session will expire
-    // $params['lifetime'] seconds from the current request. If it is not set,
-    // it will expire when the browser is closed.
-    $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
-    setcookie($insecure_session_name, $session_id, $expire, $params['path'], $params['domain'], FALSE, $params['httponly']);
-    $_COOKIE[$insecure_session_name] = $session_id;
-  }
-
-  if (drupal_session_started()) {
-    $old_session_id = session_id();
-  }
-  session_id(Crypt::hashBase64(uniqid(mt_rand(), TRUE) . Crypt::randomBytes(55)));
-
-  if (isset($old_session_id)) {
-    $params = session_get_cookie_params();
-    $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
-    setcookie(session_name(), session_id(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
-    $fields = array('sid' => session_id());
-    if ($is_https) {
-      $fields['ssid'] = session_id();
-      // If the "secure pages" setting is enabled, use the newly-created
-      // insecure session identifier as the regenerated sid.
-      if (settings()->get('mixed_mode_sessions', FALSE)) {
-        $fields['sid'] = $session_id;
-      }
-    }
-    db_update('sessions')
-      ->fields($fields)
-      ->condition($is_https ? 'ssid' : 'sid', $old_session_id)
-      ->execute();
-  }
-  elseif (isset($old_insecure_session_id)) {
-    // If logging in to the secure site, and there was no active session on the
-    // secure site but a session was active on the insecure site, update the
-    // insecure session with the new session identifiers.
-    db_update('sessions')
-      ->fields(array('sid' => $session_id, 'ssid' => session_id()))
-      ->condition('sid', $old_insecure_session_id)
-      ->execute();
-  }
-  else {
-    // Start the session when it doesn't exist yet.
-    // Preserve the logged in user, as it will be reset to anonymous
-    // by _drupal_session_read.
-    $account = $user;
-    drupal_session_start();
-    $user = $account;
-  }
-  date_default_timezone_set(drupal_get_user_timezone());
-}
-
-/**
- * Session handler assigned by session_set_save_handler().
- *
- * Cleans up a specific session.
  *
- * @param $sid
- *   Session ID.
+ * @deprecated as of Drupal 8.0
  */
-function _drupal_session_destroy($sid) {
-  global $user;
-
-  // Nothing to do if we are not allowed to change the session.
-  if (!drupal_save_session()) {
-    return;
-  }
-
-  $is_https = \Drupal::request()->isSecure();
-  // Delete session data.
-  db_delete('sessions')
-    ->condition($is_https ? 'ssid' : 'sid', $sid)
-    ->execute();
 
-  // Reset $_SESSION and $user to prevent a new session from being started
-  // in drupal_session_commit().
-  $_SESSION = array();
-  $user = drupal_anonymous_user();
-
-  // Unset the session cookies.
-  _drupal_session_delete_cookie(session_name());
-  if ($is_https) {
-    _drupal_session_delete_cookie(substr(session_name(), 1), FALSE);
-  }
-  elseif (settings()->get('mixed_mode_sessions', FALSE)) {
-    _drupal_session_delete_cookie('S' . session_name(), TRUE);
-  }
-}
-
-/**
- * Deletes the session cookie.
- *
- * @param $name
- *   Name of session cookie to delete.
- * @param boolean $secure
- *   Force the secure value of the cookie.
- */
-function _drupal_session_delete_cookie($name, $secure = NULL) {
-  if (isset($_COOKIE[$name]) || (!\Drupal::request()->isSecure() && $secure === TRUE)) {
-    $params = session_get_cookie_params();
-    if ($secure !== NULL) {
-      $params['secure'] = $secure;
-    }
-    setcookie($name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
-    unset($_COOKIE[$name]);
-  }
+function drupal_session_regenerate() {
+  return \Drupal::service('session')->migrate();
 }
 
 /**
@@ -479,7 +82,8 @@ function _drupal_session_delete_cookie($name, $secure = NULL) {
  */
 function drupal_session_destroy_uid($uid) {
   // Nothing to do if we are not allowed to change the session.
-  if (!drupal_save_session()) {
+  $session = \Drupal::service('session');
+  if (!$session->isSaveEnabled()) {
     return;
   }
 
@@ -489,27 +93,6 @@ function drupal_session_destroy_uid($uid) {
 }
 
 /**
- * Session handler assigned by session_set_save_handler().
- *
- * Cleans up stalled sessions.
- *
- * @param $lifetime
- *   The value of session.gc_maxlifetime, passed by PHP.
- *   Sessions not updated for more than $lifetime seconds will be removed.
- */
-function _drupal_session_garbage_collection($lifetime) {
-  // Be sure to adjust 'php_value session.gc_maxlifetime' to a large enough
-  // value. For example, if you want user sessions to stay in your database
-  // for three weeks before deleting them, you need to set gc_maxlifetime
-  // to '1814400'. At that value, only after a user doesn't log in after
-  // three weeks (1814400 seconds) will his/her session be removed.
-  db_delete('sessions')
-    ->condition('timestamp', REQUEST_TIME - $lifetime, '<')
-    ->execute();
-  return TRUE;
-}
-
-/**
  * Determines whether to save session data of the current request.
  *
  * This function allows the caller to temporarily disable writing of
@@ -523,14 +106,13 @@ function _drupal_session_garbage_collection($lifetime) {
  *
  * @return
  *   FALSE if writing session data has been disabled. Otherwise, TRUE.
+ *
+ * @deprecated as of Drupal 8.0
  */
 function drupal_save_session($status = NULL) {
-  // PHP session ID, session, and cookie handling happens in the global scope.
-  // This value has to persist across calls to drupal_static_reset(), since a
-  // potentially wrong or disallowed session would be written otherwise.
-  static $save_session = TRUE;
-  if (isset($status)) {
-    $save_session = $status;
+  $session = \Drupal::service('session');
+  if ($status !== NULL) {
+    $status ? $session->enableSave() : $session->disableSave();
   }
-  return $save_session;
+  return $session->isSaveEnabled();
 }
diff --git a/core/lib/Drupal/Core/EventSubscriber/SessionListener.php b/core/lib/Drupal/Core/EventSubscriber/SessionListener.php
new file mode 100644
index 0000000..7971d1d
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/SessionListener.php
@@ -0,0 +1,118 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\EventSubscriber\SessionListener.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\HttpFoundation\Session\SessionInterface;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Drupal\Core\Session\Storage\Proxy\CookieProxyInterface;
+
+/**
+ * Response subscriber to handle session cookies.
+ */
+class SessionListener implements EventSubscriberInterface {
+
+  /**
+   * The session service.
+   *
+   * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
+   */
+  protected $session;
+
+  /**
+   * The session storage proxy service.
+   *
+   * @var \SessionHandlerInterface
+   */
+  protected $sessionProxy;
+
+  /**
+   * Constructs a SessionListener object.
+   *
+   * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session
+   *  The currently active session service.
+   * @param \SessionHandlerInterface $session_proxy
+   *  The currently active session storage proxy service.
+   */
+  public function __construct(SessionInterface $session, \SessionHandlerInterface $session_proxy) {
+    $this->session = $session;
+    $this->sessionProxy = $session_proxy;
+  }
+
+  /**
+   * Sets the object that handles the session data for this request.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+   *   The event to process.
+   */
+  public function initSession(GetResponseEvent $event) {
+    if ($event->getRequestType() !== HttpKernelInterface::MASTER_REQUEST) {
+      return;
+    }
+
+    $request = $event->getRequest();
+    if (!$this->session || $request->hasSession()) {
+      return;
+    }
+
+    // @todo Kill this global.
+    global $user;
+
+    // Set the session service to the current request.
+    $request->setSession($this->session);
+    // And initialize it.
+    $this->session->init();
+
+    if ($user->isAuthenticated()) {
+      // @todo this does not really belong here.
+      drupal_page_is_cacheable(FALSE);
+    }
+
+    date_default_timezone_set(drupal_get_user_timezone());
+  }
+
+  /**
+   * Manipulates session related cookies.
+   *
+   * @param Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
+   *   The event to process.
+   */
+  public function setSessionCookies(FilterResponseEvent $event) {
+    if ($event->getRequestType() !== HttpKernelInterface::MASTER_REQUEST) {
+      return;
+    }
+
+    $request = $event->getRequest();
+    if (!$this->session || !$request->hasSession()) {
+      return;
+    }
+    $response = $event->getResponse();
+    if ($this->sessionProxy instanceof CookieProxyInterface) {
+      foreach ($this->sessionProxy->getCookies() as $cookie) {
+        // Set the cookies for sending.
+        $response->headers->setCookie($cookie);
+      }
+    }
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    $events[KernelEvents::REQUEST][] = array('initSession', 300);
+    $events[KernelEvents::RESPONSE][] = array('setSessionCookies');
+    return $events;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Session/Session.php b/core/lib/Drupal/Core/Session/Session.php
new file mode 100644
index 0000000..250e669
--- /dev/null
+++ b/core/lib/Drupal/Core/Session/Session.php
@@ -0,0 +1,226 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Session\Session.
+ */
+
+namespace Drupal\Core\Session;
+
+use Symfony\Component\HttpFoundation\Session\Session as BaseSession;
+use Drupal\Core\Session\Storage\Proxy\CookieProxyInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Session\UserSession;
+
+/**
+ * Defines a Session implementation to implement Drupal specific features.
+ *
+ * Allows lazy cookie sending and explicit session save disabling.
+ */
+class Session extends BaseSession {
+
+  /**
+   * Keep track of the save enabled state.
+   *
+   * @var bool
+   */
+  protected $saveEnabled = TRUE;
+
+  /**
+   * Keep track for the save handler state when disabling session write.
+   *
+   * @var bool
+   */
+  protected $lastSaveHandlerState;
+
+  /**
+   * This is a reference to the current global $user object.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $userSession;
+
+  /**
+   * Initializes the session handler, starting a session if needed.
+   */
+  public function init() {
+    $this->setUserSessionFromGlobal();
+    $save_handler = $this->storage->getSaveHandler();
+    if ($save_handler instanceof CookieProxyInterface) {
+      $save_handler->init();
+      // If a session cookie exists, initialize the session. Otherwise the
+      // session is only started on demand in Session::save(), making
+      // anonymous users not use a session cookie unless something is stored in
+      // $_SESSION. This allows HTTP proxies to cache anonymous page views.
+      if (!$save_handler->lazySession) {
+        $this->start();
+      }
+      else {
+        $this->userSession = $this->getAnonymousSession();
+      }
+    }
+  }
+
+  /**
+   * Sets the current user session object.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $user_session
+   *   A user session object.
+   */
+  public function setUserSession(AccountInterface $user_session) {
+    $this->userSession = $user_session;
+  }
+
+  /**
+   * Returns the current user session object.
+   *
+   * @return \Drupal\Core\Session\AccountInterface
+   *   The current user session object.
+   */
+  public function getUserSession() {
+    return $this->userSession;
+  }
+
+  /**
+   * Sets current session from the user global.
+   */
+  public function setUserSessionFromGlobal() {
+    global $user;
+    $this->userSession = &$user;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function start() {
+    // Check whether there is a session started from somewhere else but here.
+    if (!($this->isStarted() && $this->storage->getSaveHandler()->getId())) {
+      // Save current session data before starting it, as PHP will destroy it.
+      $session_data = isset($_SESSION) ? $_SESSION : NULL;
+      try {
+        $this->storage->start();
+      }
+      catch (\RuntimeException $e) {
+        trigger_error($e->getMessage());
+      }
+      // Restore session data.
+      if (!empty($session_data)) {
+        $_SESSION += $session_data;
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save() {
+    if (!$this->isSaveEnabled()) {
+      return;
+    }
+    if ($this->userSession && $this->userSession->isAnonymous() && $this->isEmpty()) {
+      // There is no session data to store, destroy the session if there is one.
+      if ($this->isStarted() && $this->storage->getSaveHandler()->getId()) {
+        session_destroy();
+      }
+    }
+    else {
+      // There is session data to store. Start the session.
+      $this->start();
+      // Write the session data.
+      return $this->storage->save();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function migrate($destroy = FALSE, $lifetime = NULL) {
+    // Prevent regenerate if saving is disabled.
+    if (!$this->isSaveEnabled()) {
+      return;
+    }
+
+    if ($destroy) {
+      $this->getMetadataBag->stampNew();
+    }
+
+    $save_handler = $this->storage->getSaveHandler();
+    if ($save_handler instanceof CookieProxyInterface) {
+      if (!$save_handler->regenerate($destroy, $lifetime)) {
+        // Start the session when it doesn't exist yet.
+        // Preserve the logged in user, as it will be reset to anonymous by
+        // session read.
+        $account = $this->userSession;
+        $this->start();
+        $this->userSession = $account;
+      }
+      return TRUE;
+    }
+
+    return $this->storage->regenerate($destroy, $lifetime);
+  }
+
+  /**
+   * Enables session save.
+   *
+   * At commit time session will be saved by the session handler and session
+   * token will be sent.
+   */
+  public function enableSave() {
+    $this->saveEnabled = TRUE;
+  }
+
+  /**
+   * Disables session save.
+   *
+   * At commit time session save will be skipped and session token will not be
+   * sent to client.
+   *
+   * This function allows the caller to temporarily disable writing of
+   * session data, should the request end while performing potentially
+   * dangerous operations, such as manipulating the global $user object.
+   * See http://drupal.org/node/218104 for usage.
+   */
+  public function disableSave() {
+    $this->saveEnabled = FALSE;
+  }
+
+  /**
+   * Returns whether the session save is enabled or not.
+   *
+   * @return bool
+   *   TRUE if session save is enabled. FALSE otherwise.
+   */
+  public function isSaveEnabled() {
+    return $this->saveEnabled;
+  }
+
+  /**
+   * Returns whether this session is empty.
+   *
+   * @return bool
+   *   TRUE if session is empty.
+   */
+  public function isEmpty() {
+    if (!empty($_SESSION)) {
+      foreach ($_SESSION as $key => $value) {
+        if (strpos($key, '_sf') === FALSE) {
+          // A value not from a symfony bag.
+          return FALSE;
+        }
+      }
+    }
+    return !count($this->getFlashBag()->all()) && !$this->count();
+  }
+
+  /**
+   * Generates a default anonymous session.
+   *
+   * @return \Drupal\Core\Session\AccountInterface
+   *   The anonymous session object.
+   */
+  public function getAnonymousSession() {
+    return $this->storage->getSaveHandler()->getAnonymousSession();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Session/Storage/Handler/DatabaseSessionHandler.php b/core/lib/Drupal/Core/Session/Storage/Handler/DatabaseSessionHandler.php
new file mode 100644
index 0000000..e51ab7e
--- /dev/null
+++ b/core/lib/Drupal/Core/Session/Storage/Handler/DatabaseSessionHandler.php
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Session\Storage\Handler\DatabaseSessionHandler.
+ */
+
+namespace Drupal\Core\Session\Storage\Handler;
+
+use Drupal\Core\Database\DatabaseExceptionWrapper;
+use Drupal\Core\Database\Connection;
+
+/**
+ * Drupal database session handler.
+ *
+ * Load and save sessions using the {sessions} table.
+ */
+class DatabaseSessionHandler implements MixedModeHandlerInterface {
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection $connection
+   */
+  protected $connection;
+
+  /**
+   * Constructs a \Drupal\Core\Session\Handler\DatabaseSessionHandler.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The databse connection object.
+   */
+  public function __construct(Connection $connection) {
+    $this->connection = $connection;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function open($save_path, $session_name) {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function close() {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function destroy($session_id, array $options = array()) {
+    try {
+      $condition = !empty($options['secure']) ? 'ssid' : 'sid';
+      $this->connection
+        ->delete('sessions')
+        ->condition($condition, $session_id)
+        ->execute();
+    }
+    catch (DatabaseExceptionWrapper $e) {
+      throw new \RuntimeException(sprintf('DatabaseException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e);
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function gc($lifetime) {
+    try {
+      $this->connection
+        ->delete('sessions')
+        ->condition('timestamp', REQUEST_TIME - $lifetime, '<')
+        ->execute();
+    }
+    catch (DatabaseExceptionWrapper $e) {
+      throw new \RuntimeException(sprintf('DatabaseException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e);
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function read($id, array $options = array()) {
+    $query = $this->connection
+      ->select('sessions', 's')
+      ->fields('s');
+    $query->join('users', 'u', 'u.uid = s.uid');
+    $query->fields('u');
+    if (!empty($options['secure'])) {
+      $query->condition('s.ssid', $id);
+    }
+    else {
+      $query->condition('s.sid', $id);
+    }
+    return $query->execute()->fetchAssoc();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function write($id, $data, array $extra = array(), array $options = array()) {
+    // Merge defaults.
+    $options += array(
+      'mixed_mode' => FALSE,
+      'secure' => FALSE,
+    );
+    // Use the session ID as 'sid' and an empty string as 'ssid' by default.
+    $key = array('sid' => $id, 'ssid' => '');
+    if ($options['secure']) {
+      $key['ssid'] = $id;
+      // The "mixed mode" setting allows a site to simultaneously use both
+      // secure and insecure session cookies. If enabled and both cookies are
+      // presented then use both keys.
+      if ($options['mixed_mode'] && !empty($extra['insecure_id'])) {
+        $key['sid'] = $extra['insecure_id'];
+        unset($extra['insecure_id']);
+      }
+    }
+    elseif ($options['mixed_mode']) {
+      unset($key['ssid']);
+    }
+
+    try {
+      $this->connection
+        ->merge('sessions')
+        ->key($key)
+        ->fields(array(
+          'session' => $data,
+        ) + $extra)
+        ->execute();
+    }
+    catch (DatabaseExceptionWrapper $e) {
+      throw new \RuntimeException(sprintf('DatabaseException was thrown when trying to write session data: %s', $e->getMessage()), 0, $e);
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function updateIdentifiers($new_id, $old_id, array $options = array()) {
+    // Merge defaults.
+    $options += array(
+      'mixed_mode' => FALSE,
+      'secure' => FALSE,
+    );
+    $fields = array('sid' => $new_id);
+    $condition_field = 'sid';
+    $condition_value = $old_id;
+    if ($options['secure']) {
+      $fields['ssid'] = $new_id;
+      $condition_field = 'ssid';
+    }
+
+    try {
+      $this->connection
+        ->update('sessions')
+        ->fields($fields)
+        ->condition($condition_field, $old_id)
+        ->execute();
+    }
+    catch (DatabaseExceptionWrapper $e) {
+      throw new \RuntimeException(sprintf('DatabaseException was thrown when trying to write session data: %s', $e->getMessage()), 0, $e);
+    }
+
+    return TRUE;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Session/Storage/Handler/MixedModeHandlerInterface.php b/core/lib/Drupal/Core/Session/Storage/Handler/MixedModeHandlerInterface.php
new file mode 100644
index 0000000..2bf6667
--- /dev/null
+++ b/core/lib/Drupal/Core/Session/Storage/Handler/MixedModeHandlerInterface.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Session\Storage\Handler\MixedModeHandlerInterface.
+ */
+
+namespace Drupal\Core\Session\Storage\Handler;
+
+/**
+ * Defines and interface for saving session data under mixed protocols.
+ */
+interface MixedModeHandlerInterface extends \SessionHandlerInterface {
+
+  /**
+   * Reads the current session.
+   *
+   * @param string $id
+   *   The session ID.
+   * @param array $options
+   *   (optional) Additional options.
+   *    - mixed_mode : (bool) Whether mixed mode sessions are enabled.
+   *    - secure: (bool) Whether the session is under a secure http connection.
+   *
+   * @return bool
+   *   TRUE on success.
+   *
+   * @see http://php.net/sessionhandlerinterface.read
+   */
+  public function read($id, array $options = array());
+
+  /**
+   * Writes the current session.
+   *
+   * @param string $id
+   *   The session ID.
+   * @param string $data
+   *   Session serialized data to save.
+   * @param array $extra
+   *   (optional) Additional info to store.
+   * @param array $options
+   *   (optional) Additional options.
+   *    - mixed_mode : (bool) Whether mixed mode sessions are enabled.
+   *    - secure: (bool) Whether the session is under a secure http connection.
+   *
+   * @return bool
+   *   TRUE on success.
+   *
+   * @see http://php.net/sessionhandlerinterface.write
+   */
+  public function write($id, $data, array $extra = array(), array $options = array());
+
+  /**
+   * Destroys the current session.
+   *
+   * @param string $id
+   *   The session ID.
+   * @param array $options
+   *   (optional) Additional options.
+   *    - mixed_mode : (bool) Whether mixed mode sessions are enabled.
+   *    - secure: (bool) Whether the session is under a secure http connection.
+   *
+   * @return bool
+   *   TRUE on success.
+   *
+   * @see http://php.net/sessionhandlerinterface.destroy
+   */
+  public function destroy($id, array $options = array());
+
+  /**
+   * Updates session identifiers.
+   *
+   * @param string $new_id
+   *   The new session id.
+   * @param string $old_id
+   *   The old session id.
+   * @param array $options
+   *   (optional) Additional options.
+   *    - mixed_mode : (bool) Whether mixed mode sessions are enabled.
+   *    - secure: (bool) Whether the session is under a secure http connection.
+   *
+   * @return bool
+   *   TRUE on success.
+   */
+  public function updateIdentifiers($new_id, $old_id, array $options = array());
+
+}
diff --git a/core/lib/Drupal/Core/Session/Storage/Proxy/CookieProxy.php b/core/lib/Drupal/Core/Session/Storage/Proxy/CookieProxy.php
new file mode 100644
index 0000000..9138902
--- /dev/null
+++ b/core/lib/Drupal/Core/Session/Storage/Proxy/CookieProxy.php
@@ -0,0 +1,434 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Session\Storage\Proxy\CookieProxy.
+ */
+
+namespace Drupal\Core\Session\Storage\Proxy;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Cookie;
+use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
+use Drupal\Core\Session\Storage\Handler\MixedModeHandlerInterface;
+use Drupal\Component\Utility\Crypt;
+use Drupal\Component\Utility\Settings;
+use Drupal\Core\Session\UserSession;
+
+/**
+ * Custom SessionHandlerProxy implementation.
+ *
+ * It allows us to handle the HTTP and HTTPS session cookies manually, and
+ * enforce strong security measures for the session handling.
+ */
+class CookieProxy extends SessionHandlerProxy implements CookieProxyInterface {
+
+  /**
+   * Whether we lazy loaded the session or not.
+   *
+   * @var bool
+   */
+  public $lazySession = FALSE;
+
+  /**
+   * An array of cookie to set on kernel.response event.
+   *
+   * @var array
+   *
+   * @see \Drupal\Core\EventSubscriber\SessionListener::setSessionCookies()
+   */
+  protected $cookies = array();
+
+  /**
+   * Session data from the last load.
+   *
+   * This is useful for comparing session values before hitting the database,
+   * avoiding unneeded overhead.
+   *
+   * @var array.
+   */
+  protected $lastRead;
+
+  /**
+   * The current request object.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   */
+  protected $request;
+
+  /**
+   * The read-only settings object.
+   *
+   * @var \Drupal\Component\Utility\Settings
+   */
+  protected $settings;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(\SessionHandlerInterface $handler, Settings $settings) {
+    parent::__construct($handler);
+    $this->settings = $settings;
+  }
+
+  /**
+   * Generates the session id for anonymous requests.
+   */
+  public function init() {
+    if (!$this->hasCookie()) {
+      // Set a session identifier for this request. This is necessary because we
+      // lazily start sessions at the end of this request, and some processes
+      // (like drupal_get_token()) needs to know the future session ID in
+      // advance.
+      $this->lazySession = TRUE;
+      $this->setId($this->generateAnonymousId());
+      if ($this->request->isSecure() && $this->settings->get('mixed_mode_sessions', FALSE)) {
+        $this->request->cookies->set($this->getInsecureCookieName(), $this->generateAnonymousId());
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCookies() {
+    return $this->cookies;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setRequest(Request $request) {
+    $this->request = $request;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isSessionHandlerInterface() {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function read($id) {
+    // Handle the case of first time visitors and clients that don't store
+    // cookies (eg. web crawlers).
+    $session = $this->request->getSession();
+    if (!$this->hasCookie()) {
+      $session->setUserSession($this->getAnonymousSession());
+      return '';
+    }
+    if ($this->request->isSecure()) {
+      $values = $this->handler->read($id, array('secure' => TRUE));
+      if (!$values && ($insecure_id = $this->getCookie($this->getInsecureCookieName()))) {
+        $values = $this->handler->read($insecure_id);
+      }
+    }
+    else {
+      $values = $this->handler->read($id);
+    }
+
+    // We found the client's session record and they are an authenticated,
+    // active user.
+    if ($values && $values['uid'] > 0 && $values['status'] == 1) {
+      // Add roles element to $user.
+      $rids = db_query("SELECT ur.rid FROM {users_roles} ur WHERE ur.uid = :uid", array(':uid' => $values['uid']))->fetchCol();
+      $values['roles'] = array_merge(array(DRUPAL_AUTHENTICATED_RID), $rids);
+      $user_session = new UserSession($values);
+    }
+    elseif ($values) {
+      // The user is anonymous or blocked. Only preserve two fields from the
+      // {sessions} table.
+      $user_session = new UserSession(array(
+        'session' => $values['session'],
+        'access' => $values['access'],
+      ));
+    }
+    else {
+      // The session has expired.
+      $user_session = $this->getAnonymousSession();
+      $user_session->session = '';
+    }
+
+    // Store the session that was read for comparison when writing the session.
+    $this->lastRead = array(
+      'id' => $id,
+      'data' => $user_session->session,
+    );
+    // Update current user's session.
+    $session->setUserSession($user_session);
+    return (string) $user_session->session;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function write($id, $data) {
+    $options = array(
+      'secure' => $this->request->isSecure(),
+      'mixed_mode' => $this->settings->get('mixed_mode_sessions', FALSE),
+    );
+    if ($options['secure'] && $options['mixed_mode']) {
+      // Store an insecure cookie if mixed mode sessions are enabled.
+      $cookie_name = $this->getInsecureCookieName();
+      $this->setCookie($cookie_name, $this->request->cookies->get($cookie_name), FALSE);
+    }
+
+    if (!$this->request->getSession()->isSaveEnabled()) {
+      return FALSE;
+    }
+
+    if (!$user_session = $this->request->getSession()->getUserSession()) {
+      // There are cases that session service is not initialized when
+      // session_write_close is called because the shutdown function is
+      // registered on NativeSessionStorage constructor.
+      return FALSE;
+    }
+
+    // Check whether $_SESSION has been changed in this request.
+    $is_changed = !isset($this->lastRead) || $this->lastRead['id'] != $id || $this->lastRead['data'] !== $data;
+
+    // For performance reasons, do not update the sessions table, unless
+    // $_SESSION has changed or more than 180 has passed since the last update.
+    if ($is_changed || !isset($user_session->timestamp) || REQUEST_TIME - $user_session->timestamp > $this->settings->get('session_write_interval', 180)) {
+      $extra = array(
+        'uid' => $user_session->id(),
+        'hostname' => $this->request->getClientIp(),
+        'timestamp' => REQUEST_TIME,
+      );
+      if ($options['secure'] && $options['mixed_mode']) {
+        $extra['insecure_id'] = $this->getInsecureCookie();
+      }
+      return (bool) $this->handler->write($id, $data, $extra, $options);
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function destroy($id) {
+    $this->destroyCookies();
+    // Reset $_SESSION and $user to prevent a new session from being started
+    // in drupal_session_commit().
+    if ($this->request->hasSession()) {
+      $session = $this->request->getSession();
+      $session->clear();
+      $session->setUserSession($this->getAnonymousSession());
+    }
+    return (bool) $this->handler->destroy($id, array('secure' => $this->request->isSecure()));
+  }
+
+  /**
+   * Destroys session related cookies.
+   */
+  protected function destroyCookies() {
+    $this->destroyCookie($this->getName());
+    if ($this->request->isSecure()) {
+      $this->destroyCookie($this->getInsecureCookieName());
+    }
+    else if ($this->settings->get('mixed_mode_sessions', FALSE)) {
+      $this->destroyCookie('S' . $this->getName(), TRUE);
+    }
+  }
+
+  /**
+   * Deletes a session cookie.
+   *
+   * @param string $name
+   *   Name of session cookie to delete.
+   *
+   * @param boolean $secure
+   *   Force the secure value of the cookie.
+   */
+  protected function destroyCookie($name, $secure = FALSE) {
+    $params = session_get_cookie_params();
+    if ($secure) {
+      $params['secure'] = $secure;
+    }
+    // This cookie will be deleted when a response fires.
+    $this->cookies[$name] = new Cookie($name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
+    // Remove also from the request object.
+    $this->request->cookies->remove($name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setCookie($name, $value, $secure = NULL) {
+    $params = session_get_cookie_params();
+    // If a session cookie lifetime is set, the session will expire
+    // $params['lifetime'] seconds from the current request. If it is not set,
+    // it will expire when the browser is closed.
+    $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
+    if ($secure === NULL) {
+      $secure = $params['secure'];
+    }
+    $this->cookies[$name] = new Cookie($name, $value, $expire, $params['path'], $params['domain'], $secure, $params['httponly']);
+    $this->request->cookies->set($name, $value);
+  }
+
+  /**
+   * Regenerate the session.
+   *
+   * Usually called when an anonymous user becomes authenticated or vice-versa.
+   *
+   * @see Drupal\Core\Session\Session::migrate()
+   *
+   * @param bool $destroy
+   *   (optional) If set to TRUE, destroy the old session.
+   * @param int $lifetime
+   *   Sets the cookie lifetime for the session cookie. A null value will leave
+   *   the system settings unchanged, 0 sets the cookie to expire with browser
+   *   session. Time is in seconds.
+   */
+  public function regenerate($destroy = FALSE, $lifetime = NULL) {
+    if ($lifetime !== NULL) {
+      ini_set('session.cookie_lifetime', $lifetime);
+    }
+    if (!($this->handler instanceof MixedModeHandlerInterface)) {
+      // Use the php built-in functionality.
+      return session_regenerate_id($destroy);
+    }
+
+    // Set an insecure cookie if mixed mode sessions are allowed.
+    if ($this->request->isSecure() && $this->settings->get('mixed_mode_sessions', FALSE)) {
+      $insecure_session_name = $this->getInsecureCookieName();
+      if (!$this->lazySession) {
+        $old_insecure_session_id = $this->request->cookies->get($insecure_session_name);
+      }
+      $insecure_id = $this->generateAthenticatedId();
+      $this->setCookie($insecure_session_name, $insecure_id, FALSE);
+    }
+
+    if ($this->request->getSession()->isStarted()) {
+      $old_session_id = $this->getId();
+    }
+    $new_session_id = $this->generateAthenticatedId();
+    // We cant call parent::setId() here, else it will throw exceptions because
+    // during session identifier regeneration, this component is considered as
+    // active.
+    session_id($new_session_id);
+
+    if ($destroy) {
+      $this->destroyCookies();
+    }
+
+    $options = array(
+      'secure' => FALSE,
+    );
+    if (isset($old_session_id) && $old_session_id) {
+      $this->setCookie($this->getName(), $this->getId());
+      if ($this->request->isSecure()) {
+        $options['secure'] = TRUE;
+        // If the "secure pages" setting is enabled, use the newly-created
+        // insecure session identifier as the regenerated sid.
+        if ($this->settings->get('mixed_mode_sessions', FALSE)) {
+          $new_session_id = $insecure_id;
+        }
+      }
+    }
+    elseif (isset($old_insecure_session_id)) {
+      // If logging in to the secure site, and there was no active session on the
+      // secure site but a session was active on the insecure site, update the
+      // insecure session with the new session identifiers.
+      $old_session_id = $old_insecure_session_id;
+    }
+    else {
+      // Nothing to update.
+      return FALSE;
+    }
+    return $this->handler->updateIdentifiers($new_session_id, $old_session_id, $options);
+  }
+
+  /**
+   * Checks whether there is an active cookie for the current visitor.
+   *
+   * @return bool
+   *   TRUE if there is either a secure or insecure cookie.
+   */
+  protected function hasCookie() {
+    $cookie = $this->getCookie();
+    $insecure_cookie = $this->getInsecureCookie();
+    return !empty($cookie) || !empty($insecure_cookie);
+  }
+
+  /**
+   * Retrieves the insecure cookie value from the current request.
+   *
+   * @return string
+   *   This session's cookie value.
+   */
+  protected function getInsecureCookie($name = NULL) {
+    if ($this->request->isSecure() && $this->settings->get('mixed_mode_sessions', FALSE)) {
+      return $this->getCookie($this->getInsecureCookieName());
+    }
+    return '';
+  }
+
+  /**
+   * Retrieves the cookie value from the current request.
+   *
+   * @return string
+   *   This session's cookie value.
+   */
+  protected function getCookie() {
+    return $this->request->cookies->get($this->getName());
+  }
+
+  /**
+   * Calculates the insecure cookie name based on current session's name.
+   *
+   * @return string
+   *   This session's cookie name.
+   */
+  protected function getInsecureCookieName() {
+    return substr($this->getName(), 1);
+  }
+
+  /**
+   * Generates a random session name for anonymous requests.
+   *
+   * Those are much faster to generate compared to the authenticated ones.
+   *
+   * @return string
+   *   A random generated string which can be used as session name.
+   */
+  protected function generateAnonymousId() {
+    return Crypt::hashBase64(uniqid(mt_rand(), TRUE));
+  }
+
+  /**
+   * Generates a random session name for authenticated requests.
+   *
+   * @return string
+   *   A random generated string which can be used as session name.
+   */
+  protected function generateAthenticatedId() {
+    return Crypt::hashBase64(uniqid(mt_rand(), TRUE) . Crypt::randomBytes(55));
+  }
+
+  /**
+   * Generates a default anonymous session.
+   *
+   * @return \Drupal\Core\Session\AccountInterface
+   *   The anonymous session object.
+   */
+  public function getAnonymousSession() {
+    $values = array(
+      'uid' => 0,
+      'hostname' => $this->request->getClientIP(),
+      'roles' => array(DRUPAL_ANONYMOUS_RID),
+    );
+    return new UserSession($values);
+  }
+
+  public function setActive($flag) {
+    $this->active = (bool) $flag;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Session/Storage/Proxy/CookieProxyInterface.php b/core/lib/Drupal/Core/Session/Storage/Proxy/CookieProxyInterface.php
new file mode 100644
index 0000000..a705b61
--- /dev/null
+++ b/core/lib/Drupal/Core/Session/Storage/Proxy/CookieProxyInterface.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Session\Storage\Proxy\CookieProxyInterface.
+ */
+
+namespace Drupal\Core\Session\Storage\Proxy;
+
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Defines an interface for session proxies that rely on cookies.
+ *
+ * It allows applying additional logic rather than relying on PHP's built in
+ * behavior, like, for example, lazy sending of cookies.
+ */
+interface CookieProxyInterface {
+
+  /**
+   * Retrieves all cookies related with the current session.
+   *
+   * @see \Drupal\Core\EventSubscriber\SessionListener::setSessionCookies()
+   */
+  public function getCookies();
+
+  /**
+   * Sets the current request which is used to retrieve cookie values from.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request $request
+   *   A request object.
+   */
+  public function setRequest(Request $request);
+
+  /**
+   * Sets a cookie to the cookies array, to be part of the response.
+   *
+   * @param string $name
+   *   The cookie name.
+   * @param mixed $value
+   *   The cookie value.
+   * @param bool|null $secure
+   *   Whether this cookie should only be transmitted over HTTPS from client.
+   *   If ommited the value from session_get_cookie_params() will be used.
+   */
+  public function setCookie($name, $value, $secure = NULL);
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Session/SessionTest.php b/core/modules/system/lib/Drupal/system/Tests/Session/SessionTest.php
index 278c77e..0e3dbc2 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Session/SessionTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Session/SessionTest.php
@@ -32,7 +32,7 @@ public static function getInfo() {
    * Tests for drupal_save_session() and drupal_session_regenerate().
    */
   function testSessionSaveRegenerate() {
-    $this->assertFalse(drupal_save_session(),'drupal_save_session() correctly returns FALSE (inside of testing framework) when initially called with no arguments.', 'Session');
+    $this->assertTrue(drupal_save_session(),'drupal_save_session() correctly returns TRUE when initially called with no arguments.', 'Session');
     $this->assertFalse(drupal_save_session(FALSE), 'drupal_save_session() correctly returns FALSE when called with FALSE.', 'Session');
     $this->assertFalse(drupal_save_session(), 'drupal_save_session() correctly returns FALSE when saving has been disabled.', 'Session');
     $this->assertTrue(drupal_save_session(TRUE), 'drupal_save_session() correctly returns TRUE when called with TRUE.', 'Session');
@@ -48,33 +48,6 @@ function testSessionSaveRegenerate() {
     $this->drupalLogin($user);
     $this->assertTrue(preg_match('/HttpOnly/i', $this->drupalGetHeader('Set-Cookie', TRUE)), 'Session cookie is set as HttpOnly.');
     $this->drupalLogout();
-
-    // Verify that the session is regenerated if a module calls exit
-    // in hook_user_login().
-    $user->name = 'session_test_user';
-    $user->save();
-    $this->drupalGet('session-test/id');
-    $matches = array();
-    preg_match('/\s*session_id:(.*)\n/', $this->drupalGetContent(), $matches);
-    $this->assertTrue(!empty($matches[1]) , 'Found session ID before logging in.');
-    $original_session = $matches[1];
-
-    // We cannot use $this->drupalLogin($user); because we exit in
-    // session_test_user_login() which breaks a normal assertion.
-    $edit = array(
-      'name' => $user->getUsername(),
-      'pass' => $user->pass_raw
-    );
-    $this->drupalPostForm('user', $edit, t('Log in'));
-    $this->drupalGet('user');
-    $pass = $this->assertText($user->getUsername(), format_string('Found name: %name', array('%name' => $user->getUsername())), 'User login');
-    $this->_logged_in = $pass;
-
-    $this->drupalGet('session-test/id');
-    $matches = array();
-    preg_match('/\s*session_id:(.*)\n/', $this->drupalGetContent(), $matches);
-    $this->assertTrue(!empty($matches[1]) , 'Found session ID after logging in.');
-    $this->assertTrue($matches[1] != $original_session, 'Session ID changed after login.');
   }
 
   /**
@@ -216,14 +189,12 @@ function testSessionWrite() {
     $this->assertNotEqual($times2->timestamp, $times1->timestamp, 'Sessions table was updated.');
 
     // Write the same value again, i.e. do not modify the session.
-    sleep(1);
     $this->drupalGet('session-test/set/foo');
     $times3 = db_query($sql, array(':uid' => $user->id()))->fetchObject();
     $this->assertEqual($times3->access, $times1->access, 'Users table was not updated.');
     $this->assertEqual($times3->timestamp, $times2->timestamp, 'Sessions table was not updated.');
 
     // Do not change the session.
-    sleep(1);
     $this->drupalGet('');
     $times4 = db_query($sql, array(':uid' => $user->id()))->fetchObject();
     $this->assertEqual($times4->access, $times3->access, 'Users table was not updated.');
@@ -236,6 +207,7 @@ function testSessionWrite() {
       'value' => 0,
       'required' => TRUE,
     );
+    sleep(1);
     $this->writeSettings($settings);
     $this->drupalGet('');
     $times5 = db_query($sql, array(':uid' => $user->id()))->fetchObject();
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
index c8b4cd3..4cafed8 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
@@ -14,6 +14,7 @@
 use Drupal\Core\Session\UserSession;
 use Drupal\simpletest\WebTestBase;
 use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Reference;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
@@ -52,12 +53,6 @@
    * Prepares the appropriate session for the release of Drupal being upgraded.
    */
   protected function prepareD8Session() {
-    // We need an IP when storing sessions
-    // so add a dummy request in the container.
-    $request = Request::create('http://example.com/');
-    $request->server->set('REMOTE_ADDR', '3.3.3.3');
-    $this->container->set('request', $request);
-
     // Generate and set a D7-compatible session cookie.
     $this->curlInitialize();
     $sid = Crypt::hashBase64(uniqid(mt_rand(), TRUE) . Crypt::randomBytes(55));
@@ -65,7 +60,7 @@ protected function prepareD8Session() {
 
     // Force our way into the session of the child site.
     drupal_save_session(TRUE);
-    _drupal_session_write($sid, '');
+    $this->container->get('session.storage.proxy')->write($sid, '');
     drupal_save_session(FALSE);
   }
 
@@ -129,6 +124,24 @@ protected function setUp() {
     if (!$this->setupDatabasePrefix) {
       return FALSE;
     }
+    // Setup the container for the session.
+    $this->container->register('session.storage.handler', 'Drupal\Core\Session\Storage\Handler\DatabaseSessionHandler')
+      ->addArgument(Database::getConnection('default'));
+    $this->container->register('session.storage.proxy', 'Drupal\Core\Session\Storage\Proxy\CookieProxy')
+      ->addArgument(new Reference('session.storage.handler'))
+      ->addArgument(settings());
+    $this->container->register('session.storage', 'Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage')
+      ->addArgument(array())
+      ->addArgument(new Reference('session.storage.proxy'));
+    $this->container->register('session', 'Drupal\Core\Session\Session')
+      ->addArgument(new Reference('session.storage'));
+    // We need a request when storing sessions
+    // so add a dummy request in the container.
+    $request = Request::create('http://example.com/');
+    $request->server->set('REMOTE_ADDR', '3.3.3.3');
+    $request->setSession($this->container->get('session'));
+    $this->container->set('request', $request);
+    $this->container->get('session.storage.proxy')->setRequest($request);
 
     // Load the database from the portable PHP dump.
     // The files may be gzipped.
@@ -161,15 +174,17 @@ protected function setUp() {
     $this->settingsSet('install_profile', $install_profile);
     $this->profile = $install_profile;
 
-    // Ensure that the session is not written to the new environment and replace
-    // the global $user session with uid 1 from the new test site.
-    drupal_save_session(FALSE);
-    // Load values for uid 1.
-    $values = db_query('SELECT * FROM {users} WHERE uid = :uid', array(':uid' => 1))->fetchAssoc();
-    // Load rolest.
-    $values['roles'] = array_merge(array(DRUPAL_AUTHENTICATED_RID), db_query('SELECT rid FROM {users_roles} WHERE uid = :uid', array(':uid' => 1))->fetchCol());
-    // Create a new user session object.
-    $user = new UserSession($values);
+    // Login as uid 1.
+    $user = db_query('SELECT * FROM {users} WHERE uid = :uid', array(':uid' => 1))->fetchObject();
+    // Load roles for the user object.
+    $roles = array(DRUPAL_AUTHENTICATED_RID => DRUPAL_AUTHENTICATED_RID);
+    $result = db_query('SELECT rid, uid FROM {users_roles} WHERE uid = :uid', array(':uid' => 1));
+    foreach ($result as $record) {
+      $roles[$record->rid] = $record->rid;
+    }
+    $user->roles = $roles;
+    // Set the session user.
+    $this->container->get('session')->setFromGlobal();
 
     // Generate and set a D8-compatible session cookie.
     $this->prepareD8Session();
diff --git a/core/modules/system/tests/modules/session_test/lib/Drupal/session_test/EventSubscriber/SessionTestSubscriber.php b/core/modules/system/tests/modules/session_test/lib/Drupal/session_test/EventSubscriber/SessionTestSubscriber.php
index 44e2882..f93d51e 100644
--- a/core/modules/system/tests/modules/session_test/lib/Drupal/session_test/EventSubscriber/SessionTestSubscriber.php
+++ b/core/modules/system/tests/modules/session_test/lib/Drupal/session_test/EventSubscriber/SessionTestSubscriber.php
@@ -24,13 +24,30 @@ class SessionTestSubscriber implements EventSubscriberInterface {
   protected $emptySession;
 
   /**
+   * The session service.
+   *
+   * @var \Drupal\Core\Session\Session $session
+   */
+  protected $session;
+
+  /**
+   * Constructs a \Drupal\session_test\EventSubscriber\SessionTestSubscriber.
+   *
+   * @param \Drupal\Core\Session\Session $session
+   *   The session object.
+   */
+  public function __construct(Session $session) {
+    $this->session = $session;
+  }
+
+  /**
    * Set header for session testing.
    *
    * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
    *   The Event to process.
    */
   public function onKernelRequestSessionTest(GetResponseEvent $event) {
-    $this->emptySession = intval(empty($_SESSION));
+    $this->emptySession = $this->session->isEmpty();
   }
 
   /**
diff --git a/core/modules/system/tests/modules/session_test/session_test.services.yml b/core/modules/system/tests/modules/session_test/session_test.services.yml
index 8ef2e20..8158c10 100644
--- a/core/modules/system/tests/modules/session_test/session_test.services.yml
+++ b/core/modules/system/tests/modules/session_test/session_test.services.yml
@@ -1,5 +1,6 @@
 services:
   session_test.subscriber:
     class: Drupal\session_test\EventSubscriber\SessionTestSubscriber
+    arguments: ['@session']
     tags:
       - { name: event_subscriber }
diff --git a/core/tests/Drupal/Tests/Core/Session/SessionTest.php b/core/tests/Drupal/Tests/Core/Session/SessionTest.php
new file mode 100644
index 0000000..6d184c2
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Session/SessionTest.php
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Session\SessionTest.
+ */
+
+namespace Drupal\Tests\Core\Session;
+
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
+use Drupal\Core\Session\Session;
+use Drupal\Tests\UnitTestCase;
+
+class SessionTest extends UnitTestCase {
+
+  /**
+   * Stores the session.
+   *
+   * @var \Drupal\Core\Session\Session
+   */
+  protected $session;
+
+  /**
+   * Stores the session storage.
+   *
+   * @var \Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage
+   */
+  protected $storage;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Session unit tests',
+      'description' => 'Unit test our symfony based session implementation',
+      'group' => 'System',
+    );
+  }
+
+  protected function setUp() {
+    parent::setUp();
+
+    $this->storage = $this->getMock('Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage', array('getSaveHandler', 'save'));
+    $session_proxy = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $this->storage
+      ->expects($this->any())
+      ->method('getSaveHandler')
+      ->will($this->returnValue($session_proxy));
+    $this->session = new Session($this->storage, new AttributeBag(), new FlashBag());
+  }
+
+  /**
+   * Tests that enabling/disabling works under different circumstances.
+   */
+  public function testEnableDisable() {
+    // Start session.
+    $this->session->start();
+    $this->storage
+      ->expects($this->exactly(2))
+      ->method('save');
+
+    $this->session->set('key', $this->randomName());
+
+    // First time $this->storage->save() is called.
+    $this->session->save();
+
+    $this->session->disableSave();
+    $this->assertFalse($this->session->isSaveEnabled());
+    // $this->storage->save() should not be called.
+    $this->session->save();
+
+    $this->session->enableSave();
+    $this->assertTrue($this->session->isSaveEnabled());
+    // Second time $this->storage->save() is called.
+    $this->session->save();
+  }
+
+  /**
+   * Tests for \Drupal\Core\Session\Session::isEmpty().
+   */
+  public function testSesssionIsEmpty() {
+    $this->session->clear();
+    // Session should be empty.
+    $this->assertTrue($this->session->isEmpty());
+
+    $this->session->set('key', $this->randomName());
+    $this->assertFalse($this->session->isEmpty());
+
+    $this->session->remove('key');
+
+    $this->assertTrue($this->session->isEmpty());
+  }
+
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Session/Storage/Proxy/CookieProxyTest.php b/core/tests/Drupal/Tests/Core/Session/Storage/Proxy/CookieProxyTest.php
new file mode 100644
index 0000000..012647e
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Session/Storage/Proxy/CookieProxyTest.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Session\Storage\Proxy\CookieProxyTest.
+ */
+
+namespace Drupal\Tests\Core\Session\Storage\Proxy;
+
+use Symfony\Component\HttpFoundation\Request;
+use Drupal\Core\Session\Session;
+use Drupal\Core\Session\Storage\Proxy\CookieProxy;
+use Drupal\Component\Utility\Settings;
+use Drupal\Tests\UnitTestCase;
+
+class CookieProxyTest extends UnitTestCase {
+
+  /**
+   * The session.
+   *
+   * @var \Drupal\Core\Session\Session
+   */
+  protected $session;
+
+  /**
+   * The request.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   */
+  protected $request;
+
+  /**
+   * The mocked session handler.
+   *
+   * @var \PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $handler;
+
+  /**
+   * Stores the proxy.
+   *
+   * @var \Drupal\Core\Session\Storage\Proxy\CookieProxy
+   */
+  protected $proxy;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Cookie proxy unit tests',
+      'description' => 'Unit tests of cookie session proxy',
+      'group' => 'System',
+    );
+  }
+
+  protected function setUp() {
+    parent::setUp();
+    $this->handler = $this->getMock('SessionHandlerInterface');
+    $this->request = new Request;
+    $this->proxy = new CookieProxy($this->handler, new Settings(array('mixed_mode_sessions' => TRUE)));
+    $this->session = new Session();
+    $this->request->setSession($this->session);
+    $this->proxy->setRequest($this->request);
+
+    $account = $this->getMock('Drupal\Core\Session\UserSession');
+
+    $account->expects($this->any())
+      ->method('id')
+      ->will($this->returnValue(10));
+
+    $this->session->setUserSession($account);
+  }
+
+  /**
+   * Test for \Drupal\Core\Session\Storage\Proxy\CookieProxy::write().
+   */
+  public function testWrite() {
+    $this->handler
+      ->expects($this->exactly(3))
+      ->method('write');
+
+    // First write.
+    $this->session->enableSave();
+    $this->proxy->write(rand(1, 10), array());
+
+    $this->session->disableSave();
+    // Should not write now.
+    $this->proxy->write(rand(1, 10), array());
+
+    $this->session->enableSave();
+    // Second write.
+    $this->proxy->write(rand(1, 10), array());
+
+    $this->assertEquals(0, count($this->proxy->getCookies()));
+    // Try writing an insecure mixed mode cookie.
+    $this->request->server->set('HTTPS', 'on');
+    // Third write.
+    $this->proxy->write(rand(100, 1000), array());
+    // We should have one cookie since HTTPS and mixed mode sessions are on.
+    $this->assertEquals(1, count($this->proxy->getCookies()));
+  }
+
+  /**
+   * Test for \Drupal\Core\Session\Storage\Proxy\CookieProxy::destroy().
+   */
+  public function testDestroy() {
+  }
+
+}
