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

# Upgrading from Quick Ave to standard OIDC

> Swap Quick Ave for a registered app and unlock refresh tokens, app branding, Connector delegation, E2EE, and confidential clients — with minimal code changes.

Quick Ave and the standard OIDC flow use the **same token families, JWKS endpoint, and OIDC discovery URL**. Upgrading is mostly a matter of swapping a few function calls and registering an app in the developer portal.

## What you gain

| Feature                             | Quick Ave              | Standard Ave                       |
| ----------------------------------- | ---------------------- | ---------------------------------- |
| Refresh tokens                      | ❌                      | ✅ (request `offline_access` scope) |
| App branding on consent screen      | ❌                      | ✅                                  |
| Confidential client (client secret) | ❌                      | ✅                                  |
| Connector delegation (app-to-app)   | ❌                      | ✅                                  |
| E2EE app-key delivery               | ❌                      | ✅                                  |
| Custom token TTLs                   | ❌                      | ✅                                  |
| `offline_access` scope              | ❌                      | ✅                                  |
| Longer-lived sessions               | ❌ (1 hour, no refresh) | ✅                                  |

## What stays the same

* Token families — `id_token`, `access_token_jwt`, and `access_token` keep the same roles
* OIDC discovery: `https://aveid.net/.well-known/openid-configuration`
* JWKS endpoint: `https://aveid.net/.well-known/jwks.json`
* Claims (`sub`, `iss`, `aud`, `name`, `email`, etc.) — same meaning in both flows
* Backend JWT validation shape — update the expected `id_token` audience from `origin:https://yourapp.com` to your registered `clientId`
* Convex `domain` config — stays `https://aveid.net`

## Upgrade steps

<Steps>
  <Step title="Register an app in the developer portal">
    Go to [devs.aveid.net](https://devs.aveid.net) and create an OAuth app. You'll receive:

    * **Client ID** (e.g. `app_xxxx`) — replaces your `origin:https://yourapp.com` clientId
    * **Client secret** — only if you choose a confidential client flow

    Register the exact callback URL you plan to use (e.g. `https://yourapp.com/callback`).

    <Tip>
      You can keep using PKCE without a client secret — just register the app and get a `clientId`. The confidential client option is only needed if you want server-side token exchange with a secret.
    </Tip>
  </Step>

  <Step title="Replace startQuickSignIn with startPkceLogin">
    ```ts theme={null}
    // Before (Quick Ave)
    import { startQuickSignIn } from "@ave-id/sdk/client";
    await startQuickSignIn();

    // After (Standard Ave with PKCE)
    import { startPkceLogin } from "@ave-id/sdk/client";
    await startPkceLogin({
      clientId: "app_xxxx",
      redirectUri: "https://yourapp.com/callback",
      scope: "openid profile email offline_access",
    });
    ```

    `startPkceLogin` works the same way as `startQuickSignIn` — it generates PKCE params, stores them in `sessionStorage`, and redirects to Ave.
  </Step>

  <Step title="Replace handleQuickCallback with finishPkceLogin">
    ```ts theme={null}
    // Before (Quick Ave) — on your /ave/callback page
    import { handleQuickCallback } from "@ave-id/sdk/client";
    await handleQuickCallback();

    // After (Standard Ave) — on your callback page
    import { finishPkceLogin } from "@ave-id/sdk/client";

    const tokens = await finishPkceLogin({
      clientId: "app_xxxx",
      redirectUri: "https://yourapp.com/callback",
    });

    // Store tokens in your own session management
    // tokens.refresh_token is now available if you requested offline_access
    ```

    Unlike `handleQuickCallback`, `finishPkceLogin` returns the full `TokenResponse` instead of a `QuickIdentity` — you manage your own session from here. State verification and token validation happen automatically, just like they did in Quick Ave.
  </Step>

  <Step title="Replace getQuickIdentity with your own session management">
    Quick Ave stored the identity in `localStorage` automatically. With Standard Ave, you decide how to store and serve the session:

    ```ts theme={null}
    // Before (Quick Ave)
    import { getQuickIdentity } from "@ave-id/sdk/client";
    const user = getQuickIdentity();
    // user.token, user.idToken, user.displayName, etc.

    // After (Standard Ave)
    // Read from your own session store (cookie, context, database, etc.)
    const { user, id_token, access_token, refresh_token } = tokens;
    // user.id is the identity UUID
    // Use refresh_token to get new tokens when they expire
    ```
  </Step>

  <Step title="Add refresh token handling">
    This is what you gain. With `offline_access` in your scope, you can silently refresh without re-authenticating the user:

    ```ts theme={null}
    import { refreshToken } from "@ave-id/sdk";

    const newTokens = await refreshToken(
      { clientId: "app_xxxx", redirectUri: "https://yourapp.com/callback" },
      { refreshToken: storedRefreshToken }
    );

    // Always store the new refresh token immediately — they rotate on every use
    storeRefreshToken(newTokens.refresh_token);
    ```
  </Step>
</Steps>

## Updating Convex

If you're using Convex, the only change is the `applicationID` in your `auth.config.ts`:

```ts theme={null}
// Before (Quick Ave)
export default {
  providers: [{
    domain: "https://aveid.net",
    applicationID: "origin:https://yourapp.com",  // derived from your origin
  }],
};

// After (Standard Ave)
export default {
  providers: [{
    domain: "https://aveid.net",
    applicationID: "app_xxxx",  // your registered client ID
  }],
};
```

Everything else — the `fetchAccessToken` implementation, Convex function auth, the JWKS endpoint — stays the same.

<Note>
  The `id_token` from a Standard Ave app has `aud: "app_xxxx"` instead of `aud: "origin:https://yourapp.com"`. Convex validates `aud` against `applicationID`, which is why you update `applicationID` when you upgrade.
</Note>

## Quick reference: function mapping

| Quick Ave                    | Standard Ave                                   | Notes                                           |
| ---------------------------- | ---------------------------------------------- | ----------------------------------------------- |
| `startQuickSignIn()`         | `startPkceLogin(config)`                       | Pass `clientId` + `redirectUri`                 |
| `handleQuickCallback()`      | `exchangeCode(config, { code, codeVerifier })` | You manage the session                          |
| `getQuickIdentity()`         | Your own session store                         | Use the `user` field from `TokenResponse`       |
| `clearQuickIdentity()`       | Clear your session + optionally redirect       | No SDK call needed                              |
| `startQuickSessionMonitor()` | Periodic refresh token check                   | Use `refreshToken()` on a timer                 |
| `user.token`                 | `tokens.access_token_jwt`                      | Ave API access JWT; not your app login token    |
| `user.idToken`               | `tokens.id_token`                              | Same OIDC token, `aud` changes to your clientId |

## If you still get logged out with `offline_access`

Quick Ave **never** gives a refresh token — sessions are **bounded by access token TTL** (\~one hour by default). After upgrading to a registered app:

* Request **`openid profile email offline_access`** and persist the full token response.
* After **every** refresh response, save the **new** `refresh_token` immediately (tokens can rotate).
* Avoid **parallel** `refreshToken` calls from different components — use a **single session layer** ([Ave Session](/guides/ave-session-and-tokens)) so refresh is single-flight.
* For Convex, do not pass a **stale** `id_token` string to `setAuth` — pass a function that returns a valid token (see [Convex custom auth](/guides/convex-custom-auth)).
