The Jwt facade
The Jwt facade is the one obvious entry point. It is auto-registered as the global alias Jwt — or import RoundlyConsulting\Jwt\Facades\Jwt:
use RoundlyConsulting\Jwt\Facades\Jwt;
use RoundlyConsulting\Jwt\UserTokens\AccessTokenRequest;
// Mint an access token with a fluent, self-documenting request.
$issued = Jwt::mintAccessToken(
AccessTokenRequest::for($user->id)
->email($user->email, verified: $user->hasVerifiedEmail())
->tokenVersion($user->token_version)
->permissions('posts.view', 'posts.edit')
);
$claims = Jwt::verify($issued->token); // RS256 + iss/aud pinned
Jwt::logout($issued); // denylist it (one-call logout)
$current = Jwt::guard('api')->claims(); // current request's claims on that guard, or nullThe whole surface
// User tokens (RS256), for the configured jwt.audience
Jwt::mintAccessToken($request); Jwt::mint($sub, $scope, $ttl, $claims, $aud);
Jwt::mintChallengeToken($sub); Jwt::mintEmailVerifyToken($sub, $email);
Jwt::verify($jwt, ?$aud);
// One jwt guard — its own audience, settings and current claims
Jwt::guard('clients')->mintAccessToken($request); // aud = the guard's audience
Jwt::guard('clients')->mint($sub, $scope, $ttl, $claims);
Jwt::guard('clients')->verify($jwt);
Jwt::guard('clients')->claims(); // ?Claims of the current request on that guard
Jwt::guard('clients')->settings(); // JwtGuardSettings
Jwt::guard('clients')->audience(); // string
// Service tokens (HS256)
Jwt::services()->issue('billing', ['job' => 'sync']);
Jwt::services()->verify($jwt);
Jwt::services()->request('billing')->post(...); // Http client with a fresh bearer
Jwt::services()->authenticate($pendingRequest, 'billing');
Jwt::services()->claims(); // ?Claims of the calling service (service-jwt guard)
// Denylist & logout
Jwt::denylist()->has($jti); Jwt::denylist()->deny($jti, $until); Jwt::denylist()->denyToken($issued);
Jwt::logout($issued); Jwt::denyClaims($claims);
// Key publishing
Jwt::publicKey(); // RsaKey — the configured verification key
Jwt::jwks(); // ['keys' => [[kty, n, e, alg, use, kid?]]] — serve as /.well-known/jwks.json| Method | Returns | Purpose |
|---|---|---|
mintAccessToken(AccessTokenRequest $request) | IssuedToken | Mint an access token from the fluent request, for the configured audience. |
mint($subject, $scope, $ttl, $extraClaims = [], $audience = null) | IssuedToken | Mint any scope with an explicit TTL — a Scope case or a free string. |
mintChallengeToken($subject, $extraClaims = []) | IssuedToken | Mint a 2fa_pending token using challenge_ttl. |
mintEmailVerifyToken($subject, $email) | IssuedToken | Mint an email_verify token carrying the email, using verify_ttl. |
verify($jwt, $audience = null) | Claims | Verify an RS256 user token; dispatches TokenVerificationFailed and rethrows on failure. |
guard(string $name) | GuardTokens | One jwt guard — mint, verify, claims, settings, audience. Throws JwtMisconfigured for a non-jwt guard. |
services() | Services | HS256 service tokens — issue, verify, request, authenticate, claims. |
denylist() | Denylist | The jti denylist store. |
logout(IssuedToken $token) | void | Denylist a freshly issued token until its own expiry. |
denyClaims(Claims $claims) | void | Denylist a token from its verified claims (jti + exp). |
publicKey() | RsaKey | The configured verification key; KeyLoadFailed when none is configured. |
jwks() | array | The RFC 7517 JWK Set of publicKey() — serve it as /.well-known/jwks.json. |
fake() | JwtFake | Facade only: swap in the recording fake — see Testing. |
Every call routes to a container-bound contract resolved lazily, so a verify-only app with no private key can still resolve the facade, and host overrides and test doubles keep working.
The guard handle
Jwt::guard($name) returns a GuardTokens handle scoped to one jwt guard’s settings. Its mints and verifies go through the manager, so TokenVerificationFailed fires and Jwt::fake() records them:
| Method | Returns | Purpose |
|---|---|---|
mint($subject, $scope, $ttl, $extraClaims = []) | IssuedToken | Mint any scope for the guard’s audience. |
mintAccessToken(AccessTokenRequest $request) | IssuedToken | Mint an access token for the guard’s audience; a request naming another audience throws JwtMisconfigured. |
verify(string $jwt) | Claims | Verify against the guard’s audience — another guard’s token fails with ClaimMismatch. |
claims() | ?Claims | The current request’s verified claims on this guard, or null. Never another guard’s. |
settings() | JwtGuardSettings | The effective options the jwt driver builds the guard from. |
audience() | string | auth.guards.<name>.audience, else jwt.audience. |
The services handle
Jwt::services() returns a Services handle that resolves the ServiceTokenIssuer and ServiceTokenVerifier contracts on every call, so a host rebinding either is honoured — see Service tokens:
| Method | Returns | Purpose |
|---|---|---|
issue(?string $audience = null, array $claims = []) | IssuedToken | Issue a token; audience defaults to service.audience, registered claim names are rejected. |
verify(string $jwt) | Claims | Verify an inbound token addressed to this service. |
request(?string $audience = null) | PendingRequest | A new outbound request carrying a fresh token. |
authenticate(PendingRequest $request, ?string $audience = null) | PendingRequest | Attach a fresh token to an existing outbound request. |
claims(?string $guard = null) | ?Claims | The calling service’s verified claims on a service-jwt guard — the first with a caller, or exactly $guard. |
Publishing the verification key
Services that verify your tokens can fetch the public key as a standard JWK Set. jwks() publishes jwt.public_key_path with alg RS256, use sig and — when JWT_KID is set — the same kid the issuer writes into token headers:
Route::get('/.well-known/jwks.json', fn () => response()->json(Jwt::jwks()));Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.