You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Firebase provider only supports ID-token Bearer auth (verify_token → firebase_admin.auth.verify_id_token). It has no support for Firebase session cookies (firebase_admin.auth.create_session_cookie / verify_session_cookie).
Firebase session cookies are the recommended primitive for cookie-based / SSR web sessions (a long-lived, Google-signed JWT set as an HttpOnly cookie, verified locally by the edge and authoritatively by the backend). A consumer adopting that pattern currently has to drop out of altissimo and hand-roll verification + a Ninja auth class, because:
There's no verify_session_cookie anywhere in the provider/service.
LayeredAuth.__call__ only delegates to identity when an Authorization: Bearer header is present — so even after suggestion: Loosen LayeredAuth identity type from NinjaHttpBearer to Protocol #10 loosened the identity type to a Protocol, a cookie-based identity can never fire in the layered/BFF pattern. Identity resolution is hard-coupled to the bearer header.
We hit this in a downstream service and implemented it locally (reference impl below); filing so it can live in the library and consumers can delete their bolt-on.
Proposed API surface
Provider (providers/firebase.py):
@staticmethoddefverify_session_cookie(cookie: str, *, check_revoked: bool=False) ->FirebaseUser:
"""Verify a Firebase session cookie → FirebaseUser. Mirrors verify_token."""# firebase_admin.auth.verify_session_cookie(cookie, check_revoked=check_revoked)# → map to FirebaseUser (see "Open question" re: get_user vs claims)
A cookie auth class, e.g. FirebaseSessionAuth(cookie_name="__session", check_revoked=False), reading request.COOKIES[cookie_name], returning FirebaseUser | None.
A combined bearer-or-cookie identity (either a flag on FirebaseAuth, or a FirebaseIdentityAuth) so a single auth= accepts both — this is what most consumers want during a Bearer→cookie migration.
LayeredAuth — the key change: when no Authorization: Bearer header is present, still consult a cookie-capable identity before falling through to anonymous. Options:
Make LayeredAuth cookie-aware directly (check the configured cookie when no bearer), or
StubFirebaseSessionAuth and/or make StubLayeredAuth cookie-aware, so downstream tests can exercise the cookie path.
Implementation note: django-ninja's TestClient builds a request whose request.COOKIES is a bare Mock (truthy .get()) unless COOKIES= is passed — the cookie reader should only trust an actual str so stubbed requests don't spuriously authenticate.
Framework parity: mirror in the fastapi and flask adapters.
Design questions to settle
FirebaseUser mapping — claims vs get_user. The Bearer path does verify_id_tokenthenget_user(uid) + _to_firebase_user (authoritative disabled/custom_claims, one RPC). For cookies we can either (a) build FirebaseUser straight from the verified cookie claims (uid/email/email_verified/name/picture — no RPC), or (b) do the same get_user round-trip for parity. Suggest (a) by default, with check_revoked=True covering the disabled/revoked cases without a second lookup.
check_revoked default. Session cookies are long-lived (up to 14 days), so revocation matters more than for a 1h ID token, and verify_session_cookie(check_revoked=True) also rejects disabled users — at the cost of one Firebase RPC per request. Lean toward True as the safe default (the backend is the authoritative revocation gate), configurable off for a local-signature-only fast path.
Do we also want a create_session_cookie helper in the provider (mint side), or keep minting in consumer code?
Acceptance criteria
FirebaseAuthProvider.verify_session_cookie(...) returning FirebaseUser, raising the same AuthUnauthorizedError/AuthForbiddenError family as verify_token.
AuthService.validate_firebase_session(...).
Ninja cookie auth class + a bearer-or-cookie identity, usable as a drop-in for FirebaseAuth().
LayeredAuth can resolve a cookie-based identity when no bearer header is present.
check_revoked plumbed through, documented, with a chosen default.
Test stubs support the cookie path (+ the TestClientCOOKIES Mock caveat handled).
fastapi + flask adapter parity.
Docs/README updated with the session-cookie flow.
Reference implementation (downstream, works today)
Verifier + the two Ninja entry points we run in production, for reference:
Related: #10 (loosening LayeredAuth identity to a Protocol) — this is the runtime counterpart: the type is loosened, but __call__ still can't drive a non-bearer identity.
Motivation
The Firebase provider only supports ID-token Bearer auth (
verify_token→firebase_admin.auth.verify_id_token). It has no support for Firebase session cookies (firebase_admin.auth.create_session_cookie/verify_session_cookie).Firebase session cookies are the recommended primitive for cookie-based / SSR web sessions (a long-lived, Google-signed JWT set as an
HttpOnlycookie, verified locally by the edge and authoritatively by the backend). A consumer adopting that pattern currently has to drop out of altissimo and hand-roll verification + a Ninja auth class, because:verify_session_cookieanywhere in the provider/service.LayeredAuth.__call__only delegates toidentitywhen anAuthorization: Bearerheader is present — so even after suggestion: Loosen LayeredAuth identity type from NinjaHttpBearer to Protocol #10 loosened the identity type to a Protocol, a cookie-based identity can never fire in the layered/BFF pattern. Identity resolution is hard-coupled to the bearer header.We hit this in a downstream service and implemented it locally (reference impl below); filing so it can live in the library and consumers can delete their bolt-on.
Proposed API surface
Provider (
providers/firebase.py):Service (
service.py):Ninja adapter (
ninja/__init__.py):FirebaseSessionAuth(cookie_name="__session", check_revoked=False), readingrequest.COOKIES[cookie_name], returningFirebaseUser | None.FirebaseAuth, or aFirebaseIdentityAuth) so a singleauth=accepts both — this is what most consumers want during a Bearer→cookie migration.LayeredAuth— the key change: when noAuthorization: Bearerheader is present, still consult a cookie-capable identity before falling through to anonymous. Options:LayeredAuthcookie-aware directly (check the configured cookie when no bearer), orLayeredAuthpre-extracting the bearer token. This is the cleaner fix and dovetails with suggestion: Loosen LayeredAuth identity type from NinjaHttpBearer to Protocol #10.Test stubs (
testing.py):StubFirebaseSessionAuthand/or makeStubLayeredAuthcookie-aware, so downstream tests can exercise the cookie path.TestClientbuilds a request whoserequest.COOKIESis a bareMock(truthy.get()) unlessCOOKIES=is passed — the cookie reader should only trust an actualstrso stubbed requests don't spuriously authenticate.Framework parity: mirror in the
fastapiandflaskadapters.Design questions to settle
FirebaseUsermapping — claims vsget_user. The Bearer path doesverify_id_tokenthenget_user(uid)+_to_firebase_user(authoritativedisabled/custom_claims, one RPC). For cookies we can either (a) buildFirebaseUserstraight from the verified cookie claims (uid/email/email_verified/name/picture — no RPC), or (b) do the sameget_userround-trip for parity. Suggest (a) by default, withcheck_revoked=Truecovering the disabled/revoked cases without a second lookup.check_revokeddefault. Session cookies are long-lived (up to 14 days), so revocation matters more than for a 1h ID token, andverify_session_cookie(check_revoked=True)also rejects disabled users — at the cost of one Firebase RPC per request. Lean towardTrueas the safe default (the backend is the authoritative revocation gate), configurable off for a local-signature-only fast path.create_session_cookiehelper in the provider (mint side), or keep minting in consumer code?Acceptance criteria
FirebaseAuthProvider.verify_session_cookie(...)returningFirebaseUser, raising the sameAuthUnauthorizedError/AuthForbiddenErrorfamily asverify_token.AuthService.validate_firebase_session(...).FirebaseAuth().LayeredAuthcan resolve a cookie-based identity when no bearer header is present.check_revokedplumbed through, documented, with a chosen default.TestClientCOOKIESMock caveat handled).Reference implementation (downstream, works today)
Verifier + the two Ninja entry points we run in production, for reference:
Related: #10 (loosening
LayeredAuthidentity to a Protocol) — this is the runtime counterpart: the type is loosened, but__call__still can't drive a non-bearer identity.