Which token to use
Ave returns two JWTs. They look similar but serve different purposes and have different audiences:| Token | aud claim | Purpose |
|---|---|---|
id_token | Your clientId | Identity assertion — proves who the user is |
access_token_jwt | https://aveid.net | API authorization — grants access to Ave’s resources |
aud claim against the applicationID you configure. If you use access_token_jwt, Convex will reject it because its audience is https://aveid.net, not your client ID.
Auth config
Add Ave as a custom provider inconvex/auth.config.ts:
domain maps to the iss (issuer) claim in the token. Ave’s issuer is https://aveid.net. Convex fetches {domain}/.well-known/openid-configuration to discover the JWKS endpoint automatically.What Convex validates
When you callconvex.setAuth(idToken) or equivalent, Convex:
- Fetches
https://aveid.net/.well-known/openid-configuration - Gets the JWKS URL from the discovery document
- Downloads and caches signing keys
- Verifies the JWT signature (RS256)
- Checks
iss === "https://aveid.net" - Checks
aud === "your_client_id"(theapplicationIDyou configured) - Checks
exp > now
ctx.auth.getUserIdentity().
What claims Convex exposes
After a successful auth check,ctx.auth.getUserIdentity() returns:
subject is the Ave identity UUID — this is the value you should use as your primary user identifier in Convex.
Full implementation
Complete the Ave OAuth flow
Follow the OAuth authorization code flow to get tokens. You need the
openid scope at minimum — without it, Ave does not return an id_token.Pass id_token to Convex
ConvexProviderWithAuth or the useAuth hook from your auth state to keep the token fresh.Handle token refresh (recommended: Ave Session)
id_token expires (check exp claim). Do not pass a static tokens.id_token to setAuth — Convex validates exp on every request. Prefer Ave Session (AveSession + wireAveSessionToConvex) so refresh is single-flight, persisted, and runs before expiry.Manual refresh (if you are not using AveSession yet):Default token lifetime is often about one hour (
expires_in). Quick Ave has no refresh token — upgrade to a registered app with offline_access. See also Sessions still flaky below.Sessions still flaky
If users drop out after ~one hour, or behavior feels random even withoffline_access:
| Cause | What to do |
|---|---|
Quick Ave (origin:… clientId) | No refresh tokens — upgrade |
Missing offline_access | Request it — refresh tokens are not issued without it |
Using static id_token in setAuth | Use a function that returns a fresh token (Ave Session) |
| Refresh rotation not saved | After every refresh, persist the new refresh_token before other requests run |
| Concurrent refresh | Two callers refreshing at once can invalidate rotated refresh tokens — use single-flight (AveSession) |
| Cold start | Ensure hydrate() (or equivalent) finishes before authenticated Convex calls |
Complete id_token payload reference
For your reference, here is the fullid_token payload shape:
Troubleshooting
Convex rejects the token with 'invalid audience'
Convex rejects the token with 'invalid audience'
You are passing
access_token_jwt instead of id_token. The access_token_jwt has aud: "https://aveid.net" — Convex expects aud === your_client_id. Always use id_token.No id_token in the token response
No id_token in the token response
You did not include
openid in the scope parameter of the authorization request. id_token is only issued when the openid scope is granted. Add openid to your scope list.Convex says issuer does not match
Convex says issuer does not match
Verify that
domain in auth.config.ts is exactly https://aveid.net. The iss claim in Ave tokens is https://aveid.net. If you have set a custom issuer via the OIDC_ISSUER environment variable in your Ave server, use that value instead.getUserIdentity() returns null
getUserIdentity() returns null
The token may have expired, or
convex.setAuth was not called before the query/mutation ran. Make sure your auth state provider calls convex.setAuth before any authenticated Convex calls.Clock skew errors
Clock skew errors
Server clocks must be reasonably in sync. Convex allows a small tolerance. If you see
exp validation failures on otherwise valid tokens, check that your backend server time is accurate.