title: how to bind user cookie with user agent in drupal 8
body: <p>i am using drupal 8 with mysql database.
  but using cookie editor i am able to get user cookie and reuse them.
  this  also provides other users access, looked at drupal community for resolution but not found drupal 8 fix of this.
  i want to bind user cookie with browser user agent to make it more secure even user get cookie they will not be able to get user session access.</p>
  <p>here is my sessionmanager.php</p>
  <pre><code>&lt;?php
  
  namespace Drupal\Core\Session;
  
  use Drupal\Component\Utility\Crypt;
  use Drupal\Core\Database\Connection;
  use Drupal\Core\DependencyInjection\DependencySerializationTrait;
  use Symfony\Component\HttpFoundation\RequestStack;
  use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
  
  class SessionManager extends NativeSessionStorage implements SessionManagerInterface {
    use DependencySerializationTrait;
    protected $requestStack;
    protected $connection;
    protected $sessionConfiguration;
    protected $startedLazy;
    protected $writeSafeHandler;
  
    public function __construct(RequestStack $request_stack, Connection $connection, MetadataBag $metadata_bag, SessionConfigurationInterface $session_configuration, $handler = NULL) {
      $options = [];
      $this-&gt;sessionConfiguration = $session_configuration;
      $this-&gt;requestStack = $request_stack;
      $this-&gt;connection = $connection;
  
      parent::__construct($options, $handler, $metadata_bag);
  
  
      $this-&gt;bags = [];
    }
  
    /**
     * {@inheritdoc}
     */
    public function start() {
      if (($this-&gt;started || $this-&gt;startedLazy) &amp;&amp; !$this-&gt;closed) {
        return $this-&gt;started;
      }
  
      $request = $this-&gt;requestStack-&gt;getCurrentRequest();
      $this-&gt;setOptions($this-&gt;sessionConfiguration-&gt;getOptions($request));
  
      if ($this-&gt;sessionConfiguration-&gt;hasSession($request)) {
        // If a session cookie exists, initialize the session. Otherwise the
        // session is only started on demand in save(), making
        // anonymous users not use a session cookie unless something is stored in
        // $_SESSION. This allows HTTP proxies to cache anonymous pageviews.
        $result = $this-&gt;startNow();
      }
  
      if (empty($result)) {
        // Randomly generate a session identifier for this request. This is
        // necessary because \Drupal\Core\TempStore\SharedTempStoreFactory::get()
        // wants to know the future session ID of a lazily started session in
        // advance.
        //
        // @todo: With current versions of PHP there is little reason to generate
        //   the session id from within application code. Consider using the
        //   default php session id instead of generating a custom one:
        //   https://www.drupal.org/node/2238561
        $this-&gt;setId(Crypt::randomBytesBase64());
  
        // Initialize the session global and attach the Symfony session bags.
        $_SESSION = [];
        $this-&gt;loadSession();
  
        // NativeSessionStorage::loadSession() sets started to TRUE, reset it to
        // FALSE here.
        $this-&gt;started = FALSE;
        $this-&gt;startedLazy = TRUE;
  
        $result = FALSE;
      }
  
      return $result;
    }
  
    /**
     * Forcibly start a PHP session.
     *
     * @return bool
     *   TRUE if the session is started.
     */
    protected function startNow() {
      if ($this-&gt;isCli()) {
        return FALSE;
      }
  
      if ($this-&gt;startedLazy) {
        // Save current session data before starting it, as PHP will destroy it.
        $session_data = $_SESSION;
      }
  
      $result = parent::start();
  
      // Restore session data.
      if ($this-&gt;startedLazy) {
        $_SESSION = $session_data;
        $this-&gt;loadSession();
      }
  
      return $result;
    }
  
    /**
     * {@inheritdoc}
     */
    public function save() {
      if ($this-&gt;isCli()) {
        // We don't have anything to do if we are not allowed to save the session.
        return;
      }
  
      if ($this-&gt;isSessionObsolete()) {
        // There is no session data to store, destroy the session if it was
        // previously started.
        if ($this-&gt;getSaveHandler()-&gt;isActive()) {
          $this-&gt;destroy();
        }
      }
      else {
        // There is session data to store. Start the session if it is not already
        // started.
        if (!$this-&gt;getSaveHandler()-&gt;isActive()) {
          $this-&gt;startNow();
        }
        // Write the session data.
        parent::save();
      }
  
      $this-&gt;startedLazy = FALSE;
    }
  
    /**
     * {@inheritdoc}
     */
    public function regenerate($destroy = FALSE, $lifetime = NULL) {
      // Nothing to do if we are not allowed to change the session.
      if ($this-&gt;isCli()) {
        return;
      }
  
      // We do not support the optional $destroy and $lifetime parameters as long
      // as #2238561 remains open.
      if ($destroy || isset($lifetime)) {
        throw new \InvalidArgumentException('The optional parameters $destroy and $lifetime of SessionManager::regenerate() are not supported currently');
      }
  
      if ($this-&gt;isStarted()) {
        $old_session_id = $this-&gt;getId();
        // Save and close the old session. Call the parent method to avoid issue
        // with session destruction due to the session being considered obsolete.
        parent::save();
        // Ensure the session is reloaded correctly.
        $this-&gt;startedLazy = TRUE;
      }
      session_id(Crypt::randomBytesBase64());
  
      // We set token seed immediately to avoid race condition between two
      // simultaneous requests without a seed.
      $this-&gt;getMetadataBag()-&gt;setCsrfTokenSeed(Crypt::randomBytesBase64());
  
      if (isset($old_session_id)) {
        $params = session_get_cookie_params();
        $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
        setcookie($this-&gt;getName(), $this-&gt;getId(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
        $this-&gt;migrateStoredSession($old_session_id);
      }
  
      $this-&gt;startNow();
    }
  
    /**
     * {@inheritdoc}
     */
    public function delete($uid) {
      // Nothing to do if we are not allowed to change the session.
      if (!$this-&gt;writeSafeHandler-&gt;isSessionWritable() || $this-&gt;isCli()) {
        return;
      }
      $this-&gt;connection-&gt;delete('sessions')
        -&gt;condition('uid', $uid)
        -&gt;execute();
    }
  
    /**
     * {@inheritdoc}
     */
    public function destroy() {
      session_destroy();
  
      // Unset the session cookies.
      $session_name = $this-&gt;getName();
      $cookies = $this-&gt;requestStack-&gt;getCurrentRequest()-&gt;cookies;
      // setcookie() can only be called when headers are not yet sent.
      if ($cookies-&gt;has($session_name) &amp;&amp; !headers_sent()) {
        $params = session_get_cookie_params();
        setcookie($session_name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
        $cookies-&gt;remove($session_name);
      }
    }
  
    /**
     * {@inheritdoc}
     */
    public function setWriteSafeHandler(WriteSafeSessionHandlerInterface $handler) {
      $this-&gt;writeSafeHandler = $handler;
    }
  
    /**
     * Returns whether the current PHP process runs on CLI.
     *
     * Command line clients do not support cookies nor sessions.
     *
     * @return bool
     */
    protected function isCli() {
      return PHP_SAPI === 'cli';
    }
  
    /**
     * Determines whether the session contains user data.
     *
     * @return bool
     *   TRUE when the session does not contain any values and therefore can be
     *   destroyed.
     */
    protected function isSessionObsolete() {
      $used_session_keys = array_filter($this-&gt;getSessionDataMask());
      return empty($used_session_keys);
    }
  
    /**
     * Returns a map specifying which session key is containing user data.
     *
     * @return array
     *   An array where keys correspond to the session keys and the values are
     *   booleans specifying whether the corresponding session key contains any
     *   user data.
     */
    protected function getSessionDataMask() {
      if (empty($_SESSION)) {
        return [];
      }
  
      // Start out with a completely filled mask.
      $mask = array_fill_keys(array_keys($_SESSION), TRUE);
  
      // Ignore the metadata bag, it does not contain any user data.
      $mask[$this-&gt;metadataBag-&gt;getStorageKey()] = FALSE;
  
      // Ignore attribute bags when they do not contain any data.
      foreach ($this-&gt;bags as $bag) {
        $key = $bag-&gt;getStorageKey();
        $mask[$key] = !empty($_SESSION[$key]);
      }
  
      return array_intersect_key($mask, $_SESSION);
    }
  
    /**
     * Migrates the current session to a new session id.
     *
     * @param string $old_session_id
     *   The old session ID. The new session ID is $this-&gt;getId().
     */
    protected function migrateStoredSession($old_session_id) {
      $fields = ['sid' =&gt; Crypt::hashBase64($this-&gt;getId())];
      $this-&gt;connection-&gt;update('sessions')
        -&gt;fields($fields)
        -&gt;condition('sid', Crypt::hashBase64($old_session_id))
        -&gt;execute();
    }
  
  }
  
  </code></pre>