Skip to content

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-decision

    user 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 _layout entry.
  • Backend auth endpoint semantics remain in auth-system.md.
  • Sign-in integration behavior remains in mobile-signin-flow.md.

After all stages are approved, apply .agent/skills/reconcile-plans/SKILL.md to propagate contract updates across linked plans.