NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
JWT for Laravel

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 null

The 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
MethodReturnsPurpose
mintAccessToken(AccessTokenRequest $request)IssuedTokenMint an access token from the fluent request, for the configured audience.
mint($subject, $scope, $ttl, $extraClaims = [], $audience = null)IssuedTokenMint any scope with an explicit TTL — a Scope case or a free string.
mintChallengeToken($subject, $extraClaims = [])IssuedTokenMint a 2fa_pending token using challenge_ttl.
mintEmailVerifyToken($subject, $email)IssuedTokenMint an email_verify token carrying the email, using verify_ttl.
verify($jwt, $audience = null)ClaimsVerify an RS256 user token; dispatches TokenVerificationFailed and rethrows on failure.
guard(string $name)GuardTokensOne jwt guard — mint, verify, claims, settings, audience. Throws JwtMisconfigured for a non-jwt guard.
services()ServicesHS256 service tokens — issue, verify, request, authenticate, claims.
denylist()DenylistThe jti denylist store.
logout(IssuedToken $token)voidDenylist a freshly issued token until its own expiry.
denyClaims(Claims $claims)voidDenylist a token from its verified claims (jti + exp).
publicKey()RsaKeyThe configured verification key; KeyLoadFailed when none is configured.
jwks()arrayThe RFC 7517 JWK Set of publicKey() — serve it as /.well-known/jwks.json.
fake()JwtFakeFacade 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:

MethodReturnsPurpose
mint($subject, $scope, $ttl, $extraClaims = [])IssuedTokenMint any scope for the guard’s audience.
mintAccessToken(AccessTokenRequest $request)IssuedTokenMint an access token for the guard’s audience; a request naming another audience throws JwtMisconfigured.
verify(string $jwt)ClaimsVerify against the guard’s audience — another guard’s token fails with ClaimMismatch.
claims()?ClaimsThe current request’s verified claims on this guard, or null. Never another guard’s.
settings()JwtGuardSettingsThe effective options the jwt driver builds the guard from.
audience()stringauth.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:

MethodReturnsPurpose
issue(?string $audience = null, array $claims = [])IssuedTokenIssue a token; audience defaults to service.audience, registered claim names are rejected.
verify(string $jwt)ClaimsVerify an inbound token addressed to this service.
request(?string $audience = null)PendingRequestA new outbound request carrying a fresh token.
authenticate(PendingRequest $request, ?string $audience = null)PendingRequestAttach a fresh token to an existing outbound request.
claims(?string $guard = null)?ClaimsThe 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 crypto

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