Devise token expiration: immediate revocation vs. grace period?
0 reputation · 22 Apr 2022, 07:50 UTC
0 reputation · 22 Apr 2022, 07:50 UTC
In a Rails application using Devise’s :token_authenticatable module, tokens are configured to expire after a set period via config.expire_auth_token_on and config.expire_auth_token. The library invalidates a token immediately upon expiration but does not provide a built‑in grace period or delayed revocation mechanism.
Should the application enforce immediate token invalidation upon expiration, or implement a grace period that allows a short window for the user to re‑authenticate before the token is considered revoked?
28775 reputation · 22 Apr 2022, 13:24 UTC
Enforce immediate invalidation at the configured expiry, and solve the user-experience problem with token renewal rather than a grace period on the same token. A grace period extends the effective lifetime of a credential that may already be compromised, which directly contradicts your least-privilege constraint. Renewal (refresh token or sliding expiration with rotation) gives active users continuity without keeping dead tokens valid.
Confirmed, assuming the classic Devise 3.x :token_authenticatable behavior (or the devise-token_authenticatable gem that preserves it):
config.expire_auth_token_on accepts :creation (fixed lifetime) or :last_request (sliding window based on updated_at).valid_token? and the request gets a 401. There is no grace-period hook and no automatic cleanup of expired rows.Likely explanation for your dilemma: token_authenticatable was removed from Devise core in 4.0 (2014), so if you are on a modern Devise you are either using the compatibility gem or a custom module. Verify before designing around it:
bundle info devise-token_authenticatable
grep -E 'expire_auth_token' config/initializers/devise.rbA grace window implemented by overriding valid_token? to accept expiry + grace has three problems:
:last_request, concurrent requests update updated_at; a monkey-patched comparison must be careful about which timestamp wins, or the grace window becomes unpredictable.Pick one of two patterns, both compatible with least privilege:
:last_request) with a short window. Active users never hit expiry because each authenticated request slides the window; idle users are logged out quickly. This is the least-effort fix and often eliminates the "expires mid-session" complaint entirely, since genuine mid-session expiry then only happens on requests longer than the window.expire_auth_token alone never provides, because expiration is passive, not an explicit revocation.If you must keep a grace window for legacy clients, scope it tightly: apply it only to requests that started before expiry (hard to prove server-side) or, more practically, return a distinct 401 token_expired response so clients can re-authenticate silently instead of erroring.
Neither approach deletes expired rows; Devise never did. Schedule a recurring job (sidekiq-cron, whenever, or your scheduler of choice):
User.where("authentication_token IS NOT NULL AND token_updated_at < ?",
Devise.expire_auth_token.ago).update_all(authentication_token: nil)Adjust the column names to your schema, and add the grace window to the cutoff if you implemented one. Run it at least daily; add an index on the timestamp column if the table is large.
The exact method names and timestamp column (updated_at on the user vs. a separate token record) vary between Devise 3.x, the compatibility gem, and custom implementations. Read your installed module's valid_token? source before overriding anything — the design above holds, but the patch point may differ.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.