Skip to main content
A confidential client is an app whose server can store a clientSecret securely — a secret never exposed to browsers, mobile apps, or end users. Server-rendered apps, API backends, and background services are typical confidential clients.
If any part of your app runs in a browser or on a user’s device, use PKCE instead. Client secrets in browser code are not secret.

Flow overview

1

Redirect the browser to Ave

From your server, construct the authorization URL and redirect the user’s browser to https://aveid.net/signin. You can still include PKCE parameters — this is actually recommended even for confidential clients, as defense-in-depth.
2

Receive the callback on your server

Your server endpoint at the registered redirect_uri receives ?code=...&state=.... Validate state against the value stored in the user’s server session before proceeding.
3

Exchange the code with your client secret

import { exchangeCodeServer } from "@ave-id/sdk/server";

const tokens = await exchangeCodeServer(
  {
    clientId: process.env.AVE_CLIENT_ID!,
    clientSecret: process.env.AVE_CLIENT_SECRET!,
    redirectUri: "https://yourapp.com/callback",
  },
  { code }
);
Or with raw HTTP:
POST https://api.aveid.net/api/oauth/token
Content-Type: application/json

{
  "grantType": "authorization_code",
  "code": "AUTH_CODE",
  "redirectUri": "https://yourapp.com/callback",
  "clientId": "YOUR_CLIENT_ID",
  "clientSecret": "YOUR_CLIENT_SECRET"
}
4

Store tokens server-side and issue a session

Store access_token, access_token_jwt, id_token, and refresh_token in your encrypted server-side session store. Issue your own session cookie to the browser.Never send raw Ave tokens to the browser — issue your own short-lived session identifier instead.
5

Refresh silently when tokens expire

import { refreshTokenServer } from "@ave-id/sdk/server";

const newTokens = await refreshTokenServer(
  {
    clientId: process.env.AVE_CLIENT_ID!,
    clientSecret: process.env.AVE_CLIENT_SECRET!,
    redirectUri: "https://yourapp.com/callback",
  },
  { refreshToken: storedRefreshToken }
);

// Replace stored refresh token immediately
await storeRefreshToken(newTokens.refresh_token);

Client secret rules

Store the secret in encrypted runtime config (environment variables, secrets manager)
Never ship clientSecret to browsers, mobile apps, or frontend code
Rotate secrets in the developer portal and your deployment environment at the same time
Log token endpoint failures without logging raw token values or secrets

Handling refresh token rotation

Ave rotates refresh tokens on every use — the old token is immediately invalidated after a successful refresh. If you store the old token and try to reuse it, you’ll get invalid_grant. Best practice:
  1. Store the new refresh_token from every refresh response before discarding the old one
  2. Use a database row lock or atomic update to prevent concurrent refreshes from the same token
  3. If refresh fails with invalid_grant: stop retry loops, clear the session, and force re-authentication
LayerWhat it holds
Server session store (encrypted)access_token, refresh_token, id_token, token expiry
BrowserShort-lived session cookie pointing to server session (HTTP-only, Secure, SameSite=Lax)
Your APIIssues responses based on server-side session lookups — Ave tokens never cross to the client
You can also mint your own short-lived app JWT from the validated Ave id_token and send that to the browser. This decouples your session format from Ave’s token format and gives you full control over expiry and claims.
Last modified on March 4, 2026