Skip to main content
Laravel Authentication ships two guards — jwt and basic — and both are sessionless. Neither writes a session cookie, reads a remember token, or maintains server-side auth state between requests. On every request the guard reads the credentials from the incoming HTTP request, resolves the identity and principal from your configured provider and resolver, and binds the contextual triple before your application code runs. When the request ends, that state is discarded.

The JWT guard (driver: jwt)

The JWT guard reads the Authorization: Bearer <token> header. When a token is present it decodes the JWT, validates the standard claims (iss, aud, typ, exp, and a configurable leeway), and then rehydrates the full contextual triple from live database state:
  1. The sub claim is used to load the identity from the configured provider.
  2. If the identity implements CanBeActive, the guard calls isActive() and rejects the request on false.
  3. The pid claim, if present, is passed to the principal resolver as a hint. The resolver must return a principal whose getPrincipalIdentifier() matches the hint exactly — the guard fails closed if the hint resolves to a different principal.
  4. The did claim, if present and if the identity implements HasDevices, is used to load the device record. A did that does not resolve to a known device causes the request to be rejected rather than silently downgraded to “no device.”
  5. All three objects are bound to the guard, making them available via Auth::identity(), Auth::principal(), and Auth::device().
Access tokens are self-contained and validated without a server-side token store. Revoking a device blocks refresh for that device, but already-issued access tokens remain valid until expiry unless the identity, principal, or device can no longer be rehydrated from live state.
The JWT guard also owns the refresh path. Call $guard->refresh($refreshToken) to exchange a refresh credential for a new access token and a rotated refresh token. The exchange validates the refresh token’s JWT claims, verifies the stored rotation digest with hash_equals, atomically rotates the device’s refresh key, and re-binds the contextual triple — all in one round trip.

The HTTP Basic guard (driver: basic)

The HTTP Basic guard reads PHP_AUTH_USER and PHP_AUTH_PW from the active request (surfaced as $request->getUser() and $request->getPassword()). When both are present it runs them through a timing-safe credential validation path:
  1. The username is looked up in the configured provider using the guard’s identifier_field (defaults to email).
  2. The entire retrieve-and-validate pipeline runs inside a Timebox with a configurable duration (default 400 ms). The elapsed time is uniform whether or not the identifier resolves, preventing user-enumeration via timing side-channel.
  3. On valid credentials, the guard checks the identity’s active state and resolves a principal via the PrincipalResolver.
  4. The identity and principal are bound to the guard. Because Basic auth carries no device token, Auth::device() always returns null for this guard.
Behind PHP-FPM + nginx, the Authorization header is not automatically forwarded into PHP_AUTH_USER/PHP_AUTH_PW. Without the following nginx directive, the guard sees no credentials and Auth::user() always returns null:
location ~ \.php$ {
    fastcgi_pass_header Authorization;
    # …rest of the fastcgi block
}
Apache with mod_php populates these variables automatically. This gotcha is specific to FastCGI transports.

Registering guards in config/auth.php

Register either guard exactly as you would any first-party Laravel guard — no service provider call required:
'guards' => [
    'api' => [
        'driver'   => 'jwt',
        'provider' => 'users',
    ],
    'cli' => [
        'driver'   => 'basic',
        'provider' => 'users',
    ],
],

'providers' => [
    'users' => [
        'driver' => 'model',
        'model'  => App\Models\User::class,
    ],
],
Your identity model implements Identity (and optionally Principal, HasPrincipals, HasDevices, CanBeActive). The guard infers which capabilities are available at runtime from the interfaces on the resolved model — no configuration flag needed.

Using guard middleware

Apply guards with standard Laravel middleware exactly as documented in the framework:
// Routes that require a valid JWT bearer token
Route::middleware('auth:api')->group(function () {
    Route::get('/profile', ProfileController::class);
});

// Routes that require HTTP Basic credentials
Route::middleware('auth:cli')->group(function () {
    Route::post('/deploy', DeployController::class);
});

How the contextual triple is exposed

Both guards store the resolved identity, principal, and device on the guard’s state after a successful authentication. The Auth facade forwards contextual accessor calls to the currently active guard:
use SineMacula\Laravel\Authentication\Facades\Auth;

Auth::identity();   // Identity|null   — the authenticated subject
Auth::principal();  // Principal|null  — the acting principal
Auth::device();     // Device|null     — the issuing device (null for Basic guard)
Auth::tenant();     // Tenant|null     — the tenant the principal acts within
Auth::type();       // string|null     — the tenant's type string, if declared
These reads are synchronous and free — the guard has already done the resolution work during the user() call that Laravel triggers on the first Auth::check() or route middleware pass.

Multiple guards with separate trust boundaries

You can register multiple JWT guards, each with its own signing secret, audience claim, and principal resolver. Tokens issued under one guard cannot authenticate against another because the aud claim is enforced on every parse:
'guards' => [
    'staff' => [
        'driver'   => 'jwt',
        'provider' => 'users',
        'jwt'      => [
            'secret'   => env('STAFF_JWT_SECRET'),
            'audience' => 'staff-api',
        ],
    ],
    'customer' => [
        'driver'   => 'jwt',
        'provider' => 'users',
        'jwt'      => [
            'secret'   => env('CUSTOMER_JWT_SECRET'),
            'audience' => 'customer-api',
        ],
    ],
],
Routes opt into a specific boundary via auth:staff or auth:customer middleware. Tokens minted for the staff-api audience are rejected by the customer guard, and vice versa. Each guard can also carry its own kid-rotation set for fully independent signing-key lifecycles. Issue tokens through the guard-scoped JWT service so the audience and signing material match the guard that will later verify them:
$accessToken = Auth::jwt('staff')->issueAccessToken($identity, $principal, $device);

JWT configuration

Configure signing secrets, key rotation, TTLs, leeway, and per-guard JWT overrides.

Advanced configuration

Per-guard principal resolvers, identifier fields, resolution caching, and timebox tuning.