> ## Documentation Index
> Fetch the complete documentation index at: https://docs.swiftaiboilerplate.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Auth Module

> Production **unified authentication** with Apple, Google, and Email via Supabase. Includes **reliable session persistence** and **automatic token refresh**.

<Info>
  **Full technical details** in your project at `/docs/modules/Auth.md`
</Info>

## What You Get

* ✅ **3 auth providers** - Apple, Google, Email (all via Supabase)
* ✅ **Reliable session persistence** - Users stay logged in for days, not hours
* ✅ **Automatic token refresh** - Silent background refresh keeps sessions alive
* ✅ **Expired session recovery** - Refresh tokens restore sessions even after access token expires
* ✅ **Keychain storage** - Secure, encrypted token persistence
* ✅ **AsyncStream state** - Observable authentication state (including `.refreshing`)
* ✅ **MockAuthClient** - DEBUG mode works without backend

**Time saved: 20-32 hours** of auth flows, token management, session handling, and comprehensive testing.

## Module structure (since v2.0)

* **`SessionManager` is split across four files**: `SessionManager.swift`, `SessionManager+SignIn.swift`, `SessionManager+Refresh.swift`, and `SessionManager+Persistence.swift`. The public actor API is **unchanged**. This is the ≤ 400-line rule in action.
* **`SupabaseAuthAPI` helpers moved** to `SupabaseAuthAPI+Mapping.swift`. Private helpers only; no API change.
* All protocol-typed properties now use the explicit `any` keyword required by Swift 6.

## Session Behavior

| Scenario                  | Behavior                                       |
| ------------------------- | ---------------------------------------------- |
| App opened after 1 hour   | ✅ Silent refresh, user stays logged in         |
| App opened after 3 days   | ✅ Silent refresh, user stays logged in         |
| App opened after 14+ days | Refresh token expired, user must sign in again |

<Note>
  Users only need to re-authenticate when the refresh token expires (7+ days by default, configurable in Supabase).
</Note>

## Key Components

### AuthClient Protocol

```swift theme={null}
@available(iOS 17.0, *)
public protocol AuthClient: Sendable {
    func signInWithApple() async throws -> AuthUser
    func signInWithGoogle() async throws -> AuthUser
    func signUpWithEmail(email: String, password: String) async throws -> AuthUser
    func signInWithEmail(email: String, password: String) async throws -> AuthUser
    func resetPassword(email: String) async throws
    func signOut() async throws
    func currentUser() async -> AuthUser?
    func authStates() -> AsyncStream<AuthState>
    func refreshIfNeeded() async throws
}
```

## Production Setup (From Real Code)

How authentication is set up in `CompositionRoot.swift`:

```swift theme={null}
// 1. Configure Supabase (values come from the generated AppConfiguration,
//    built at compile time from Config/Secrets.xcconfig)
let authConfig = AuthConfig(
    supabaseURL: url,                              // AppConfiguration.SUPABASE_URL
    supabaseAnonKey: AppConfiguration.SUPABASE_ANON_KEY
)

// 2. Create HTTP client for Supabase API
let supabaseHTTPClient = Auth.SupabaseHTTPClient(
    baseURL: authConfig.supabaseURL,
    session: .shared
)

// 3. Create Apple Sign In coordinator
let appleSignInCoordinator = Auth.AppleSignInCoordinator()

// 4. Create SessionManager (production auth client)
let sessionManager = Auth.SessionManager(
    httpClient: supabaseHTTPClient,
    keychain: keychainStore,
    apple: appleSignInCoordinator,
    config: authConfig
)

// SessionManager automatically:
// - Loads saved session from Keychain on init
// - Schedules proactive token refresh (60s before expiry)
// - Emits auth state changes via AsyncStream
// - Handles Apple + Email out of the box (Google once a GoogleSignInCoordinator is passed)
```

### What SessionManager Does

**On App Launch:**

1. Loads session from Keychain
2. Validates token expiry
3. Auto-refreshes if expiring soon
4. Emits `.authenticated` or `.unauthenticated` state

**On Sign In:**

1. Exchanges provider token with Supabase
2. Saves access + refresh tokens to Keychain
3. Schedules proactive refresh
4. Emits `.authenticated(user)` state

**Token Refresh:**

* Scheduled 60s before token expiry
* Recovers expired sessions using refresh token
* Retries up to 3 times with exponential backoff
* Handles Supabase token rotation
* Users stay logged in for days, not hours

**Production Quality:**

* ✅ Race-safe refresh mutex
* ✅ Cancellation-aware
* ✅ Comprehensive error handling
* ✅ Fully tested (85%+ coverage)

## Token Management

### Automatic Refresh

```swift theme={null}
// SessionManager handles:
// - Token refresh before expiry
// - Retry on refresh failure
// - Keychain persistence
// - Logout on refresh failure
```

