Mobile Auth Gating
Plan Metadata
- Plan type:
plan - Parent plan:
N/A - Depends on:
auth-system.md - Status:
documentation
System Intent
- What is being built: Contract for mobile startup auth-state bootstrap and route-gating behavior.
- Primary consumer(s): Expo root layout, auth provider, auth gate, and auth route group screens.
- Boundary (black-box scope only): Auth provider bootstrap state, auth gate redirect decisions, and protected-vs-auth route access behavior. Sign-in integration details are split into
mobile-signin-flow.md.
Stage Gate Tracker
- [x] Stage 1 Mermaid approved
- [x] Stage 2 I/O contracts approved
- [x] Stage 3 pseudocode/technical details approved or skipped
1. Mermaid Diagram
Reference: .agent/skills/create-mermaid-diagram/SKILL.md
flowchart TD
A[Root Layout - main/app/app/_layout.tsx] -->|mounts auth context provider| B[Auth Provider - main/app/lib/api/auth/auth-provider.tsx]
A -->|mounts route guard wrapper| C[Auth Gate - main/app/components/auth-gate.tsx]
B -->|session and loading state| C
C -->|if loading=true then render null and defer navigation| G[Loading Hold - main/app/components/auth-gate.tsx]
C -->|if session=null then redirect or stay in auth group| D[Auth Route Group - main/app/app]
C -->|if session!=null then redirect or stay in protected surface| P[Protected Surface - main/app/app]
B -->|sign-in and handoff methods rely on backend auth contract| F[Auth System Contract Boundary - docs/plans/auth-system.md]
classDef unchanged fill:#d3d3d3,stroke:#666,stroke-width:1px;
classDef updated fill:#ffe58a,stroke:#666,stroke-width:1px;
classDef deleted fill:#f4a6a6,stroke:#666,stroke-width:1px;
classDef created fill:#a8e6a3,stroke:#666,stroke-width:1px;
class A,B,C,D,F,G,P unchanged; 2. Black-Box Inputs and Outputs
Keep this short. Define types in JSON-style blocks and capture each flow with path-level rows. Section intent: when a user reaches main/app/app/_layout.tsx, what should display for each auth/loading/route state.
Global Types
Define shared types used across multiple flows.
AuthSession {
accessToken: string
idToken: string
refreshToken: string
expiresAt: number
userId: string
username: string
}
RouteGroup {
value: "auth" | "protected"
}
DisplaySurface {
value: "none" | "auth-group" | "protected-surface"
}
LayoutDisplayState {
rendered_surface: DisplaySurface
redirect_to?: "/(auth)/login" | "/"
rendered_route_path?: string
}
Flow: main/app/app/_layout.tsx.RootLayout, main/app/__tests__/auth-gate.test.tsx
Type Definitions
RootLayoutDisplayInput {
auth_loading: boolean
session: AuthSession | null
requested_route_group: RouteGroup
requested_route_path: string
}
RootLayoutDisplayOutput = LayoutDisplayState
Paths
| path-name | input | output/expected state change | path-type | notes | updated |
|---|---|---|---|---|---|
root-layout-display.loading-hold | RootLayoutDisplayInput auth_loading=true | RootLayoutDisplayOutput rendered_surface=none | subpath | gate renders nothing while auth bootstrap is unresolved | |
root-layout-display.unauthenticated-redirect-to-login | RootLayoutDisplayInput auth_loading=false session=null requested_route_group=protected | RootLayoutDisplayOutput rendered_surface=auth-group redirect_to=/(auth)/login rendered_route_path=/(auth)/login | happy path | unauthenticated users entering protected routes are redirected to login | |
root-layout-display.unauthenticated-auth-group-allowed | RootLayoutDisplayInput auth_loading=false session=null requested_route_group=auth | RootLayoutDisplayOutput rendered_surface=auth-group rendered_route_path=requested_route_path | happy path | unauthenticated users can continue in auth routes (login/verify) | |
root-layout-display.authenticated-auth-group-redirect-home | RootLayoutDisplayInput auth_loading=false session!=null requested_route_group=auth | RootLayoutDisplayOutput rendered_surface=protected-surface redirect_to=/ rendered_route_path=/ | happy path | authenticated users are redirected out of auth routes to home | |
root-layout-display.authenticated-protected-allowed | RootLayoutDisplayInput auth_loading=false session!=null requested_route_group=protected | RootLayoutDisplayOutput rendered_surface=protected-surface rendered_route_path=requested_route_path | happy path | authenticated users remain on protected routes | |
root-layout-display.invalid-route-group | RootLayoutDisplayInput requested_route_group not in {auth, protected} | RootLayoutDisplayOutput rendered_surface=none | error | undefined route groups are outside this contract and should not render a documented surface |
Test coverage note: dedicated auth-gating entry tests are currently not present under main/app/__tests__.
3. Pseudocode / Technical Details for Critical Flows (Optional)
-
Flow name::
root-layout-display-decisionuser enters app route through _layout mount AuthProvider and AuthGate read {loading, session} and requested route group if loading then render none else if no session and requested group is protected then redirect to /(auth)/login and show auth group else if no session and requested group is auth then show requested auth route else if session exists and requested group is auth then redirect to / else show requested protected route -
Implementation notes:
- This plan is intentionally limited to display/routing outcomes triggered from
_layoutentry. - Backend auth endpoint semantics remain in
auth-system.md. - Sign-in integration behavior remains in
mobile-signin-flow.md.
4. Handoff to Related Plan Reconciliation
After all stages are approved, apply .agent/skills/reconcile-plans/SKILL.md to propagate contract updates across linked plans.