How mobile auth is wired vs chromex session cookies vs lambda (no end-user auth).
Evidence from worktree 7bz0 code + design docs. Click cards, flow nodes, or matrix rows for paths.
Claims marked uncertain are flagged — ask before treating as fact.
Two auth surfaces, one identity model.
Chromex/web use Django sessionid cookies.
Mobile uses HS256 bearer JWTs (1h) + opaque refresh tokens (60d idle slide) stored in SecureStore — no cookie jar for API auth.
Both resolve to the same request.user (Django accounts.User) + request.mongo_user.
Lambda authenticates Google Pub/Sub only — not mobile users.
Need the vocabulary first? Open the Theory tab — cookies, Django sessions, JWT, PKCE, Bearer, CSRF, SecureStore — with official-doc links. Emailzap wiring lives in the other tabs.
Doc drift:docs/specs/mobile-app/05-mobile-auth-design.md §D-E still describes rotating refresh.
Live code is non-rotating (token_service.refresh). Prefer code + backend/mobile/docs/mobile-auth-api.md over that design section.
Prerequisite knowledge only. Claims below cite official standards / platform docs.
Emailzap-specific mapping called out in in Emailzap callouts — those map theory → this codebase, not invent standards.
Terms used across this artifact. Official meaning first; Emailzap usage in the right column when it differs or specializes.
Term
Meaning (official / general)
In this doc / Emailzap
Mint (verb)
Not an official IETF/OAuth term. Informal engineering jargon meaning issue / create / generate a new credential (token, code, JWT). RFCs usually say “issue,” “create,” or “obtain” (e.g. RFC 6749 refresh tokens are used to obtain access tokens).
Code/docs say mint_bearer_pair, “mint OTC,” “mint access” = server creates that credential. Same meaning as issue.
Cookie
HTTP state: server Set-Cookie → user-agent stores → client sends Cookie on later matching requests (MDN, RFC 6265).
Chromex/web transport for Django sessionid. Mobile API auth does not use cookies.
Session
Server-side (usually) bag of data for one visitor; cookie often holds only the session key (Django sessions). Also colloquially: “logged-in period.”
Django django_session + sessionid cookie (chromex). Mobile “session” in session-store.ts = SecureStore tokens + cached identity — not a Django session.
Session cookie (MDN sense)
Cookie with no Max-Age/Expires — discarded when browser session ends (MDN). Easy to confuse with “session id cookie.”
Django’s sessionid is typically persistent (SESSION_COOKIE_AGE), not an MDN “session cookie.” Naming collision — see § Session vs cookie.
Bearer token
Credential sent as Authorization: Bearer …; possession is enough (RFC 6750).
Mobile Emailzap API access JWT. Header set by axios interceptor.
Access token
Short-lived credential to call a protected API (OAuth / app-defined).
Mobile: HS256 JWT purpose=bearer_access (~1h). Google’s Gmail access token is a different access token.
Refresh token
Longer-lived credential used to obtain new access tokens without re-login (RFC 6749 §1.5).
Mobile Emailzap: opaque string; SHA-256 in Mongo. Google refresh token (Fernet on gmail_subscriptions) is separate.
Delegation protocol: user authorizes client to access resources at a resource server (RFC 6749). Not itself “login to your app,” though apps often wrap it for signup.
Google OAuth obtains Gmail API scopes + identity. Emailzap then creates Django/Mongo user and issues its own session or bearer.
Authorization code
Short-lived code from IdP redirect; exchanged for tokens at token endpoint.
Google returns a code to Emailzap backend; backend talks to Google. Mobile client never holds Google’s code — it receives Emailzap OTC instead.
OTC (one-time code)
App-specific short-lived code (not an OAuth standard name).
Emailzap deep-link emailzap://callback?code= → POST /mobile/auth/exchange/. Bridging Google OAuth finish → mobile bearer mint.
PKCE
Proof Key for Code Exchange — binds code redeem to the client that started auth (RFC 7636).
Mobile: binds OTC exchange (and related flows), not Google’s token endpoint directly from the app.
CSRF
Cross-Site Request Forgery — abuse of auto-sent cookies (OWASP).
Relevant for cookie session. Bearer header not auto-attached by browsers.
IdP / authorization server
Issues tokens / runs consent (here: Google).
Google for Gmail + sign-in. Apple for Sign in with Apple (mobile).
Resource server
API that accepts access tokens (RFC 6749).
Google APIs (Gmail) with Google tokens; Emailzap API with session cookie or Emailzap bearer.
Sources linked per row.
1 · Cookie
A cookie is a small name/value pair the user-agent (browser, or extension cookie API) stores after a Set-Cookie response and returns in the Cookie request header when URL/domain/path/SameSite rules match.
1. Server → 200 OK
Set-Cookie: sessionid=abc…; Path=/; Secure; HttpOnly; SameSite=None
2. Browser stores cookie for that domain/path.
3. Later request to same site →
Cookie: sessionid=abc…
4. Server reads Cookie header. If this is a Django session key,
it loads server-side session data for key abc…
Cookies are transport + storage owned by the user-agent — not “the session itself” and not the same as a Bearer header the app attaches.
MDN “session cookie” = no Max-Age/Expires (dies when browser session ends). Persistent cookie = has expiry. Separate idea from “session id cookie” used for server sessions.
Native mobile apps usually have no shared browser cookie jar for your API host → Bearer + SecureStore is the common substitute.
A cookie value can be anything (prefs, CSRF token, opaque id). Meaning is defined by the server that set it.
in Emailzap Chromex: sessionid (+ csrftoken, user_id, …). Mobile API auth: no cookie credential — see Storage tab.
2 · Session vs cookie
People say “session cookie” for two different things. Separate them:
Cookie (mechanism)
HTTP header protocol (Set-Cookie / Cookie)
Stored by browser / chrome.cookies
Auto-attached on matching requests
Can hold: prefs, CSRF secret, or a session key, or (rarely) whole signed session data
Session (application state)
Server’s per-visitor data bag (user id, flash messages, …)
Default Django: data in database table; cookie only holds random session_key
Cookie is the pointer; session is the payload on the server
Logout / flush invalidates server data; deleting cookie drops the client’s pointer
WRONG mental model: “the cookie IS the session”
RIGHT (Django default): cookie sessionid ──► django_session.session_data
Analogy: hotel key card (cookie) vs room contents (session data).
Steal the key → enter the room until lock changes (logout / expiry).
Question
Cookie
Session (Django DB)
Where does data live?
Client user-agent
Server (django_session)
What travels on the wire each request?
The cookie value (e.g. key)
Nothing of the payload — server loads by key
Who attaches it?
Browser / cookie API automatically
N/A — server lookup after cookie arrives
Can JS read it?
Only if not HttpOnly
No — never sent as payload to JS
CSRF risk?
Yes — auto-send enables CSRF
Indirect — session auth via cookie enables CSRF
Naming trap: MDN “session cookie” = ephemeral cookie lifetime. Django “session cookie” (sessionid) = cookie that identifies a server session, often with a long SESSION_COOKIE_AGE. Same English word, different layers.
in Emailzap Chromex = cookie pointer + Django session. Mobile = no Django session; “session” in mobile code means SecureStore-held Emailzap tokens.
3 · HttpOnly, Secure, SameSite
Cookie attributes on Set-Cookie control who can read the cookie and when the browser sends it.
HttpOnly — JavaScript cannot read the cookie via document.cookie. Still sent on HTTP requests initiated by JS (fetch / XHR). Mitigates XSS stealing session cookies — MDN.
Secure — cookie only sent over HTTPS (localhost exception). Resists passive network sniffing — MDN Set-Cookie.
SameSite — when cookie is attached on cross-site requests: Strict / Lax / None. Helps CSRF / third-party cookie control. SameSite=NonerequiresSecure — MDN SameSite.
HttpOnly blocks page JS. Browser extensions with the cookies permission (Chrome) can still read cookies via chrome.cookies — different privilege model than a webpage.
in Emailzap Prod/dev set session/CSRF cookies Secure=True, SameSite=None, domain .emailzap.co (see execfn/settings/prod.py / dev.py). Django’s default session cookie is HttpOnly unless overridden — Django docs recommend leaving SESSION_COOKIE_HTTPONLY True.
4 · Django sessions (database by default)
Django’s session framework stores arbitrary data server-side and puts only a session key in the cookie (unless you choose the signed-cookie backend). Quote from Django docs: cookies contain a session ID — not the data itself (unless using the cookie-based backend).
Browser cookie: sessionid = <random key>
Server table: django_session
session_key | session_data (encoded) | expire_date
On each request: SessionMiddleware reads cookie → loads row → request.session
login(request, user) attaches user id into that session data.
Default engine: database-backed sessions via model django.contrib.sessions.models.Session. Enable django.contrib.sessions + migrate → creates the table — Django 5.2 sessions.
Other engines (must set SESSION_ENGINE): cache, cached_db, file, signed_cookies. Signed-cookie backend stores data in the cookie (signed, not encrypted) — different threat model.
Logout:logout(request) flushes server session data. Stealing a live sessionid still works until expiry/flush — classic session hijacking.
Age:SESSION_COOKIE_AGE controls lifetime (Django default 2 weeks; projects may override).
in Emailzap No SESSION_ENGINE override found → default database sessions (django_session). SESSION_COOKIE_AGE = 400 days in base.py. Mobile OAuth branch does not call login() / set sessionid.
5 · CSRF (why cookies need extra protection)
If the browser auto-sends cookies, a malicious site can trigger a request to your API and the cookie rides along. CSRF defenses prove the request was intentionally initiated from your origin.
Django’s CsrfViewMiddleware checks a CSRF token (cookie + form/header) on unsafe methods — Django CSRF protection.
Bearer tokens in Authorization headers are not auto-attached by the browser to third-party pages, so classic cookie-CSRF does not apply the same way — still protect against XSS stealing the token.
SameSite cookies also reduce CSRF surface (MDN), but are not a full substitute for CSRF tokens in all browsers/flows.
in Emailzap Chromex: ExtensionCSRFMiddleware skips CSRF checks when Origin is an extension and sessionid is present. Mobile bearer: CSRF N/A for the Authorization header pattern.
6 · Bearer tokens
RFC 6750 defines using an access token as a Bearer credential. Client proves identity to your API by sending the token explicitly — the browser does not auto-attach it like a cookie.
Authorization: Bearer <access_token>
Client must:
1. Obtain token (login / refresh)
2. Store it securely (e.g. Keychain via SecureStore)
3. Attach header on every API request
4. Refresh or re-login when access expires / 401
“Bearer” = possession is authentication — treat like a password; HTTPS required in practice.
Unlike cookies: no automatic send on cross-site form posts → classic cookie-CSRF does not apply the same way (still guard XSS).
Often paired with a long-lived refresh token to mint (issue/create) new short-lived access tokens (RFC 6749 §1.5 says refresh tokens are used to obtain access tokens — “mint” is informal synonym). Rotation policy is implementation-defined. See Vocabulary → Mint.
Access token format is not fixed by Bearer RFC — can be opaque or JWT. Bearer only specifies how it is sent.
in Emailzap Mobile axios sets Bearer. Access = HS256 JWT (~1h). Refresh = opaque, non-rotating idle-slide (60d). Chromex does not use Bearer for Emailzap API — uses sessionid.
7 · Bearer vs OAuth — how Google OAuth fits
These layers get conflated. OAuth is a delegation protocol (get permission to call Google). Bearer is a credential transport (how you send some access token). An app can use OAuth without Bearer-to-its-own-API (chromex cookies), or Bearer without talking to Google on every request (mobile after login).
OAuth 2.0 (e.g. Google)
Bearer to Emailzap API
Django session cookie
Question it answers
“May this app call Gmail as the user?”
“Is this request from a logged-in Emailzap mobile user?”
“Is this request from a logged-in Emailzap chromex/web user?”
Who issues the credential?
Google (authorization server)
Emailzap backend
Emailzap Django session
What does it unlock?
Google APIs (Gmail scopes)
Emailzap REST/SSE as that user
Same Emailzap API via cookie
How sent?
Backend uses Google access token vs Google (server-side)
Authorization: Bearer
Cookie: sessionid=…
Stored where (typical)?
Server: encrypted on gmail_subscriptions
Client SecureStore + server hash of refresh
Client cookie jar + django_session
TWO DIFFERENT “ACCESS TOKENS” AFTER GOOGLE SIGN-IN
┌─────────────┐ OAuth code flow ┌──────────────┐
│ Mobile/Chromex│ ─────────────────────► │ Google IdP │
│ (browser UI) │ ◄── consent + code ─── │ │
└──────┬────────┘ └──────┬───────┘
│ opens Emailzap /google-auth/… │
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ Emailzap backend │
│ 1. Exchange Google code → Google access + refresh │
│ 2. Store Google tokens (Fernet) on gmail_subscriptions │
│ 3. Create/update User + MongoUser │
│ 4a. Chromex: login() → Set-Cookie sessionid │
│ 4b. Mobile: mint OTC → deep link → exchange → │
│ Emailzap Bearer JWT + Emailzap refresh │
└──────────────────────────────────────────────────────────┘
Later Gmail sync (backend/lambda) uses Google tokens.
Later inbox UI API calls use sessionid OR Emailzap Bearer — never Google’s token from the client.
OAuth does not replace app login by itself — RFC 6749 is about authorizing access to a resource server. Apps commonly also create a local account/session after OAuth succeeds.
Bearer is how mobile proves Emailzap login after that handoff. Chromex proves the same with a cookie instead.
Google’s Bearer token (to Google APIs) stays on the server. Mobile/chromex clients do not attach Google’s token to Emailzap API calls.
Apple Sign-In is a parallel IdP path for mobile identity; Gmail still needs Google OAuth for mailbox scopes when linking.
in Emailzap Shared /accounts/google-auth/*. Fork: chromex gets cookies; mobile gets OTC→bearer. Both paths write the same Gmail subscription tokens.
8 · OAuth 2.0 authorization code (sketch)
OAuth lets a user grant an app limited access without sharing their Google password with the app. Authorization Code flow: browser visits authorization server → user consents → app receives a short-lived code → app exchanges code (+ secrets/PKCE) for tokens.
App → Authorization endpoint (user login/consent at Google)
← redirect with ?code=…
App → Token endpoint (code + client auth and/or PKCE verifier)
← access_token (+ refresh_token) for Google APIs
Defined in RFC 6749. Public clients (mobile/SPA) cannot hold a confidential client secret safely — PKCE was designed for that class.
The Google access token (for Gmail API) is separate from Emailzap’s session / bearer credential that authenticates to Emailzap’s own API — see §7.
in Emailzap Both chromex and mobile open the same backend /accounts/google-auth/login/ which redirects to Google. Backend keeps Gmail tokens on MongoGmailSubscription. Mobile additionally mints OTC → Emailzap bearer pair.
9 · PKCE (Proof Key for Code Exchange)
RFC 7636: public OAuth clients are vulnerable to authorization code interception (another app on the device steals the redirect code). PKCE binds the token exchange to the client that started the flow.
1. Client creates random code_verifier (high entropy)
2. code_challenge = BASE64URL(SHA256(code_verifier)) # method S256
3. Send code_challenge with authorization request
4. Later, token/exchange request must include code_verifier
5. Server recomputes challenge; mismatch → reject
Pronounced “pixy” (RFC abstract).
S256 is the recommended challenge method; plain exists but is weaker.
Verifier must stay secret until exchange — typically memory only, never logged.
in Emailzap Mobile generates PKCE before Google/Apple flows; challenge stored with OTC; /exchange/ requires verifier. Defends emailzap://callback?code= custom-scheme interception.
10 · JWT (JSON Web Token)
RFC 7519: compact, URL-safe representation of claims between two parties. Typically three Base64url parts: header.payload.signature (JWS).
Claims are JSON fields (e.g. exp, iat, custom ids). Anyone who has the token can read unsigned/signed payloads unless encrypted (JWE).
Signature (e.g. HS256 = HMAC-SHA256 with a shared secret) proves integrity + authenticity of claims — not confidentiality.
in Emailzap Mobile access token = HS256 JWT, purpose bearer_access, TTL 1h, secret MOBILE_JWT_SECRET. Refresh token is not a JWT — opaque random; only SHA-256 stored in Mongo.
11 · expo-secure-store
Expo’s official API for encrypting and storing key–value pairs on device — intended for secrets, not bulk app state.
iOS: Keychain Services as kSecClassGenericPassword. Data may persist across uninstall/reinstall with same bundle ID (Keychain behavior; Expo says do not rely on this as a guarantee) — Expo SecureStore.
Android: values in SharedPreferences, encrypted with Android Keystore. Cleared on uninstall.
Not a substitute for server authority — Expo: do not rely on it as sole SoT for irreplaceable critical data.
Large values can be rejected by the platform (historical ~2048 byte iOS limit noted in Expo docs).
Contrast: AsyncStorage / MMKV are general local storage — not hardware-backed secret stores. Expo and mobile security guidance: keep credentials out of them.
in Emailzapsession-store.ts uses SecureStore for access/refresh/user/accounts with env-namespaced keys. PKCE verifier stays in memory. MMKV used for query cache — cleared on logout, not for tokens.
12 · chrome.cookies (extension privilege)
Chrome extensions with the "cookies" permission can read/write/remove cookies for allowed URLs — including HttpOnly cookies that page JS cannot see — via chrome.cookies.get/set/remove.
Built-ins: Session, Basic, Token; custom / third-party JWT common
What “session authentication” means in Django
Session auth = “this request is tied to a logged-in User via Django’s session framework,” not “we store passwords in cookies.”
1. User authenticates once (password form, OAuth callback calling login(), …)
2. django.contrib.auth.login(request, user)
→ writes user id into request.session (server-side)
3. SessionMiddleware ensures session key cookie (sessionid) is set/sent
4. Later request: Cookie: sessionid=…
→ SessionMiddleware loads session
→ AuthenticationMiddleware sets request.user from session
5. DRF SessionAuthentication: same session → request.user for API views
Logout: logout(request) flushes session data; delete cookie drops client pointer.
Django overview: auth system handles accounts + cookie-based user sessions — Django auth.
DRF: SessionAuthentication “uses Django's default session backend… appropriate for AJAX clients that are running in the same session context as your website” — DRF SessionAuthentication.
Unsafe methods (POST/PUT/PATCH/DELETE) need a valid CSRF token when using SessionAuthentication (DRF + Django CSRF docs).
Bare DRF SessionAuthentication often returns 403 when unauthenticated (no authenticate_header). Projects may subclass to return 401 (Emailzap does).
Common mechanisms — web vs mobile vs both
Mechanism
Typical client
How it works
Security concerns
Pros
Cons
Recommended when
Session auth Django + DRF SessionAuthentication
Web / browser extension
Server session row; client holds sessionid cookie; browser auto-sends; CSRF on mutating requests.
CSRF; session fixation/hijack if cookie stolen; XSS if cookie not HttpOnly; long-lived cookies widen window.
Native browser jar; logout can flush server session; fits same-site web apps; no custom header plumbing.
Awkward for pure native mobile (no cookie jar); CSRF complexity; multi-device revocation is session-row oriented.
First-party web UI / SPA same-site as API; browser extensions that can use cookies. DRF: AJAX in same session context.
DRF TokenAuthentication Authorization: Token …
Mobile / desktop (DRF says so)
Opaque token in DB (authtoken); client sends header each request.
Token theft = full access until rotated; default scheme is simple (often one token/user, weak expiry story) — DRF points to Knox for tighter design; HTTPS required.
Simple; no CSRF; works without browser cookies; easy to test with curl.
Built-in token model is minimal (expiry/rotation/multi-device often DIY or Knox); long-lived static tokens risky.
Simple client-server APIs; native clients when you accept DRF’s simple token model or adopt Knox/similar. Not used in Emailzap (no authtoken found).
Session: HttpOnly + Secure + sensible SameSite; CSRF tokens; short enough SESSION_COOKIE_AGE for risk appetite; regenerate session on login (Django login flow); HTTPS.
DRF Token: HTTPS; treat token like password; plan rotation/compromise; consider Knox if you need expiry/multi-token — per DRF docs.
JWT Bearer: strong secret/keys; short access TTL; validate exp/alg/purpose; soft-delete/revocation gates; store refresh in secure storage; never put secrets in JWT payload expecting privacy (signed ≠ encrypted).
Basic: HTTPS only; no persistent password storage on client — DRF.
OAuth: PKCE for public clients; minimal scopes; protect refresh tokens at rest (Fernet etc.).
in Emailzap
Default DRF = SessionAuthenticationWith401.
Mobile = custom Bearer JWT + refresh (not DRF TokenAuthentication).
Many product views: session + bearer additive.
Google/Apple OAuth = how users enter; not the per-request Emailzap API credential.
14 · Theory → Emailzap quick map
Concept
Chromex / web
Mobile
Credential transport
Cookie sessionid
Authorization: Bearer
Server store
django_session row
JWT verify + mobile_refresh_tokens hash
Client store
Browser cookie jar
expo-secure-store
CSRF
Middleware (+ extension bypass)
N/A for bearer header
Google OAuth
Same backend endpoints
Same + mobile=true + PKCE/OTC
Mailbox Google tokens
gmail_subscriptions (Fernet) — separate from either session credential
Open other tabs for wiring evidence. Theory tab stays vocabulary + official refs.
1 · Google sign-in (mobile) — wired
Same Google OAuth client/scopes as chromex. Branch: ?mobile=true → OTC deep-link, nosessionid.
Mobile client
↓
↓
↓
Shared OAuth (accounts)
↓
externalGoogle consent screensame OAuth client + scopes as chromex
↓
↓
Token mint
↓
↓
2 · Apple Sign-In (mobile iOS) — wired
Side-door into a Google-anchored family. Unlinked Apple → in-memory apple_link_token → AI consent / Connect Gmail (not SecureStore).
Design intent from 05-mobile-auth-design.md §1–2, verified against live code where noted.
Mobile
Credential: Authorization: Bearer <jwt>
Storage: expo-secure-store (env-namespaced keys)
OAuth UI: WebBrowser.openAuthSessionAsync
CSRF: N/A (bearer)
Apple Sign-In: yes (iOS)
Header: X-Emailzap-Client: mobile
Refresh: client interceptor → non-rotating
Chromex
Credential: Django sessionid cookie
Storage: browser cookie jar + chrome.storage metadata
OAuth UI: chrome.identity.launchWebAuthFlow
CSRF: bypassed via ExtensionCSRFMiddleware when session present
Apple Sign-In: no
Query/identity: client=chromex / installation id
Refresh: Django session age (400 days)
Shared (unchanged by mobile auth)
User / MongoUser / MongoGmailSubscription / SecondaryUserThrough — family model identical.
Account filtering = client profile_ids query param (pill bar), not a server “active account” switch.
Google consent screen + scopes identical when using shared /accounts/google-auth/*.
Mobile does not use cookies for API auth. Chromex does. Backend still sets cookies for chromex/web OAuth success.
Mobile SecureStore keys
Kind
Key pattern
Contents
access
mobile_access_token_{env}
HS256 JWT (1h)
refresh
mobile_refresh_token_{env}
Opaque random (server stores SHA-256 only)
user
mobile_user_{env}
Cached identity JSON (Option C cold start)
accounts
mobile_accounts_{env}
Cached account family JSON
Env = local | dev | production. Cross-env cleanup on first read (Keychain bleed defense for shared bundle IDs).
What is NOT used for mobile credentials
AsyncStorage — explicitly forbidden for PKCE/tokens in comments.
MMKV — query cache / drafts / filters; wiped on logout; not tokens.
Cookie jar / CookieManager — no API-auth cookie path found on mobile.
Backend cookies (chromex / web path)
Cookie
Set when
Cleared by LogoutAPIView
Notes
sessionid
Django login() on OAuth callback (non-mobile)
yes
Primary auth credential for chromex
csrftoken
Django CSRF
yes
Bypassed for extension Origin + session
user_id
set_cookies after OAuth
yes
httponly=False in set_cookies
profile
profile switch paths
yes
—
token
never set
yes (delete only)
Likely legacy
Auth source of truth for mobile tokens / OTC / Apple = Mongo. Postgres tables with the same names are dual-write projections (not used to authenticate requests).
Mongo collections (auth-relevant)
Collection
Model
Represents
Used by
Postgres tables (auth-relevant)
Table
Model
Represents
Role in auth
Mobile auth routes · /api/v1/mobile/
Method · path
Auth
Role
Shared Google OAuth · /api/v1/accounts/google-auth/
Early-return when ?mobile=true or state.metadata.mobile. Chromex continues without those flags.
Endpoint
Mobile branch
Chromex branch
GET …/login/
_mobile_login → Google + PKCE in state
Standard OAuth start
GET …/callback/
_mobile_callback → OTC deep-link (no session)
login() + set_cookies
GET …/google-add/
_mobile_google_add via add_account_token
Session-authed add secondary
GET …/add-account-callback/
_mobile_add_account_callback → OTC
Cookie session path
Wired vs dead across surfaces. Click a row for evidence.
Surface
Path
Status
Things intentionally not claimed as fact. Resolve with product/backend owners if needed.
Chromex server logout: teardown clears cookies client-side; no POST /user/me/logout/ caller found in chromex. Django django_session row may linger until SESSION_COOKIE_AGE (400d) unless another surface calls LogoutAPIView. Cookie absence still blocks the client.
Refresh rotation docs: design §D-E + some memory docs stale vs non-rotating token_service.
revoke_family on logout: typed on mobile client; LogoutRequestSerializer only accepts refresh_token. Family revoke happens on account deletion cascade, not logout.
Bearer coverage completeness: many mailbot/app views use additive [Session, MobileBearer]; whether every endpoint mobile needs has bearer was not exhaustively audited in this pass.
ChromeExtensionInstallation / MobileLeadSubmission Mongo collection names: no explicit Meta.db_table seen — verify in Atlas if needed.
Lambda: no mobile-user auth path. Pub/Sub verification only.
Ask me if…
You want a deep dive on secondary add / reconnect-only JWT flows (EMA-841).
You need live Atlas collection names / indexes confirmed against prod.
You want an exhaustive list of every DRF view missing MobileBearerAuthentication.