### Secure Storage

All tokens stored in Keychain:

* ✅ Access token
* ✅ Refresh token
* ✅ Never in UserDefaults
* ✅ OS-level encryption

## Auth State Observation

```swift theme={null}
// Observe auth state changes
for await state in authClient.authStates() {
    switch state {
    case .authenticated(let user):
        // Navigate to authenticated content
    case .unauthenticated:
        // Show sign-in screen
    case .refreshing:
        // Keep the current UI while restoring the session
    }
}
```

## Customization Examples

### Add a Social Provider

Apple and Email ship wired out of the box. Google is implemented in the package
(`GoogleSignInCoordinator`) but is **not** wired in `CompositionRoot` by default —
pass a Google provider to `SessionManager(google:)` to enable it. To add another
provider, declare it on `AuthClient` and implement it on the production client
(`SessionManager`, in `SessionManager+SignIn.swift`):

```swift theme={null}
// 1. Add to the AuthClient protocol (Packages/Auth/.../Protocols/AuthClient.swift)
func signInWithGitHub() async throws -> AuthUser

// 2. Implement in SessionManager (Packages/Auth/.../Session/SessionManager+SignIn.swift)
public func signInWithGitHub() async throws -> AuthUser {
    // Exchange the provider token with Supabase, then persist the session
}
```

### Add Custom Fields

```swift theme={null}
// Extend AuthUser (real fields: id, email, name, avatarURL)
struct AuthUser {
    let id: String
    let email: String?
    let name: String?
    let avatarURL: URL?
    var customField: String?  // Add custom fields
}
```

## Mock Auth in DEBUG

**Enabled by default!** No setup needed.

`CompositionRoot` selects the mock at startup. In DEBUG it uses `MockAuthClient`
unless `AUTH_BYPASS=0` is set; in RELEASE the mock is compiled out entirely.

```swift theme={null}
#if DEBUG
let authBypassValue = ProcessInfo.processInfo.environment["AUTH_BYPASS"]
let shouldUseMock = authBypassValue != "0"  // mock unless explicitly disabled

if shouldUseMock {
    self.sessionManager = Auth.MockAuthClient()
} else {
    // real Supabase + Apple + Email auth …
}
#else
// RELEASE always uses real authentication
#endif
```

**To use real auth in DEBUG:**

1. Edit Scheme → Run → Environment Variables
2. Add `AUTH_BYPASS` = `0`
3. Configure Supabase in `Config/Secrets.xcconfig`

## Key Files

| Component     | Location                                                |
| ------------- | ------------------------------------------------------- |
| Protocol      | `Packages/Auth/Sources/Auth/Protocols/AuthClient.swift` |
| Supabase      | `Packages/Auth/Sources/Auth/Supabase/`                  |
| Apple Sign In | `Packages/Auth/Sources/Auth/Apple/`                     |
| Session       | `Packages/Auth/Sources/Auth/Session/`                   |
| Mock          | `Packages/Auth/Sources/Auth/Mock/`                      |

## Dependencies

* **Core** - Error handling, logging
* **Networking** - HTTP client (for Supabase)

## Used By

* **All features** - Protected by authentication
* **CompositionRoot** - Observes auth state
* **LaunchRouter** - Auth-gated navigation

## Best Practices

<AccordionGroup>
  <Accordion title="Token Security">
    * Always store in Keychain
    * Never log tokens
    * Refresh before expiry
    * Clear on sign out
  </Accordion>

  <Accordion title="Error Handling">
    * Map to AppError
    * User-friendly messages
    * Retry transient failures
    * Log technical details
  </Accordion>

  <Accordion title="Testing">
    * Use MockAuthClient
    * Test token refresh
    * Test error scenarios
    * Test state transitions
  </Accordion>
</AccordionGroup>

## Learn More

<CardGroup cols={2}>
  <Card title="Full Documentation" icon="book" href="https://github.com/SwiftAIBoilerplatePro/SwiftAIBoilerplatePro-Distribution/blob/main/docs/modules/Auth.md">
    Complete Auth guide
  </Card>

  <Card title="Supabase Setup" icon="server" href="/pages/guides/supabase-setup">
    Configure backend
  </Card>

  <Card title="Building Guide" icon="hammer" href="/pages/guides/building-your-app">
    Customize auth flow
  </Card>

  <Card title="Architecture" icon="diagram-project" href="/pages/architecture">
    See auth in system
  </Card>
</CardGroup>

## Test Coverage

**85%+** - Comprehensive auth testing (run via the `AuthTests` target in the single `Boilerplate.xctestplan` pass)

Tests include:

* Sign in/up flows
* Token refresh
* Session management
* Apple Sign In coordination
* Error scenarios
* State transitions
