andrewdyer / jwt-auth
A lightweight, framework-agnostic JWT authentication orchestrator for PHP, built around contracts for user resolution, token handling, and claims generation
Requires
- php: ^8.3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.75
- phpunit/phpunit: ^10.5
This package is auto-updated.
Last update: 2026-03-26 00:11:29 UTC
README
A lightweight, framework-agnostic JWT authentication orchestrator for PHP, built around contracts for user resolution, token handling, and claims generation.
Introduction
This library provides a clean, contract-driven approach to JSON Web Token authentication by coordinating token issuance and parsing while delegating token handling, user resolution, and claims generation to user-defined implementations. By relying on simple interfaces, it remains fully framework-agnostic and unopinionated, allowing integration with any authentication system or JWT library.
Installation
composer require andrewdyer/jwt-auth
Requires PHP 8.3 or newer.
Getting Started
1. Implement the JWT subject
Any class that represents an authenticated user or entity must implement JwtSubjectInterface. This provides the identifier that will be stored in the token's sub claim.
use AndrewDyer\JwtAuth\Contracts\JwtSubjectInterface; class User implements JwtSubjectInterface { public function __construct(private int $id) {} public function getJwtIdentifier(): int|string { return $this->id; } }
2. Implement the auth provider
A class implementing AuthProviderInterface must be provided to resolve users by credentials or by ID. JwtAuth calls these methods internally during attempt() and authenticate().
use AndrewDyer\JwtAuth\Contracts\AuthProviderInterface; class MyAuthProvider implements AuthProviderInterface { public function byCredentials(string $username, string $password): ?JwtSubjectInterface { // Return a JwtSubjectInterface instance on success, or null on failure } public function byId(int|string $id): ?JwtSubjectInterface { // Return a JwtSubjectInterface instance, or null if not found } }
3. Implement the JWT provider
A class implementing JwtProviderInterface handles token encoding and decoding. This is the integration point for a preferred JWT library such as firebase/php-jwt or lcobucci/jwt.
use AndrewDyer\JwtAuth\Contracts\JwtProviderInterface; class MyJwtProvider implements JwtProviderInterface { public function encode(array $claims): string { // Encode the claims array into a signed token string } public function decode(string $token): mixed { // Decode and verify the token; return the payload as an array or object } }
4. Implement the claims factory
A class implementing ClaimsFactoryInterface builds the JWT claims for a given user. The iat, nbf, and exp fields accept plain Unix timestamps from time(), Carbon, or any other source.
use AndrewDyer\JwtAuth\Claims; use AndrewDyer\JwtAuth\Contracts\ClaimsFactoryInterface; use AndrewDyer\JwtAuth\Contracts\JwtSubjectInterface; class MyClaimsFactory implements ClaimsFactoryInterface { public function forSubject(JwtSubjectInterface $subject): Claims { $now = time(); return new Claims( iss: 'my-app', aud: 'my-api', iat: $now, nbf: $now, exp: $now + 3600, jti: bin2hex(random_bytes(16)), sub: $subject->getJwtIdentifier(), ); } }
When using Carbon, Carbon::now()->timestamp is a drop-in replacement for time().
Usage
Create a JwtAuth instance
The three dependencies are wired up to create a JwtAuth instance:
use AndrewDyer\JwtAuth\JwtAuth; $auth = new JwtAuth( authProvider: new MyAuthProvider(), jwtProvider: new MyJwtProvider(), claimsFactory: new MyClaimsFactory(), );
Attempt a login
Validates a username and password and returns a signed token. Throws InvalidCredentialsException if the credentials are invalid.
use AndrewDyer\JwtAuth\Exceptions\InvalidCredentialsException; try { $token = $auth->attempt('user@example.com', 'secret'); } catch (InvalidCredentialsException $e) { // Credentials did not match a valid user }
Authenticate a token
Decodes a token, verifies it, and returns the corresponding user. Throws InvalidTokenException if the token is invalid or the user cannot be found.
use AndrewDyer\JwtAuth\Exceptions\InvalidTokenException; try { $user = $auth->authenticate($token); } catch (InvalidTokenException $e) { // Token is invalid or the user no longer exists }
Parse a token
Decodes a token into a Claims object without looking up the user.
use AndrewDyer\JwtAuth\Exceptions\InvalidTokenException; try { $claims = $auth->parse($token); echo $claims->sub; // The subject identifier echo $claims->iss; // The issuer echo $claims->exp; // Expiry timestamp } catch (InvalidTokenException $e) { // Token could not be decoded }
Claims
The Claims class is a read-only value object representing the payload of a JWT. It exposes the standard registered claims as typed public properties:
| Property | Type | Description |
|---|---|---|
iss |
string |
Issuer |
aud |
?string |
Audience |
iat |
int |
Issued-at timestamp |
nbf |
int |
Not-before timestamp |
exp |
int |
Expiry timestamp |
jti |
string |
Unique token identifier |
sub |
int|string |
Subject identifier (user ID) |
custom |
array |
Any additional custom claims |
A Claims instance can be serialized back to an array using toArray(), which omits null values:
$array = $claims->toArray();
A Claims instance can also be constructed directly from an array. The claims iss, iat, nbf, exp, jti, and sub are all required; aud is optional and defaults to null if omitted. All values must match their expected types — throws InvalidTokenException if any required claim is missing or any claim has an invalid type:
$claims = Claims::fromArray([ 'iss' => 'my-app', 'aud' => 'my-api', 'iat' => 1711324800, 'nbf' => 1711324800, 'exp' => 1711328400, 'jti' => 'abc123', 'sub' => 42, ]);
Any keys not in the standard set are captured in the custom array.
Exceptions
| Exception | Thrown when |
|---|---|
InvalidCredentialsException |
attempt() is called and the credentials do not resolve to a valid user |
InvalidTokenException |
A token cannot be decoded, required claims are missing or have invalid types, or the subject cannot be resolved to a valid user |
Both extend RuntimeException.
License
Licensed under the MIT license and is free for private or commercial projects.