Resolution cache
Cross-request resolution caching is disabled by default. When enabled, it applies exclusively to JWT bearer identity rehydration through model providers — the lookup that turns thesub claim in an access token back into a live identity model. Everything else stays live on every request.
What is cached: the identity model resolved from the bearer token’s sub claim.
What stays live on every request regardless of cache state:
- HTTP Basic credential lookups
- Bearer-path device resolution (
didclaim) - Principal resolution (
pidclaim) - The entire refresh flow — revocation and replay detection remain device-backed and immediate
Config block
config/authentication.php
Fields
The Laravel cache store to use for bearer identity caching. Accepts any store name configured in
config/cache.php (for example, redis, memcached, dynamodb). When left empty or unset, the cache is disabled regardless of TTL settings. You must set both this and identity_ttl_seconds to a non-zero value to enable caching..env
How long a cached bearer identity entry lives, in seconds.
0 disables the cache entirely. When non-zero, a cache hit on the bearer path skips the provider lookup for the identity model for subsequent requests within this window. Active-state checks, pid matching, and did device validation still run live on every request..env
Reserved for future use. Keep this at
0. Principal resolution runs live on every request and is not currently cached..env
Enabling the cache
Configure a cache store
Set
AUTHENTICATION_RESOLUTION_CACHE_STORE to a named store in your config/cache.php. A persistent store such as Redis is required — the in-memory array driver does not share state across requests..env
Wire explicit invalidation
Add a model observer (or equivalent write-path hook) to your identity model that calls Register the observer in a service provider:
ResolutionCacheInvalidator::forgetIdentity() whenever the identity is saved or deleted. This step is mandatory. Skipping it means cached entries survive identity updates, bans, or deletions until the TTL expires.app/Observers/UserObserver.php
app/Providers/AppServiceProvider.php
Credential timebox
Thebasic guard wraps every credential check in Illuminate\Support\Timebox to prevent timing side-channels. Without a timebox, an attacker can distinguish “username not found” (fast) from “wrong password” (slow, bcrypt) by measuring response latency, leaking the existence of accounts.
Config block
config/authentication.php
Field
The minimum number of microseconds the credential-validation path must take. The default of Setting this too low defeats the timing protection. Setting it higher than necessary adds latency to every Basic auth request. Choose a value that exceeds your hasher’s measured worst-case cost with a comfortable margin.
400000 (400 ms) is intentionally above the worst-case cost of bcrypt at cost factor 12, which typically runs between 150 ms and 250 ms. If you use a higher bcrypt cost or a different hasher, increase this value to exceed your worst-case hash time..env
Per-guard identifier field and principal resolvers
Both the identifier field used for credential lookups and the principal resolver can be layered per guard, following the same override pattern as JWT config.Identifier field
Thecredentials.identifier_field setting controls which column the basic guard uses when constructing the credential lookup from the HTTP Basic username:
config/authentication.php
The app-wide default field name passed to the identity provider when looking up credentials from an HTTP Basic username. Override when your identity model keys off
username, phone, key_id, or any other column..env
basic guard can override this app-wide default with an identifier_field entry directly in config/auth.php. This lets you register multiple Basic guards backed by different providers and different lookup columns in a single app:
config/auth.php
tenant_api guard looks up credentials against the key_id column on TenantApiKey, while cli uses the default email column on your user model.
Per-guard principal resolvers
By default, every guard resolves principals via the app-wideSineMacula\Laravel\Authentication\Contracts\PrincipalResolver container binding, which falls back to the package’s DefaultPrincipalResolver when no custom binding is registered. Any guard can override this with a principal_resolver entry in config/auth.php:
config/auth.php
auth.guards.<name>.principal_resolver— guard-local class, highest priority- The app-wide
PrincipalResolver::classcontainer binding - The package default
DefaultPrincipalResolver
app/Providers/AppServiceProvider.php