feat(api): OAuth callback → bearer token handoff for native clients #215

Closed
opened 2026-06-21 16:37:49 +00:00 by james · 0 comments
Owner

Context

PR #194 added /api/auth/token for native clients using local-credential password grants but explicitly deferred the OAuth completion → bearer token bridge. Today the OAuth callback issues a session cookie and redirects through the web flow. A native (Android, Flatpak) client cannot complete an OAuth2/OIDC login because it has no cookie store and cannot ride the cookie-redirect handoff.

The discriminated-union grantType shape on /api/auth/token was deliberately left open so this can land without a breaking change.

Source

PR #194 body ("Out of scope"):

OAuth-completion → token handoff. The existing OAuth callback issues a session cookie; the callback-to-token bridge for native is a follow-up. Today grantType: "password" is the only supported grant; the discriminated-union request shape leaves room without a breaking change.

Scope

  • Add a flow where a native client initiates OAuth via a deep-link redirect URI (Android: app intent filter; Flatpak: custom protocol handler) and the callback hands back a one-time code that the native client exchanges at /api/auth/token with grantType: "authorization_code" (Carol-issued code, not the IdP code).
  • Or: extend the callback to detect a native-origin request and respond with a token pair directly (no cookie).
  • Either path must preserve PKCE / nonce / state validation that already exists in apps/api/app/api/auth/oauth/callback/[provider]/route.ts.
  • Wire the Android client (apps/client/) to drive the new flow via expo-auth-session or equivalent.

Acceptance criteria

  • Native client can sign in via GitHub OAuth and end up with a bearer access + refresh token pair stored in SecureStore.
  • The PKCE / nonce / state path stays the load-bearing check; no oracle is introduced on grantType: "authorization_code".
  • OIDC provider instances (per ADR-0017) work via the same flow — not GitHub-only.
  • Tests cover happy path, replayed code, expired code, and mismatched state.
  • docs/api-conventions.md or a sibling auth doc describes the native OAuth flow.

Out of scope

  • iOS support (out-of-scope per idea.md).
  • Auto-linking on OAuth signup — preserves ADR-0015 "do not auto-merge by email" rule.

Composes with

  • ADR-0015 (OAuth flow), ADR-0017 (OIDC), ADR-0027 (universal client), PR #194 (bearer leg).
## Context PR #194 added `/api/auth/token` for native clients using local-credential `password` grants but explicitly deferred the OAuth completion → bearer token bridge. Today the OAuth callback issues a session cookie and redirects through the web flow. A native (Android, Flatpak) client cannot complete an OAuth2/OIDC login because it has no cookie store and cannot ride the cookie-redirect handoff. The discriminated-union `grantType` shape on `/api/auth/token` was deliberately left open so this can land without a breaking change. ## Source PR #194 body ("Out of scope"): > OAuth-completion → token handoff. The existing OAuth callback issues a session cookie; the callback-to-token bridge for native is a follow-up. Today `grantType: "password"` is the only supported grant; the discriminated-union request shape leaves room without a breaking change. ## Scope - Add a flow where a native client initiates OAuth via a deep-link redirect URI (Android: app intent filter; Flatpak: custom protocol handler) and the callback hands back a one-time code that the native client exchanges at `/api/auth/token` with `grantType: "authorization_code"` (Carol-issued code, not the IdP code). - Or: extend the callback to detect a native-origin request and respond with a token pair directly (no cookie). - Either path must preserve PKCE / nonce / state validation that already exists in `apps/api/app/api/auth/oauth/callback/[provider]/route.ts`. - Wire the Android client (`apps/client/`) to drive the new flow via `expo-auth-session` or equivalent. ## Acceptance criteria - [ ] Native client can sign in via GitHub OAuth and end up with a bearer access + refresh token pair stored in SecureStore. - [ ] The PKCE / nonce / state path stays the load-bearing check; no oracle is introduced on `grantType: "authorization_code"`. - [ ] OIDC provider instances (per ADR-0017) work via the same flow — not GitHub-only. - [ ] Tests cover happy path, replayed code, expired code, and mismatched state. - [ ] `docs/api-conventions.md` or a sibling auth doc describes the native OAuth flow. ## Out of scope - iOS support (out-of-scope per idea.md). - Auto-linking on OAuth signup — preserves ADR-0015 "do not auto-merge by email" rule. ## Composes with - ADR-0015 (OAuth flow), ADR-0017 (OIDC), ADR-0027 (universal client), PR #194 (bearer leg).
james closed this issue 2026-06-23 14:04:42 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
james/carol#215
No description provided.