---
title: "Logout & denylist — JWT for Laravel | Roundly"
description: "Log users out by denylisting a token’s jti — a cache-backed store that evicts each entry once the token can no longer verify."
url: https://roundly-consulting.com/open-source/docs/jwt-for-laravel/logout-denylist
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[JWT for Laravel](https://roundly-consulting.com/open-source/docs/jwt-for-laravel.md)

# 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:

```php
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:

```php
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:

```php
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](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

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

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/jwt-for-laravel.md)
