Scheduled maintenance
Several tables grow on the token path and are not cleaned up automatically — you must schedule their pruning yourself:
- Passport’s tables (
oauth_access_tokens,oauth_refresh_tokens,oauth_auth_codes) grow one row per token issuance. With short access-token TTLs and refresh-token rotation, that’s a row on every refresh. Prune them with Passport’spassport:purge. - This package’s tables grow too:
oidc_authentication_contexts(one row per login) andoidc_access_token_contexts(one row per access-token issuance). Prune them withoidc:prune-authentication-contexts.
Schedule all three — running only some leaves tables growing unbounded, or leaves relying
parties unnotified of expired sessions. In routes/console.php:
use Illuminate\Support\Facades\Schedule;
Schedule::command('passport:purge')->daily();Schedule::command('oidc:dispatch-expired-session-logouts')->hourly();Schedule::command('oidc:prune-authentication-contexts')->daily();Order matters: dispatch before prune
Section titled “Order matters: dispatch before prune”oidc:dispatch-expired-session-logouts sends OIDC back-channel logout to a session’s
relying-party participants once the session hits its absolute lifetime (see
Logout). It must run ahead of oidc:prune-authentication-contexts, which
deletes oidc_sessions rows only after a grace window — scheduling dispatch first (and more
frequently) ensures every expired session is announced before its row is removed. Back-channel
logout is opt-in per relying-party client: a client only receives it if it has registered a
backchannel_logout_uri.
What prune deletes
Section titled “What prune deletes”oidc:prune-authentication-contexts deletes:
oidc_authentication_contextsrows past theirexpires_at(i.e. pastoidc.session.absolute_lifetimefrom login — the hard session cap). Once a context is gone, refreshing its tokens is denied.oidc_access_token_contextslink rows older thanoidc.session.absolute_lifetimeplus the refresh-token lifetime, so a still-rotating refresh chain never loses its link early (which would silently drop the deny-on-expiry cap). Retention is fully self-managed here and does not depend on howpassport:purgeis configured.
For large deployments, note that Passport’s oauth_refresh_tokens.expires_at is unindexed
upstream, so passport:purge’s expiry scan can be slow at scale — add an index via your own
migration if that becomes a bottleneck.