ez-php / auth
Authentication module for the ez-php framework — session and token-based auth with a flexible user provider interface
Requires
- php: ^8.5
- ez-php/cache: ^2.0
- ez-php/console: ^2.0
- ez-php/contracts: ^2.0
- ez-php/http: ^2.0
- ez-php/rate-limiter: ^2.0
Requires (Dev)
- ez-php/docker: ^2.0
- ez-php/orm: ^2.0
- ez-php/testing-application: ^2.0
- friendsofphp/php-cs-fixer: ^3.94
- phpstan/phpstan: ^2.1
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^13.0
Suggests
- ez-php/orm: Needed to run database/migrations/*.php (they build tables with EzPhp\Orm\Schema\Blueprint)
Provides
None
Conflicts
None
Replaces
None
- dev-main
- 2.5.8
- 2.5.7
- 2.5.6
- 2.5.5
- 2.5.4
- 2.5.3
- 2.5.2
- 2.5.1
- 2.5.0
- 2.4.11
- 2.4.10
- 2.4.9
- 2.4.8
- 2.4.7
- 2.4.6
- 2.4.5
- 2.4.4
- 2.4.3
- 2.4.2
- 2.4.1
- 2.4.0
- 2.3.9
- 2.3.8
- 2.3.7
- 2.3.6
- 2.3.5
- 2.3.4
- 2.3.3
- 2.3.2
- 2.3.1
- 2.3.0
- 2.2.1
- 2.1.1
- 2.1.0
- 2.0.1
- 2.0.0
- 1.14.0
- 1.13.1
- 1.13.0
- 1.12.2
- 1.12.1
- 1.12.0
- 1.11.2
- 1.11.1
- 1.11.0
- 1.10.0
- 1.9.2
- 1.9.1
- 1.9.0
- 1.8.0
- 1.7.1
- 1.7.0
- 1.6.1
- 1.6.0
- 1.5.1
- 1.5.0
- 1.4.2
- 1.4.1
- 1.4.0
- 1.3.0
- 1.2.0
- 1.1.1
- 1.1.0
- 1.0.1
- 1.0.0
- 0.9.3
- 0.9.2
- 0.9.1
- 0.9.0
- 0.8.6
- 0.8.5
- 0.8.4
- 0.8.3
- 0.8.2
- 0.8.1
- 0.8.0
- 0.7.0
- 0.6.1
- 0.6.0
- 0.5.1
- 0.5.0
- 0.4.1
- 0.4.0
- 0.3.0
- 0.2.0
- 0.1.0
This package is auto-updated.
Last update: 2026-10-16 17:37:02 UTC
README
Authentication module for the ez-php framework — session, Bearer token, JWT, and personal access token authentication with a flexible user provider interface.
Requirements
- PHP 8.5+
- ez-php/framework 0.*
Installation
composer require ez-php/auth
Setup
Register the service provider in your application:
$app->register(\EzPhp\Auth\AuthServiceProvider::class); // Optional — register JWT support: $app->register(\EzPhp\Auth\JwtServiceProvider::class);
Implement UserProviderInterface to connect your user storage:
use EzPhp\Auth\UserInterface; use EzPhp\Auth\UserProviderInterface; class UserProvider implements UserProviderInterface { // Session restoration (the `auth_id` stored by Auth::login()). public function findById(int|string $id): ?UserInterface { ... } // AuthMiddleware provider mode: the raw Bearer token from the request. public function findByToken(string $token): ?UserInterface { ... } // Auth::attempt(): look the user up by login name / e-mail before password_verify(). public function findByCredentials(string $identifier): ?UserInterface { ... } // Auth::attemptRemember(): receives the RAW token from the cookie — look it up by // hash('sha256', $token), never store or compare the raw value (see "Remember me"). public function findByRememberToken(string $token): ?UserInterface { ... } }
Your user class implements UserInterface: getAuthId() (the identifier stored in the
session) and getAuthPassword() (the password_hash() hash Auth::attempt() verifies against).
Bind it before AuthServiceProvider:
$this->app->bind(UserProviderInterface::class, UserProvider::class);
Usage
Session / Bearer token authentication
use EzPhp\Auth\Auth; // Authenticate Auth::login($user); $user = Auth::user(); Auth::logout(); // Protect routes with middleware $router->get('/dashboard', $handler)->middleware(\EzPhp\Auth\Middleware\AuthMiddleware::class);
Remember me
Auth::loginWithRemember($user) logs the user in and returns a random raw token. The module
does not persist it for you — the application stores it, and findByRememberToken() reads it back:
$token = Auth::loginWithRemember($user); // Persist only the hash (e.g. a `remember_token` column on the users table) … $users->updateRememberToken($user->getAuthId(), hash('sha256', $token)); // … and hand the raw token to the browser. $response = $response->withCookie('remember', $token, 60 * 60 * 24 * 30, secure: true, httpOnly: true, sameSite: 'Lax'); // On a later request without a session: $raw = $request->cookie('remember'); if (is_string($raw) && $raw !== '') { Auth::attemptRemember($raw, $provider); // provider: WHERE remember_token = hash('sha256', $raw) }
Store and compare only hash('sha256', $token) — a leaked database then yields no usable
cookie values. findByRememberToken() receives the raw cookie value precisely so the provider
does the hashing at the lookup. Rotate the stored hash on every remember-login and clear it on
logout.
The hash loginWithRemember() also writes to $_SESSION['auth_remember_token'] is a
same-session check only: Auth::verifyRememberToken($token) confirms that a presented cookie
was issued in this session (e.g. before a sensitive action). It cannot restore a login after
the session has ended — that is what the persisted hash and attemptRemember() are for.
JWT authentication
JWT_SECRET= # at least 32 bytes, e.g. php -r 'echo bin2hex(random_bytes(32));' JWT_TTL=3600
JWT_SECRET is required: resolving JwtManager throws when it is unset or shorter than
32 bytes (JwtManager::MIN_SECRET_BYTES, the HS256 minimum from RFC 7518), instead of
signing with an empty key that would let anyone forge tokens.
$jwt = $app->make(\EzPhp\Auth\Jwt\JwtManager::class); $token = $jwt->issue($user->getAuthId()); $claims = $jwt->validate($token); $response = ['access_token' => $token, 'token_type' => 'Bearer', 'expires_in' => $jwt->ttl()]; // Protect routes $router->get('/api/me', $handler)->middleware(\EzPhp\Auth\Middleware\JwtMiddleware::class);
Rate-limited login attempts
Login-attempt throttling is not part of this module (see "What Does NOT Belong Here") —
compose ez-php/rate-limiter's ThrottleMiddleware in front of your login route instead,
passing the route's limit as middleware parameters (maxAttempts,decaySeconds[,bucket]):
use EzPhp\RateLimiter\Middleware\ThrottleMiddleware; $app->middlewareAlias('throttle', ThrottleMiddleware::class); // before bootstrap // 5 attempts per 10 minutes in the 'login' bucket, keyed per client IP — independent // of the throttle guarding any other route. $router->post('/login', $handler) ->middleware('throttle:5,600,login') // runs first — throttled requests never reach AuthMiddleware ->middleware(AuthMiddleware::class);
Auth's static-façade design is untouched by this — the throttle lives entirely in the
middleware chain in front of it.
Example login/register scaffold
auth:scaffold writes a starting-point login/register/logout controller and routes file:
php ez auth:scaffold
Creates:
app/Controllers/AuthController.php—login()/register()/logout()built onAuth::login()/hashPassword()/verifyPassword()routes/auth.php— the matchingPOST /login,POST /register,POST /logoutroutes
Require the routes file from routes/web.php to activate it:
require __DIR__ . '/auth.php';
Refuses to run if AuthController.php already exists. Not a complete user-management system —
findUserByEmail()/createUser() in the generated controller throw RuntimeException until
you replace them with your application's actual user lookup/persistence; ez-php/auth has no
user model or schema to generate those against.
Register the command before bootstrap, same as auth:token:
$app->registerCommand(\EzPhp\Auth\Console\AuthScaffoldCommand::class);
Personal access tokens
$manager = $app->make(\EzPhp\Auth\PersonalAccessTokenManager::class); [$rawToken, $token] = $manager->create($userId, 'my-token', ['read', 'write']); $token = $manager->find($rawToken); $manager->revoke($token->id);
One-time tokens for e-mail verification or password reset live in the same table but are bound to one purpose, work once, and never authenticate as a Bearer token:
$raw = $manager->issueOneTime($userId, 'password-reset', ttl: 3600); // revokes earlier reset links // … send $raw in the reset link … $token = $manager->consume($rawFromLink, 'password-reset'); // null if invalid, expired, used or wrong purpose if ($token !== null) { // reset the password of $token->userId } $manager->revokeFor($userId, 'password-reset'); // e.g. after a successful reset
Register the bundled migration before migrating:
database/migrations/2024_01_01_000000_create_personal_access_tokens_table.php
Console command
# Generate a personal access token for a user (the raw token is printed once) php ez auth:token <user_id> <name> [--abilities=read,write] [--expires=3600]
--abilities defaults to * (all abilities); omit --expires for a token that never expires.
The command is opt-in — AuthServiceProvider does not register it, since issuing tokens from
the CLI should be a deliberate choice. Register it before bootstrap:
$app->registerCommand(\EzPhp\Auth\Console\TokenCommand::class);
Classes
| Class | Description |
|---|---|
Auth |
Static façade — login(), logout(), user(), check(), id(), hashPassword(), verifyPassword() |
AuthServiceProvider |
Registers Auth singleton; optionally injects UserProviderInterface |
UserInterface |
Contract for authenticated user objects — getAuthId(), getAuthPassword() |
UserProviderInterface |
Contract for user lookup — findById(), findByToken(), findByCredentials(), findByRememberToken() |
AuthorizableInterface |
Optional contract for authorization checks on user objects |
PersonalAccessToken |
Immutable value object — isExpired(), can() |
PersonalAccessTokenManager |
Token CRUD — create(), find(), revoke(), rotate(), pruneExpired(); one-time tokens — issueOneTime(), consume(), revokeFor() |
AuthMiddleware |
Bearer token middleware (static list or provider mode) |
JwtMiddleware |
JWT Bearer token middleware with optional blacklist and user resolution |
JwtManager |
Issues and validates HMAC-HS256 JWTs |
JwtBlacklist |
Cache-backed token blacklist (SHA-256 keyed) |
JwtServiceProvider |
Registers JwtManager and JwtBlacklist |
Console\TokenCommand |
auth:token CLI command |
Console\AuthScaffoldCommand |
auth:scaffold CLI command — example login/register controller + routes |
License
MIT — Andreas Uretschnig