Skip to main content
The authorization code flow is the standard way to authenticate users with Ave. The user is redirected to Ave, authenticates, and Ave redirects back with a short-lived code. You exchange the code for tokens server-side (or via PKCE from the browser).

The two JWT tokens

Ave returns two different JWTs on a successful token exchange. Understanding the difference is critical before writing any validation or auth integration.
The id_token is an OIDC identity token. It proves who the user is. It is signed by Ave and its audience is your client ID.
Profile claims (name, preferred_username, picture) are only present if you requested the profile scope. email is only present if you requested email and the selected identity has a verified email. nonce is only present if you included one in the authorization request.Use id_token for:
  • Establishing your app session (validating who logged in)
  • Convex custom auth integration
  • Any system that needs to verify Ave-issued identity
If you are building Convex auth, use id_token. Its aud is your clientId, which is what Convex validates against. The access_token_jwt has aud: "https://aveid.net" — Convex will reject it because the audience does not match your app.

Claim reference

Full flow

There are three ways to implement this — pick what fits your setup.

Refresh token flow

Refresh tokens allow you to get new access tokens without requiring the user to log in again. They are only issued when you request offline_access scope.
For public PKCE clients, the rotating refresh token is the credential for this grant and remains bound to the registered clientId. This works for browser, desktop custom-protocol, and native application origins without a client secret.
Refresh tokens rotate on every use. Each successful refresh invalidates the old token and issues a new one. If you try to reuse an old refresh token, you will get invalid_grant and the server may revoke the entire token family as a security measure. Always persist the new refresh_token from the response before discarding the old one.

Userinfo endpoint

GET /api/oauth/userinfo returns live identity claims. Use this when you need fresh data that may not be in the cached id_token.
The returned claims depend on the scopes your access token has. The response always includes sub (identity UUID).

OIDC discovery & manual token validation

Fetch the discovery document to programmatically get endpoint URLs and signing key locations:
For non-JS environments or any library that validates tokens without the SDK, follow this order:
1

Fetch JWKS

GET https://aveid.net/.well-known/jwks.json. Cache the response and re-fetch only when you encounter an unknown kid.
2

Verify signature

Use the key matching the kid in the token header. Ave signs with RS256.
3

Check timestamps

Verify exp > now and iat <= now. Use a small clock tolerance (60 seconds) to handle drift.
4

Verify issuer and audience

iss must equal https://aveid.net. aud must equal your client ID. Reject anything else.
5

Verify nonce

If you sent a nonce in the authorization request, the id_token must contain the same value. This prevents replay attacks.

Edge cases

Don’t store the code and exchange later. Exchange immediately after validating state on the callback page.
Protocol, hostname, path, and query string must all match exactly. A trailing slash difference will cause invalid_grant. Register the exact URI you use.Development mode relaxes this only for localhost, loopback, and Expo Go redirect URLs so local ports can change without adding every callback. Production callback URLs still need explicit registration.
The openid scope was not requested or was not granted. Add openid to your scope list.
The offline_access scope was not requested or was not granted. Add offline_access to your scope list.
The user_id scope was not included in the granted scopes. The uid claim and user_id token response field only appear when user_id is in the authorized scope list.
Last modified on August 27, 2026