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

Logout & denylist

A signed JWT stays valid until it expires, so logging out means recording its jti and refusing it for the rest of its lifetime:

use RoundlyConsulting\Jwt\Facades\Jwt;

Jwt::logout($issued);                 // from a freshly issued token
Jwt::denyClaims(Jwt::verify($jwt));   // or from verified claims (reads jti + exp)

// Power users not using the facade:
app(\RoundlyConsulting\Jwt\Denylist\Contracts\Denylist::class)->denyToken($issued);

A typical logout endpoint denylists the current request’s token:

Route::middleware('auth:api')->post('/logout', function () {
    Jwt::denyClaims(Jwt::guard('api')->claims());   // throws ClaimMismatch if jti/exp are absent

    return response()->noContent();
});

The denylist store

Jwt::denylist() returns the Denylist contract, backed by CacheDenylist:

Jwt::denylist()->deny($jti, $expiresAt);   // CarbonImmutable $until
Jwt::denylist()->has($jti);                 // true
  • has(string $jti): bool — whether a token id is currently denied.
  • deny(string $jti, CarbonImmutable $until): void — deny until the instant plus the verifier leeway; a token already past exp + leeway is a no-op.
  • denyToken(IssuedToken $token): void — deny a freshly issued token until its own expiresAt.

Entries live on the jwt.denylist.store cache store (default redis) under the jwt:denylist: prefix, with a TTL of the token’s remaining lifetime plus jwt.leeway — the verifier still accepts a token until exp + leeway, so the denial lasts exactly as long. No unbounded growth. Every denial dispatches TokenDenied.

How the guard enforces it

The jwt guard rejects denylisted tokens while guard.check_denylist is true. The check is fail-closed: a token with no jti can’t be looked up, so it can never authenticate while denylisting is on. Set JWT_CHECK_DENYLIST=false to skip the cache round-trip entirely, or switch it per guard with check_denylist. A blank JWT_CHECK_DENYLIST= is not set, so the check stays on.

To log a user out of every device at once, bump their token version instead — see Guards.

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.