~fkooman/php-secookie

Modern HTTP cookie and session library
remove DateTime from TestFileSessionStorage
add phpstan.neon
update (C) year, update .php-cs-fixer.dist.php

clone

read-only
https://git.sr.ht/~fkooman/php-secookie
read/write
git@git.sr.ht:~fkooman/php-secookie

You can also use your local clone with git send-email.

Summary: Modern HTTP cookie and session library

Description: An easy to use, secure, simple and modern PHP cookie and session library supporting multiple parallel sessions without using PHP's built-in session management.

License: MIT

builds.sr.ht status

#Introduction

This is an easy to use, secure, simple and modern PHP cookie and session library supporting multiple parallel sessions without using PHP's built-in session management.

Many thanks to Jørn Åne de Jong (Uninett) for invaluable feedback during the development.

#Why

The existing PHP way of using and configuring cookies and sessions is complicated and not secure by default. It has various configuration flags in php.ini that also vary in different versions of PHP.

For our purposes we also require support of multiple parallel sessions, this is very complicated with PHP's session management. With the introduction of the SameSite cookie value, this became even more important as in some situations it is recommended to use multiple sessions in parallel with different SameSite values, see e.g. section 8.8.2 of Cookies: HTTP State Management Mechanism.

#Requirements

Uses only core PHP extensions, no other dependencies.

#Use

Currently php-secookie is not hosted on Packagist. It may be added in the future. To use the library, you can add this to your composer.json:

{
    "repositories": [
        {
            "type": "vcs",
            "url": "https://git.sr.ht/~fkooman/php-secookie"
        }
    ],
    "require": {
        "fkooman/secookie": "^6"
    }
}

You can also download the signed source code archive from the project page under "release notes".

#API

#Cookies

Create a cookie foo with the value bar:

$myCookie = new fkooman\SeCookie\Cookie();
if(null === $cookieValue = $myCookie->get('foo')) {
    // no value for cookie "foo" (yet)
    $myCookie->set('foo', 'bar');
}

#Sessions

Start a new session, store a key foo with value bar, get the session value again, remove it and stop the session:

$mySession = new fkooman\SeCookie\Session();
$mySession->start();
$mySession->set('foo', 'bar');

echo $mySession->get('foo');

$mySession->remove('foo');
$mySession->stop();

Note that stopping the session is also done automatically at the end of the script by the destructor, so only call this if you need to stop the session and write the session data to storage.

Calling Session::set or Session::remove will always give a new session and destroy the old session, there is no need to manually "regenerate". When designing your application, only modify your session data when needed, e.g. during authentication, not on every page load.

You can also completely destroy the session and remove the session data:

$mySession->destroy();

The Session::set method only takes string as a second parameter. You MUST convert everything you want to store in your sessions to string, e.g. using PHP's built-in serialize(), or json_encode().

#Options

In order to modify cookie options, a CookieOptions object can be used as the parameter to the Cookie constructor, e.g.:

$myCookie = new fkooman\SeCookie\Cookie(
    fkooman\SeCookie\CookieOptions::init()->withSameSiteStrict()
);

You can use the following methods on CookieOptions:

  • withPath(string) - restrict the cookie to the provided path. The default restricts the cookie to the URL path that issues the cookie;
  • withMaxAge(int) - specify the maximum lifetime of the cookie in seconds;
  • withSameSiteNone() - only use this if you need to allow cross domain POST responses, e.g. when implementing a SAML SP
  • withSameSiteLax() - only send the cookie for cross domain "top level navigation", not for methods that can change state on the server, e.g. POST requests;
  • withSameSiteStrict() - do not send any cookie for any cross domain request
  • withoutSecure() - omits the Secure flag from the cookie options, this is ONLY meant for development!

NOTE: CookieOptions is immutable. This means that when you call withX() or withoutX() you get a copy of the current CookieOptions with the new value set. It will NOT modify the existing object!

#Session

In order to modify session options, a SessionOptions object can be used as the first parameter to the Session constructor. If you also want to modify the CookieOptions, specify a CookieOptions object as the second parameter, e.g.:

$mySession = new fkooman\SeCookie\Session(
    fkooman\SeCookie\SessionOptions::init()->withName('MYSID'),
    fkooman\SeCookie\CookieOptions::init()->withSameSiteStrict()
);

You can use the following methods on SessionOptions:

  • withName(string) - specify the session name. The default is SID;
  • withExpiresIn(DateInterval) - specify the time a session is valid, on the server,. The default is new DateInterval('PT30M'), which is, 30 minutes;

NOTE: SessionOptions is immutable. This means that when you call withX() or withoutX() you get a copy of the current SessionOptions with the new value set. It will NOT modify the existing object!

The third parameter is the SessionStorageInterface. You can specify the storage backend as shown below.

By creating multiple Session objects, you can have multiple parallel sessions.

#Storage Backends

#File

The file backend is the simplest to use and the default. By default it will use the same directory as PHP's built-in session storage as a place to store the session data. You can optionally specify your own path as well.

Example:

$sessionStorage = new fkooman\SeCookie\FileSessionStorage();

#Garbage Collection

In order to periodically remove expired sessions from the disk, you can use the following command, or for example run it daily from cron(8).

On CentOS/Fedora:

$ sudo /usr/bin/find /var/lib/php/session -ignore_readdir_race -type f -name "sses_6_*" -mtime +0 -delete

On Debian/Ubuntu:

$ sudo /usr/bin/find /var/lib/php/sessions -ignore_readdir_race -type f -name "sses_6_*" -mtime +0 -delete

#Memcache

In case you want to use load balancing for your application you can use memcached. By deploying this on multiple machines you can create redundancy.

Session data is written to all memcache servers. Reading session data will be attempted from all servers, but be satisfied with the first response. This allows you to add/remove memcache servers. As long as one stays up, the sessions will remain valid.

NOTE: there is currently NO synchronization between memcache servers, so if you add a new one, or reboot a server it will receive new sessions, but will never know about the "old" ones. If this is important you could write your own "sync" mechanism. If you do, please contribute it :-)

$sessionStorage = new fkooman\SeCookie\MemcacheSessionStorage(
    [
        'cache1.example.org:11211',
        'cache2.example.org:11211',
    ]
);

#Database

The database backend SHOULD not be used, as it is slow, especially when using databases over the network.

If you design your applications properly, at least session data is only read from the database and not written (back) at every page load as only modified session data is written to storage again.

$sessionStorage = new fkooman\SeCookie\PdoSessionStorage(
    new PDO('sqlite:///path/to/db.sqlite')
);

// to initialize the database
$sessionStorage->init();

#Testing

PHPUnit tests are included. You can easily run them:

$ vendor/bin/phpunit

#Resources

Please read the following resources so you have a good understanding of sessions and whether this library is suitable for your application or you are better of using any of the other available libraries or perhaps native PHP sessions.

#License

MIT.

#Contact

You can contact me with any questions or issues regarding this project. Drop me a line at fkooman@tuxed.net.

If you want to (responsibly) disclose a security issue you can also use the PGP key with key ID 9C5EDD645A571EB2 and fingerprint 6237 BAF1 418A 907D AA98 EAA7 9C5E DD64 5A57 1EB2.