Firestore Security Rules: Enforcing a Revocation Boundary for Revoked Sessions
0 reputation · 23 Nov 2023, 16:02 UTC
0 reputation · 23 Nov 2023, 16:02 UTC
Enforce least-privilege in Firestore Security Rules so a revoked session — say, after a password reset handled with the Admin SDK's refresh-token revocation — stops authorizing reads and writes immediately instead of up to an hour later.
exp claim even after refresh tokens are revoked.Assuming a recent firebase-admin release (v6 or later, to be verified against current documentation) and the modular Web SDK, it is unclear whether request.auth.token exposes standard claims such as auth_time reliably enough to compare against a per-user revocation timestamp, and whether the Emulator Suite reproduces production revocation timing.
33275 reputation · 23 Nov 2023, 22:22 UTC
No. Firestore Security Rules has no built-in refresh-token revocation check, and there is no supported lookup-free rule that rejects a token minted before a per-user revocation timestamp. The practical supported boundary is a rule that reads the user’s revocation timestamp with get() and compares it to request.auth.token.auth_time.
Use auth_time, not iat. auth_time represents the authentication event, while a refreshed token can keep the old auth_time. That is the behavior you want for revoking sessions. Comparing iat would let a post-revocation refresh slip through whenever the refresh token is still valid.
exp; revoking refresh tokens does not invalidate already issued ID tokens.request.auth plus documents read during rule evaluation.Store revocationTimestamp as integer epoch seconds, mirroring the trusted backend revocation time. The field must not be client-writable; otherwise a user could bypass the boundary by writing a future timestamp.
function sessionNotRevoked() {
return request.auth != null
&& request.auth.token.auth_time is int
&& get(/databases/$(database)/documents/users/$(request.auth.uid)).data.revocationTimestamp < request.auth.token.auth_time;
}This denies tokens whose original authentication predates the revocation timestamp, including refreshed tokens that retain the old auth_time. A missing user document or missing timestamp should fail closed.
After Admin SDK revocation, persist the revocation time into the user document. Verify the exact property name against your installed Admin SDK version; the pattern does not depend on a specific v6 feature.
const revokedAfter = Math.floor(new Date(user.tokensValidAfterTime).getTime() / 1000);
await db.doc(`users/${uid}`).set({ revocationTimestamp: revokedAfter }, { merge: true });A modular Web SDK forced refresh can help the active client pick up new custom claims when the refresh token is still valid:
const user = auth.currentUser;
if (user) {
try { await user.getIdToken(true); }
catch { await auth.signOut(); }
}But it does not invalidate existing valid JWTs held elsewhere, and behavior is not uniform across Web and native SDKs because token persistence and background refresh timing differ. If refresh tokens were revoked, a forced refresh may fail; treat that as a sign-out path, not as proof that all sessions are closed.
| Approach | Revocation speed | Tradeoff |
|---|---|---|
| Custom claims only | Next token refresh, up to about one hour | No lookup, but stale privileges remain |
auth_time plus get() | Immediate for rules after timestamp write | One billed read per request; requires trusted user document |
Admin verifyIdToken revocation check | Server-side only | Not available to Firestore client rules per request |
The practical upper bound for custom-claims propagation is the remaining ID token lifetime, roughly up to one hour. That is not a hard SLA.
auth_time is present as an integer.revocationTimestamp to auth_time - 1 and expect allow; set it to auth_time + 1 and expect deny.get() resolves the user document during rule evaluation and account for the added read.If auth_time is absent for a provider, that flow cannot rely on this boundary; require reauthentication or deny access until a supported claim is present.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.