diff --git a/core/core.services.yml b/core/core.services.yml
index 895b384..ad14750 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -379,3 +379,20 @@ services:
     class: Drupal\system\Plugin\ImageToolkitInterface
     factory_method: getDefaultToolkit
     factory_service: image.toolkit.manager
+  session.storage.backend:
+    class: Drupal\Core\Session\Storage\Handler\DatabaseSessionHandler
+    arguments: ['@database']
+  session.storage.proxy:
+    class: Drupal\Core\Session\Storage\Proxy\CookieOverrideProxy
+    arguments: ['@session.storage.backend', '@request']
+  session.storage:
+    class: Drupal\Core\Session\Storage\DrupalSessionStorage
+    arguments: [[], '@session.storage.proxy']
+  session:
+    class: Drupal\Core\Session\Session
+    arguments: ['@session.storage']
+  session.listener:
+    class: Drupal\Core\EventSubscriber\SessionListener
+    arguments: ['@service_container']
+    tags:
+      - { name: event_subscriber }
diff --git a/core/lib/Drupal/Core/EventSubscriber/SessionListener.php b/core/lib/Drupal/Core/EventSubscriber/SessionListener.php
new file mode 100644
index 0000000..f930aba
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/SessionListener.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\EventSubscriber\SessionListener.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+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 Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Response subscriber to handle session cookies.
+ */
+class SessionListener implements EventSubscriberInterface {
+
+  /**
+   * The currently active container object.
+   *
+   * @var \Symfony\Component\DependencyInjection\ContainerInterface
+   */
+  private $container;
+
+  /**
+   * Constructs a SessionListener object.
+   *
+   * @param \Drupal\Core\Session\Storage\DrupalSessionStorage $container
+   *  The currently active container object.
+   */
+  public function __construct(ContainerInterface $container) {
+    $this->container = $container;
+  }
+
+  /**
+   * Sets the object that handles the session data for this request.
+   *
+   * @param Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
+   *   The event to process.
+   */
+  public function setSessionHandler(GetResponseEvent $event) {
+    if ($event->getRequestType() !== HttpKernelInterface::MASTER_REQUEST) {
+      return;
+    }
+
+    $request = $event->getRequest();
+    if (!$this->container->has('session') || $request->hasSession()) {
+      return;
+    }
+    $session = $this->container->get('session');
+    $request->setSession($session);
+  }
+
+  /**
+   * 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;
+    }
+
+    $response = $event->getResponse();
+    $proxy = $this->container->get('session.proxy');
+    if (isset($proxy->cookies)) {
+      foreach ($proxy->cookies as $cookie) {
+        $response->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('setSessionHandler', 100);
+    $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..87377e1
--- /dev/null
+++ b/core/lib/Drupal/Core/Session/Session.php
@@ -0,0 +1,117 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Session\Session.
+ */
+
+namespace Drupal\Core\Session;
+
+use Symfony\Component\HttpFoundation\Session\Session as BaseSession;
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+
+/**
+ * Overrides Symfony's Session object in order to implement Drupal specific
+ * session features, such as 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;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save() {
+    // Session saving is checked upper, but avoid accidental save() trigger in
+    // case save is disabled.
+    // @todo May be should throw a \LogicException here?
+    if (!$this->isSaveEnabled()) {
+      return;
+    }
+
+    parent::save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function migrate($destroy = FALSE, $lifetime = NULL) {
+    // Prevent regenerate if saving is disabled.
+    if (!$this->isSaveEnabled()) {
+      return;
+    }
+
+    return $this->storage->regenerate($destroy, $lifetime);
+  }
+
+  /**
+   * Enable 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;
+
+    if ($this->lastSaveHandlerState !== NULL && $this->lastSaveHandlerState) {
+      $this->storage->getSaveHandler()->setActive($this->lastSaveHandlerState);
+    }
+  }
+
+  /**
+   * Disable 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;
+
+    // As a side effect, the save handler in some occasions can be reached by
+    // either PHP native session handling either Symfony session handling. By
+    // disabling it manually we ensure it won't save anything behind our back.
+    $saveHandler = $this->storage->getSaveHandler();
+
+    if ($this->lastSaveHandlerState = $saveHandler->isActive()) {
+       $saveHandler->setActive(FALSE);
+    }
+  }
+
+  /**
+   * Is the session save enabled.
+   *
+   * @return bool
+   */
+  public function isSaveEnabled() {
+    return $this->saveEnabled;
+  }
+
+  /**
+   * Does this session is empty.
+   *
+   * Note:
+   *   Bags can not be directly accessed via protected attributes, and they
+   *   don't have either a count() or isEmpty() method.
+   *
+   * @return bool
+   *   TRUE if session is empty.
+   */
+  public function isEmpty() {
+    return !count($this->getFlashBag()->all()) && !count($this->all());
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Session/Storage/DrupalSessionStorage.php b/core/lib/Drupal/Core/Session/Storage/DrupalSessionStorage.php
new file mode 100644
index 0000000..b2f017f
--- /dev/null
+++ b/core/lib/Drupal/Core/Session/Storage/DrupalSessionStorage.php
@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Session\Storage\DrupalSessionStorage.
+ */
+
+namespace Drupal\Core\Session\Storage;
+
+use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag;
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+use Drupal\Core\Session\Storage\Proxy\CookieOverrideProxy;
+
+/**
+ * Default session storage.
+ *
+ * This is a proxy class between the $_SESSION super global and the Session
+ * object bags.
+ */
+class DrupalSessionStorage extends NativeSessionStorage {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $options = array(), $handler = NULL, MetadataBag $meta_bag = NULL) {
+    // In the parent class, the session_register_shutdown() is called. Because
+    // PHP native session will run the close handler in the PHP shutdown hooks,
+    // most Drupal systems our handler relies on will be destructed before this
+    // call. This is the main reason why we need to extend Symfony's component
+    // in order to avoid the native shutdown to run.
+    $this->setMetadataBag($meta_bag);
+    $this->setOptions($options);
+    $this->setSaveHandler($handler);
+
+    // If a session cookie exists, initialize the session. Otherwise the
+    // session is only started on demand in DrupalSessionStorage::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 (!$this->saveHandler->lazySession) {
+      $this->start();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save() {
+    if (empty($user->uid) && empty($_SESSION)) {
+      // There is no session data to store, destroy the session if it was
+      // previously started.
+      if ($this->isStarted()) {
+        session_destroy();
+      }
+    }
+    else {
+      // There is session data to store. Start the session if it is not already
+      // started.
+      if (!$this->isStarted()) {
+        $this->start();
+        $this->saveHandler->setCookies($this->getName());
+      }
+      // Write the session data.
+      parent::save();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clear() {
+    parent::clear();
+
+    // Clearing the session is a signal sent when session is invalidated, this
+    // means we can mark the session handler as inactive so it won't attempt
+    // any empty session write. Our session handler will send session cookie at
+    // write time. This allows lazy cookie sending to the client.
+    $this->saveHandler->setActive(FALSE);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function regenerate($destroy = FALSE, $lifetime = NULL) {
+
+    if ($lifetime !== NULL) {
+      ini_set('session.cookie_lifetime', $lifetime);
+    }
+
+    if ($destroy) {
+      $this->metadataBag->stampNew();
+    }
+
+    // If the current save handler is our own we must rely its own session
+    // identifier generation method.
+    if ($this->saveHandler instanceof CookieOverrideProxy) {
+      return $this->saveHandler->regenerate($destroy);
+    }
+    else {
+      return session_regenerate_id($destroy);
+    }
+  }
+}
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..f52de94
--- /dev/null
+++ b/core/lib/Drupal/Core/Session/Storage/Handler/DatabaseSessionHandler.php
@@ -0,0 +1,135 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Storage\Session\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 \SessionHandlerInterface {
+
+  /**
+   * 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.
+   */
+  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) {
+    try {
+      $this->connection
+        ->delete('sessions')
+        ->condition('sid', $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($session_id) {
+    $data = $this->connection
+      ->query("SELECT s.* FROM {sessions} s WHERE s.sid = :sid", array(':sid' => $session_id))
+      ->fetchObject();
+    return !empty($data) ? $data->session : '';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function write($session_id, $data) {
+    try {
+      $this->connection
+        ->merge('sessions')
+        ->key(array(
+          'sid' => $session_id,
+        ))
+        ->fields(array(
+          'session' => $data,
+          'timestamp' => time(),
+        ))
+        ->execute();
+    }
+    catch (DatabaseExceptionWrapper $e) {
+      throw new \RuntimeException(sprintf('DatabaseException was thrown when trying to write session data: %s', $e->getMessage()), 0, $e);
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * Updates identifiers of a session.
+   */
+  public function updateIdentifiers($session_id, $identifiers, $secure = FALSE) {
+    try {
+      $this->connection
+        ->update('sessions')
+        ->fields($identifiers)
+        ->condition($secure ? 'ssid' : 'sid', $session_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/Proxy/CookieOverrideProxy.php b/core/lib/Drupal/Core/Session/Storage/Proxy/CookieOverrideProxy.php
new file mode 100644
index 0000000..dd52ea6
--- /dev/null
+++ b/core/lib/Drupal/Core/Session/Storage/Proxy/CookieOverrideProxy.php
@@ -0,0 +1,269 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Session\Storage\Proxy\CookieOverrideProxy.
+ */
+
+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\Component\Utility\Settings;
+
+/**
+ * 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 CookieOverrideProxy extends SessionHandlerProxy {
+
+  /**
+   * An array of cookie to set on the Response event.
+   *
+   * @var array
+   *
+   * @see \Drupal\Core\EventSubscriber\SessionListener::setSessionCookies()
+   */
+  public $cookies = array();
+
+  /**
+   * Whether we lazy loaded the session or not.
+   *
+   * 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.
+   *
+   * @var bool
+   */
+  public $lazySession = FALSE;
+
+  /**
+   * A request object.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   */
+  protected $request;
+
+  /**
+   * A settings object.
+   *
+   * @var \Drupal\Component\Utility\Settings
+   */
+  protected $settings;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(\SessionHandlerInterface $handler, Request $request, Settings $settings) {
+    parent::__construct($handler);
+    $this->request = $request;
+    $this->settings = $settings;
+
+    if ($id = $this->getIdFromCookie()) {
+      $this->setId($id);
+    }
+    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.
+      $this->lazySession = TRUE;
+      $this->regenerateId();
+      if ($this->request->isSecure() && $this->settings->get('mixed_mode_sessions', FALSE)) {
+        $insecure_session_name = substr($this->getName(), 1);
+        // @todo Get rid of me! http://drupal.org/node/1929288
+        $session_id = drupal_hash_base64(uniqid(mt_rand(), TRUE));
+        $this->request->cookies->set($insecure_session_name, $session_id);
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isSessionHandlerInterface() {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function write($id, $data) {
+
+    if (!$this->isActive()) {
+      return FALSE;
+    }
+
+    // Cookie sending must be when we are sure we need to keep the session, this
+    // ensure the lazy session init. Lazy session init is abusive talking we are
+    // not lazy initializing the session, but lazy sending the session cookie
+    // instead. Each anonymous user will intrinsically have a session tied, which
+    // allows to generate tokens for forms and such, but if the session ends up
+    // empty, the cookies will not be sent and the session will not be saved on
+    // disk.
+    if ($this->request->isSecure() && $this->settings->get('mixed_mode_sessions', FALSE)) {
+      // Store an insecure cookie if mixed mode sessions are enabled.
+      $cookie_name = substr($id, 1);
+      $this->setCookie($cookie_name, $this->request->cookies->get($cookie_name), FALSE);
+    }
+
+    return (bool) $this->handler->write($id, $data);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function destroy($id) {
+    $this->destroyCookies($id);
+
+    return (bool) $this->handler->destroy($id);
+  }
+
+  /**
+   * Get current session identifier from cookie, if any.
+   *
+   * @return string
+   *   Session identifier or NULL if none found.
+   */
+  protected function getIdFromCookie() {
+    if ($cookie = $this->request->cookies->get($this->getName())) {
+      return $cookie;
+    }
+    else if ($this->request->isSecure() && $this->settings->get('mixed_mode_sessions', FALSE)) {
+      return $this->request->cookies->get(substr($this->getName(), 1));
+    }
+  }
+
+  /**
+   * Destroys session related cookies.
+   */
+  protected function destroyCookies() {
+    $this->destroyCookie($this->getName());
+    if ($this->request->isSecure()) {
+      $this->destroyCookie(substr($this->getName(), 1));
+    }
+    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) {
+    if ($this->request->cookies->get($name) || (!$this->request->isSecure() && $secure)) {
+      $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, NULL, 1, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
+      // Remove also from the request object.
+      $this->request->cookies->remove($name);
+    }
+  }
+
+  /**
+   * Sets a cookie to the cookies array, for the session listener to send.
+   *
+   * @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.
+   */
+  protected 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.
+   *
+   * @todo See if this implementation can be made a part of Symfony.
+   *
+   * @see Drupal\Core\Session\Proxy\Storage\DrupalSessionStorage::regenerate()
+   *
+   * @param bool $destroy
+   *   (optional) If set to TRUE, destroy the old session.
+   */
+  public function regenerate($destroy = FALSE) {
+    if ($this->request->isSecure() && $this->settings->get('mixed_mode_sessions', FALSE)) {
+      $insecure_session_name = substr($this->getName(), 1);
+      if (!$this->lazySession) {
+        $old_insecure_session_id = $this->request->cookies->get($insecure_session_name);
+      }
+      // @todo Get rid of me! http://drupal.org/node/1929288
+      $session_id = drupal_hash_base64(uniqid(mt_rand(), TRUE) . drupal_random_bytes(55));
+      $this->setCookie($insecure_session_name, $session_id, FALSE);
+    }
+
+    $old_session_id = $this->getId();
+    $this->regenerateId();
+
+    if (isset($old_session_id)) {
+      $this->setCookie($this->getName(), $this->getId());
+      $fields = array('sid' => $this->getId());
+      if ($this->request->isSecure()) {
+        $fields['ssid'] = $this->getId();
+        // 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)) {
+          $fields['sid'] = $session_id;
+        }
+        $this->handler->updateIdentifiers($old_session_id, $fields, TRUE);
+      }
+      $this->handler->updateIdentifiers($old_session_id, $fields);
+    }
+    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.
+      $this->handler->updateIdentifiers($old_insecure_session_id, array('sid' => $session_id, 'ssid' => $this->getId()));
+    }
+
+    if ($destroy) {
+      $this->destroyCookies();
+    }
+    return TRUE;
+  }
+
+  /**
+   * Generates and sets a new session id.
+   */
+  public function regenerateId() {
+    // 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.
+    // @todo Get rid of me! http://drupal.org/node/1929288
+    $id = drupal_hash_base64(uniqid(mt_rand(), TRUE) . drupal_random_bytes(55));
+
+    // Do not call parent::setId() here, else it will throw exceptions because
+    // during session identifier regeneration, this component is considered as
+    // active.
+    session_id($id);
+  }
+
+}
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..66ddcd0
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Session/SessionTest.php
@@ -0,0 +1,94 @@
+<?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 \Drupal\Core\Session\Storage\DrupalSessionStorage
+   */
+  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 in different circumstances.
+   */
+  public function testEnableDisable() {
+    // Start session.
+    $this->session->start();
+    $this->storage
+      ->expects($this->exactly(2))
+      ->method('save');
+
+    // 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() {
+    // 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/DrupalSessionStorageTest.php b/core/tests/Drupal/Tests/Core/Session/Storage/DrupalSessionStorageTest.php
new file mode 100644
index 0000000..4f219f0
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Session/Storage/DrupalSessionStorageTest.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Session\Storage\DrupalSessionStorageTest.
+ */
+
+namespace Drupal\Tests\Core\Session\Storage;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
+use Drupal\Core\Session\Storage\DrupalSessionStorage;
+use Drupal\Component\Utility\Settings;
+use Drupal\Tests\UnitTestCase;
+
+class DrupalSessionStorageTest extends UnitTestCase {
+
+  /**
+   * Stores the handler.
+   *
+   * @var \Drupal\Core\Session\Storage\Proxy\CookieOverrideProxy
+   */
+  protected $handler;
+
+  /**
+   * Stores the session storage.
+   *
+   * @var \Drupal\Core\Session\Storage\DrupalSessionStorage
+   */
+  protected $storage;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Session storage unit tests',
+      'description' => 'Unit test drupal session storage',
+      'group' => 'System',
+    );
+  }
+
+  protected function setUp() {
+    parent::setUp();
+
+    $this->handler = $this->getMock('Drupal\Core\Session\Storage\Proxy\CookieOverrideProxy', array('setActive', 'regenerate'), array(new NullSessionHandler, new Request, new Settings(array())));
+    $this->storage = new DrupalSessionStorage(array(), $this->handler);
+    $this->storage->registerBag(new AttributeBag);
+  }
+
+  /**
+   * Tests for \Drupal\Core\Session\Storage\DrupalSessionStorage::clear().
+   */
+  public function testClear() {
+    $this->handler
+      ->expects($this->once())
+      ->method('setActive')
+      ->with(FALSE);
+    $this->storage->clear();
+  }
+
+  /**
+   * Tests for \Drupal\Core\Session\Storage\DrupalSessionStorage::regenerate().
+   */
+  public function testRegenerate() {
+    $this->handler
+      ->expects($this->at(0))
+      ->method('regenerate')
+      ->with(FALSE);
+    $this->handler
+      ->expects($this->at(1))
+      ->method('regenerate')
+      ->with(TRUE);
+    $this->storage->regenerate();
+    $this->storage->regenerate(TRUE);
+  }
+
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Session/Storage/Proxy/CookieOverrideProxyTest.php b/core/tests/Drupal/Tests/Core/Session/Storage/Proxy/CookieOverrideProxyTest.php
new file mode 100644
index 0000000..c1a332c
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Session/Storage/Proxy/CookieOverrideProxyTest.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Session\Storage\Proxy\CookieOverrideProxyTest.
+ */
+
+namespace Drupal\Tests\Core\Session\Storage\Proxy;
+
+use Symfony\Component\HttpFoundation\Request;
+use Drupal\Core\Session\Storage\Proxy\CookieOverrideProxy;
+use Drupal\Component\Utility\Settings;
+use Drupal\Tests\UnitTestCase;
+
+class CookieOverrideProxyTest extends UnitTestCase {
+
+  /**
+   * Stores the proxy.
+   *
+   * @var \Drupal\Core\Session\Storage\Proxy\CookieOverrideProxy
+   */
+  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->proxy = new CookieOverrideProxy($this->getMock('SessionHandlerInterface'), new Request, new Settings(array()));
+  }
+
+  /**
+   * Test for \Drupal\Core\Session\Storage\Proxy\CookieOverrideProxy::write().
+   */
+  public function testWrite() {
+    $this->handler
+      ->expects($this->once())
+      ->method('setActive')
+      ->with(FALSE);
+    $this->storage->clear();
+  }
+
+  /**
+   * Test for \Drupal\Core\Session\Storage\Proxy\CookieOverrideProxy::destroy().
+   */
+  public function testDestroy() {
+    $this->handler
+      ->expects($this->at(0))
+      ->method('regenerateId')
+      ->with(FALSE);
+    $this->handler
+      ->expects($this->at(1))
+      ->method('regenerateId')
+      ->with(TRUE);
+    $this->storage->regenerate();
+    $this->storage->regenerate(TRUE);
+  }
+
+
+}
