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

# Authenticate Your App's Users with ChatGPT Sign-In

> Learn how Chappie's device-code flow lets users sign in with ChatGPT while the SDK stores credentials in Keychain and refreshes them automatically.

Chappie handles authentication by guiding users through a device-code sign-in flow tied to their existing ChatGPT account. Once sign-in completes, the SDK stores credentials securely in the iOS or macOS Keychain — your app never receives raw tokens. Instead, you observe a published `ChappieAuthState` value and an optional `ChappieAccountInfo` struct that contains the user's email, plan, and account identifiers. Chappie also refreshes expired tokens automatically before every request, so you don't need to manage token lifetimes yourself.

## How authentication works

<Steps>
  <Step title="User taps sign in">
    Your app calls `startDeviceCodeSignIn()` — or uses the `ChappieSignIn` button that calls it for you. The SDK requests a short-lived device code from OpenAI.
  </Step>

  <Step title="User approves in a browser">
    The user opens the verification URL (automatically or manually), signs in with their ChatGPT account, and pastes the device code. Chappie polls in the background until the code is approved.
  </Step>

  <Step title="Credentials stored in Keychain">
    When polling succeeds, the SDK writes the access and refresh tokens to Keychain using the accessibility and account settings you configured. Your app never sees the raw tokens.
  </Step>

  <Step title="Auth state published to your UI">
    `ChappieAuthSession.state` transitions to `.signedIn` and `accountInfo` is populated. Subsequent app launches restore the session from Keychain automatically.
  </Step>

  <Step title="Automatic token refresh">
    Before every request, Chappie checks whether the stored access token is expired. If it is, the SDK silently refreshes it using the stored refresh token and writes the new credential back to Keychain.
  </Step>
</Steps>

***

## Creating an auth session

Create a single `ChappieAuthSession` as a `@StateObject` in the view that owns your sign-in flow. Chappie reads the Keychain at init time, so the session is already populated if the user signed in during a previous launch.

```swift theme={null}
@StateObject private var authSession = Chappie.authSession()
```

Pass a `ChappieConfiguration` to customise which Keychain slot is used, or to support multiple accounts. See [Multi-Account](/authentication/multi-account) for details.

***

## Observing auth state

`ChappieAuthSession` is an `ObservableObject` with four published properties:

| Property          | Type                     | Description                                                   |
| ----------------- | ------------------------ | ------------------------------------------------------------- |
| `state`           | `ChappieAuthState`       | The current sign-in lifecycle stage                           |
| `deviceLinkState` | `ChappieDeviceLinkState` | Fine-grained progress through the device-code exchange        |
| `accountInfo`     | `ChappieAccountInfo?`    | Email, plan, userID, and accountID — populated once signed in |
| `lastError`       | `ChappieAuthError?`      | The most recent auth failure, or `nil` if none                |

React to `state` in your SwiftUI views to switch between signed-out and signed-in UI:

```swift theme={null}
switch authSession.state {
case .signedIn:
    print(authSession.accountInfo?.plan?.displayName ?? "Unknown plan")
case .signedOut:
    // Show sign-in UI
case .reauthenticationRequired:
    // Prompt the user to sign in again
case .failed(let message):
    print("Auth failed:", message)
default:
    break
}
```

### `ChappieAuthState` values

| Case                                             | Meaning                                                                               |
| ------------------------------------------------ | ------------------------------------------------------------------------------------- |
| `.signedOut`                                     | No credential is stored; the user has not signed in                                   |
| `.signingIn`                                     | Sign-in has started; the device code is being requested                               |
| `.awaitingDeviceCode(userCode:verificationURL:)` | The device code is ready; waiting for the user to approve it in a browser             |
| `.signedIn(expiresAt:)`                          | The user is signed in; `expiresAt` is the credential expiry date, or `nil` if unknown |
| `.reauthenticationRequired`                      | The stored refresh token is invalid or missing; the user must sign in again           |
| `.failed(String)`                                | An unrecoverable error occurred; the associated string is a human-readable message    |

***

## Account info

After a successful sign-in, `authSession.accountInfo` is a `ChappieAccountInfo` value with the following fields:

| Field              | Type           | Description                                                  |
| ------------------ | -------------- | ------------------------------------------------------------ |
| `email`            | `String?`      | The user's ChatGPT email address                             |
| `userID`           | `String?`      | The ChatGPT user identifier                                  |
| `accountID`        | `String?`      | The ChatGPT account (organisation) identifier                |
| `plan`             | `ChappiePlan?` | The user's current plan, with a human-readable `displayName` |
| `isFedRAMPAccount` | `Bool`         | `true` for FedRAMP-compliant accounts                        |

```swift theme={null}
if let info = authSession.accountInfo {
    print(info.email ?? "No email")
    print(info.plan?.displayName ?? "Unknown plan")
}
```

***

## Signing out

Call `signOut()` to clear the stored Keychain credential and reset state to `.signedOut`. Any in-progress sign-in polling task is cancelled automatically.

```swift theme={null}
authSession.signOut()
```

***

## Manual token refresh

Chappie refreshes credentials automatically before requests. If you need to force a refresh — for example, after the user upgrades their plan — call `refreshAuth()`:

```swift theme={null}
try await authSession.refreshAuth()
```

If the refresh token is invalid or missing, `refreshAuth()` transitions the session to `.reauthenticationRequired` and throws a `ChappieAuthError`. Prompt the user to sign in again in that case.

***

## Error reference

<Accordion title="ChappieAuthError cases">
  | Case                                     | When it occurs                                                                  |
  | ---------------------------------------- | ------------------------------------------------------------------------------- |
  | `.expiredDeviceCode`                     | The user did not approve the device code before it expired                      |
  | `.cancelledSignIn`                       | Sign-in was cancelled programmatically or by the user                           |
  | `.networkFailure(String)`                | A network request failed; the associated string is the underlying error message |
  | `.pollingTimeout`                        | The SDK stopped polling before the device code was approved                     |
  | `.callbackTimeout`                       | The sign-in callback did not arrive in time                                     |
  | `.authorizationDenied(String)`           | OpenAI denied the authorisation request                                         |
  | `.tokenExchangeFailed(statusCode:body:)` | The token exchange returned an HTTP error; inspect `statusCode` and `body`      |
  | `.missingRefreshToken`                   | No refresh token is stored; the user must sign in from scratch                  |
  | `.secureRandomFailed(statusCode:)`       | Secure random number generation failed at the OS level                          |
  | `.invalidResponse`                       | The sign-in response could not be parsed                                        |
  | `.unknown(String)`                       | An unexpected error occurred; the associated string contains details            |

  When `requiresReauthentication` is `true` on a `ChappieAuthError` (currently `.missingRefreshToken` and certain token-exchange failures), the SDK automatically signs out and transitions to `.reauthenticationRequired`. All other errors leave existing credentials intact so a retry is possible.
</Accordion>

***

<Note>
  Your app never receives raw access or refresh tokens through Chappie's public API. `ChappieAccountInfo` exposes only identity and plan metadata derived from the stored credential.
</Note>
