---
title: "Multiple guards — JWT for Laravel | Roundly"
description: "Run several jwt guards — users, clients — each isolated by its own audience, with per-guard scope, identity and denylist options."
url: https://roundly-consulting.com/open-source/docs/jwt-for-laravel/multiple-guards
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)

# Multiple guards

Several account types — say users and clients, each with its own table — can each run on their own jwt guard. Every jwt guard resolves sub through its own provider, so give each guard its own audience; otherwise a users token with sub = 5 authenticates as client #5:

```php
// config/auth.php
'guards' => [
    'users'   => ['driver' => 'jwt', 'provider' => 'users',   'audience' => env('JWT_USERS_AUDIENCE', 'app-users')],
    'clients' => ['driver' => 'jwt', 'provider' => 'clients', 'audience' => env('JWT_CLIENTS_AUDIENCE', 'app-clients')],
],
```

## Per-guard keys

Each guard reads these optional keys from its own auth.guards.<name> array. An unset or blank key defers to the global value, while a present but unusable one — a non-string scope, an identity that isn’t a ClaimsAuthenticatable — throws JwtMisconfigured naming the key:

| auth.guards.<name> key | Defers to | Type |
| --- | --- | --- |
| `audience` | `jwt.audience` | Non-empty string; unset or blank defers. |
| `scope` | `jwt.guard.scope` | String or Scope case; unset or blank defers, a non-string value throws JwtMisconfigured. |
| `token_version` | `jwt.guard.token_version` | Invokable class-string, or a closure (not config-cacheable). |
| `check_denylist` | `jwt.guard.check_denylist` | Bool; env spellings true/false, 1/0, on/off and yes/no are understood. Unset or blank defers — a blank is never read as false. An unparseable value throws JwtMisconfigured — it never defers to the global one. |
| `identity` | `jwt.guard.identity` | Class-string of a ClaimsAuthenticatable; unset or blank defers, anything else throws JwtMisconfigured. Unset at both levels, TokenUser. |

## Minting and reading per guard

Mint for, verify against and read a specific guard with Jwt::guard($name):

```php
$clients = Jwt::guard('clients');

$clients->mintAccessToken(AccessTokenRequest::for($client->id)); // aud = 'app-clients'
$clients->mint((string) $client->id, 'access', ttl: 900);        // any scope, for the guard's audience
$clients->verify($jwt);      // a users token fails here (ClaimMismatch)
$clients->audience();        // 'app-clients' — auth.guards.clients.audience, else jwt.audience
$clients->settings();        // JwtGuardSettings: guard, audience, scope, checkDenylist, identity, tokenVersion
$clients->claims();          // that guard's verified claims, or null — never another guard's
```

Jwt::guard() throws JwtMisconfigured for a guard that isn’t a jwt guard, and mintAccessToken() refuses a request that already names a different audience rather than silently re-addressing it. jwt.audience stays required — it is the default for every guard without its own audience. A token minted for one guard’s audience yields a 401 on every other guard.

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