**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](https://builds.sr.ht/~fkooman/php-secookie.svg)](https://builds.sr.ht/~fkooman/php-secookie?)

# 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](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-06#section-8.8.2).

# Requirements

Uses only core PHP extensions, no other dependencies.

# Use

Currently php-secookie is not hosted on [Packagist](https://packagist.org/). It 
may be added in the future. To use the library, you can add this to your 
`composer.json`:

```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`:

```php
$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:

```php
$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:

```php
$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

### Cookie

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

```php
$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.:

```php
$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:

```php
$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:

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

On Debian/Ubuntu:

```bash
$ 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](https://www.memcached.org/). 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 :-)

```php
$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.

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

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

# Security

Early versions of version 6 of the library used exclusively the PHP functions
`serialize()` and `unserialize()` to _serialize_ the session data. This is only
secure if the session data can't be manipulated outside of the application 
using this library. In version >= 6.2 there is the option to also _serialize_
to JSON. This will be the default (and only option) in version 7 of the 
library.

For example, to use the JSON serialization with `MemcacheSessionStorage`:

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

It is HIGLY RECOMMENDED! to always use `JsonSerializer()`!

# Testing

Unit tests are included. You can easily run them:

    $ vendor/bin/put

# 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.

- [Session Locking](https://ma.ttias.be/php-session-locking-prevent-sessions-blocking-in-requests/)
- [PHP Session Security Management](https://www.php.net/manual/en/features.session.security.management.php) 
- [Safe and Secure PHP sessions](https://paragonie.com/blog/2015/04/fast-track-safe-and-secure-php-sessions)
- [Cookies: HTTP State Management Mechanism](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-04)

# License

[MIT](LICENSE).

# Contact

You can contact me with any questions or issues regarding this project. Drop
me a line at [fkooman@tuxed.net](mailto: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`.
