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

A guard is an isolated audience of accounts — users, clients, staff — with its own model and table, config block, routes, JWT audience, refresh-token owner type, throttle keys and activity rows. Resolve one through the facade:

use RoundlyConsulting\Auth\Facades\Authentication;

$users = Authentication::guard('users');    // GuardContext
Authentication::guard();                    // the default guard (authentication.default)
Authentication::guards();                   // ['users', 'clients']

$users->name();                             // 'users'
$users->config();                           // typed GuardConfig
$users->accounts()->findForLogin('[email protected]');

Isolation

  • JWT — each jwt guard carries its own audience; a users token presented on a clients route is 401, even for the same primary key.
  • Refresh tokens — redeemed with the guard’s owner type; a token of another guard is unknown and not consumed.
  • Secrets — every link and code MAC includes the guard and purpose; lookups filter by guard.
  • Passkeys — passwordless login expects the guard’s owner type, so another guard’s credential is refused before its sign counter moves.
  • Distinct model morph classes are enforced: two guards on one model would share sessions.

Adding a guard

php artisan authentication:guard clients
php artisan authentication:guard staff --model=StaffMember --no-passkeys
php artisan authentication:check staff

authentication:guard writes app/Models/<Model>.php, a create_<table>_table migration (key type per authentication.key_type, the account columns, 2FA columns, passkey handle, nullable password, soft deletes) and a factory, then prints the authentication.guards block and the config/auth.php snippet. The password and email-verified columns follow authentication.columns.* in all three files. The guard and table names must be plain identifiers (lowercase letters, digits, underscores) and the model StudlyCase, or nothing is written. It never edits config. An existing *_create_<table>_table migration counts as the migration, so a re-run refuses instead of adding a second one; --force overwrites existing files — that migration in place.

The user provider

Point each auth.providers entry at the authentication driver with its guard name. The provider never resolves a disabled account and has no remember-me; a provider without a guard key throws AuthenticationMisconfigured.

The doctor

php artisan authentication:check {guard?} reports every validation problem plus: the provider uses the authentication driver for the right guard, the table has every needed column, URL templates contain {token}, routes enabled but not registered, readable JWT keys, the bound refresh-token revoker and mail configuration. Exit code 1 on any error. Warnings never fail the run: queued delivery on a sync queue, sync delivery (known-account responses get measurably slower), passwords.reset.enabled with password login off, registration.mode = closed with invitations on (they cannot be accepted), and two-factor on with two-factor.attempts = null — two-factor’s per-account limiter is then off, so the per-challenge max_attempts is the only bound on second-factor codes and every new login opens a fresh budget.

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.