Overview
Implement OAuth 2.0 with OpenID Connect across a Next.js web backend and iOS Swift mobile app. The system supports dual authentication: session cookies for web and Bearer tokens for mobile, with PKCE for secure native app auth.
Architecture Summary
| Platform | Flow | Client Type | Auth Method |
|---|---|---|---|
| Web Admin | Authorization Code | Confidential | Client Secret |
| iOS App | Authorization Code + PKCE | Public | No Secret Required |
Both paths converge at the Next.js API layer, which accepts either Bearer tokens or session cookies via a unified getSession() helper.
Read references/architecture.md for the full flow diagram and endpoint reference.
Workflow: Set Up Backend Authentication (Next.js)
Step 1: Install Dependencies
npm install next-auth@5.0.0-beta.25
Step 2: Configure Environment
Create .env in the backend directory:
AUTH_SECRET=<openssl rand -base64 32>
AUTH_ISSUER=https://{auth-domain}
AUTH_CLIENT_ID=client_xxx
AUTH_CLIENT_SECRET=secret_xxx
Step 3: Create Auth Configuration
Create auth.ts with the OIDC provider, JWT callback (storing tokens + handling refresh), and session callback (exposing tokens).
Read references/backend-auth.md for complete Auth.js setup including the refresh token function.
Step 4: Add Bearer Token Support
Create lib/auth-helper.ts with:
- •
getBearerToken(request)- Extract token from Authorization header - •
verifyBearerToken(token)- Verify against OAuth provider's userinfo endpoint - •
getSession(request?)- Unified session resolver: tries Bearer token first, falls back to session cookie
Read references/backend-auth.md for the full implementation.
Step 5: Set Up Route Protection
Create middleware (proxy.ts) that:
- •Allows public paths (
/login,/api/auth,/api/v1, etc.) - •Redirects unauthenticated requests to
/login - •Lets
/api/v1routes handle their own auth viagetSession()
Step 6: Use in API Routes
All /api/v1 routes follow this pattern:
export async function GET(request: NextRequest) {
const session = await getSession(request);
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// Use session.user.id for ownership queries
}
Workflow: Set Up iOS Authentication (Swift)
Step 1: Configure xcconfig Files
Set up build-time configuration:
- •
Debug.xcconfig- Points to localhost, uses dev client ID - •
Release.xcconfig- Points to production, uses prod client ID - •
Secrets.xcconfig(git-ignored) - Contains actual client IDs
Read references/ios-auth.md for xcconfig setup and Info.plist integration.
Step 2: Create AppConfiguration
Singleton that reads OAuth settings from Bundle.main.infoDictionary at runtime (populated from xcconfig via Info.plist variable substitution).
Step 3: Implement OAuthManager
Core authentication class using ASWebAuthenticationSession with PKCE:
- •Generate PKCE code verifier (32 random bytes, base64url-encoded)
- •Compute code challenge (SHA256 of verifier, base64url-encoded)
- •Launch browser with authorization URL including challenge
- •Receive callback with authorization code
- •Exchange code for tokens (sending code_verifier, not client_secret)
Read references/ios-auth.md for the complete OAuthManager and PKCE implementation.
Step 4: Implement Token Storage
Use Keychain (Security framework) with kSecAttrAccessibleAfterFirstUnlock for:
- •Access token, refresh token, expiration timestamp
- •Actor-based (
TokenStorage) for thread safety - •10-minute pre-expiry buffer for proactive refresh
Step 5: Add Authentication Middleware
Create AuthenticationMiddleware (conforming to ClientMiddleware from OpenAPIRuntime):
- •Inject Bearer token into every request
- •Catch 401 responses, attempt token refresh, retry once
- •Post
.authSessionExpirednotification if refresh fails
Step 6: Wire Up SwiftUI
Switch on authManager.authState (.unknown / .authenticated / .unauthenticated) to show the appropriate view. Listen for .authSessionExpired to trigger sign-out.
Workflow: Set Up E2E Testing
Step 1: Enable Mock Mode
Set IS_E2E=true environment variable. This causes getSession() to return a mock session without OAuth.
Step 2: Configure Multi-User Testing
Use custom headers to simulate different users:
- •
X-Test-User-Id- Simulate specific user ID - •
X-Test-User-Email- Simulate specific email
Step 3: Use In-Memory Database
When IS_E2E=true, switch to in-memory SQLite for test isolation.
Read references/e2e-testing.md for Playwright config, test examples, iOS UI test setup, and permission testing patterns.
Security Checklist
- •Use PKCE (S256) for all mobile/native clients
- •Never embed client secrets in mobile app binaries
- •Store tokens in Keychain (iOS) or HttpOnly cookies (web)
- •Proactively refresh tokens before expiration
- •Clear all tokens on sign-out
- •Request
offline_accessscope for refresh tokens
Troubleshooting
Read references/troubleshooting.md for common issues: invalid client errors, 401 failures, token refresh problems, xcconfig setup, and debug logging.