initphp/auth

PHP authentication & authorization library with pluggable storage adapters (session, cookie, custom) and a small permission manager.

Maintainers

Package info

github.com/InitPHP/Auth

pkg:composer/initphp/auth

Statistics

Installs: 39

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

2.0.0 2026-05-24 17:15 UTC

This package is auto-updated.

Last update: 2026-05-24 17:15:46 UTC


README

A small PHP authentication & authorization library with pluggable storage adapters (session, signed cookie, or custom) and a tiny case-insensitive permission set.

Latest Stable Version Total Downloads CI License PHP Version Require

Features

  • Pluggable storage — pick SessionAdapter, CookieAdapter, or roll your own by implementing AdapterInterface.
  • Signed cookies — JSON payload sealed with constant-time HMAC-SHA256; tampered values are dropped before decoding ever runs.
  • Strict cookie defaultsSecure, SameSite=Lax, HttpOnly, and refusal of the unsafe SameSite=None + Secure=false combination.
  • Testable — inject a CookieWriterInterface to capture every setcookie() call in unit tests instead of touching response headers.
  • Tiny permission setPermission does case-insensitive membership checks and ships magic accessors ($perm->is_admin).
  • Honest contracts — typed properties, return types, @throws on every implementation-defined exception, PHPStan level 8 clean.

Requirements

  • PHP 8.0 or later (tested on 8.0 – 8.4)
  • ext-json, ext-hash (both bundled with default PHP builds)
  • initphp/parameterbag ^2.0

Installation

composer require initphp/auth

Quick start

Session-backed auth

use InitPHP\Auth\Segment;

session_start();

$auth = Segment::session('auth');
$auth->set('user_id', 42)->set('role', 'editor');

if ($auth->has('user_id')) {
    $user = loadUser($auth->get('user_id'));
}

$auth->destroy(); // unsets $_SESSION['auth']

Signed-cookie auth

use InitPHP\Auth\Segment;

$auth = Segment::cookie('auth', [
    // 32+ byte secret. Generate with bin2hex(random_bytes(32)) and
    // load it from configuration — never hard-code it in source.
    'salt'   => $_ENV['AUTH_COOKIE_SECRET'],
    'path'   => '/',
    'domain' => 'example.com',
]);

$auth->set('user_id', 42);
echo $auth->get('user_id'); // 42

$auth->destroy(); // emits a deletion cookie with matching path/domain

Permissions

use InitPHP\Auth\Permission;

// Comparison is case-insensitive: 'Editor', 'EDITOR', and 'editor'
// are the same permission. The constructor normalizes its input the
// same way push() and remove() do.
$perm = new Permission(['Editor', 'post_list', 'post_edit']);

if ($perm->is('editor')) {
    $perm->push('user');         // returns 1
    $perm->remove('post_edit');  // returns 1
}

$perm->is('admin', 'editor');    // true if any of the names is present
isset($perm->is_admin);          // magic accessor for templates

Public API

Segment

Method Purpose
Segment::session(string $name, array $options = []): self Build a segment backed by $_SESSION.
Segment::cookie(string $name, array $options): self Build a segment backed by a signed cookie (salt required).
Segment::custom(string $name, class-string $adapterClass, array $options = []): self Build a segment backed by your own adapter.
Segment::create(string $name, int|string $adapter, array $options = []): self Legacy v1 factory; kept for BC.
adapter(): AdapterInterface Escape hatch for adapter-specific methods.
get/set/has/remove/collective/destroy Forwarded to the underlying adapter.

AdapterInterface

Method Purpose
get(string $key, mixed $default = null): mixed Look up a value or fall back to $default.
set(string $key, mixed $value): static Assign / replace a value.
collective(array $data): static Atomic bulk write. Cookie adapters emit one Set-Cookie instead of N.
has(string $key): bool Existence check (a stored null still counts as present).
remove(string ...$keys): static Drop one or more keys (missing keys are a no-op).
destroy(): bool Tear down the backing store. Subsequent calls raise RuntimeException.

Permission

Method Purpose
is(string ...$names): bool True when any of the names is present. Case-insensitive.
push(string ...$names): int Adds names, returns the count actually inserted.
remove(string ...$names): int Removes names, returns the count actually removed; the list is reindexed.
getPermissions(): list<string> Snapshot of the current permission list.

Magic accessors: $perm->is_admin (call), isset($perm->is_admin), unset($perm->is_admin).

CookieAdapter options

Key Type Default Notes
salt string — required At least 32 bytes. Use bin2hex(random_bytes(32)).
expires int|null now + 86 400 s Unix timestamp. null resets to the default.
path string '/' RFC 6265 path scope.
domain string '' Empty disables the Domain attribute.
secure bool true When false, modern browsers reject SameSite=None.
httponly bool true Blocks JS access via document.cookie.
samesite 'Lax'|'Strict'|'None' 'Lax' 'None' is rejected unless secure=true.

Cookie wire format

base64url(json_encode($data)) . "." . hash_hmac('sha256', $json, $salt)

The signature is verified with hash_equals() before the JSON is decoded, so a forged or modified cookie never reaches the parser.

Exceptions

Exception Raised when
InvalidArgumentException Missing/short/non-string salt, SameSite=None without Secure, unknown adapter constant, missing adapter class, class that does not extend AbstractAdapter.
RuntimeException SessionAdapter constructed with no active session, or any read/write on an adapter whose destroy() has been called.
BadMethodCallException Permission::__call() invoked with a name that does not start with is_.

Development

composer install
composer test         # PHPUnit
composer analyse      # PHPStan (level 8)
composer cs:check     # PHP-CS-Fixer dry-run
composer cs:fix       # PHP-CS-Fixer apply

CI runs the matrix across PHP 8.0, 8.1, 8.2, 8.3, and 8.4.

Documentation

Upgrading from v1

v2 ships intentional behaviour changes — most notably a new cookie format (old cookies become unreadable and are rolled), case-folding moved into the Permission constructor, a stricter cookie default profile, NullAdapter::has() returning false instead of true, and a clean adapter interface that no longer enforces a constructor signature. See docs/upgrading-from-v1.md.

Contributing & Security

Credits

License

Released under the MIT License.