OAuth 2.0 / OIDC endpoints
External apps use the Authorization Code + PKCE flow for SSO. It is the only way to obtain application-bound JWTs.
| Method | Endpoint | Purpose | Auth |
|---|---|---|---|
GET | /.well-known/openid-configuration | OIDC discovery document. | Public |
GET | /api/v1/oauth/jwks?client_id=hrm | Application public keys as JWKS. | Public |
GET | /api/v1/oauth/authorize | Validate an authorization request for the UI consent page. | Public |
POST | /api/v1/oauth/authorize | Issue an authorization code after the Auth UI session approves the request. | Auth UI bearer token |
GET | /api/v1/oauth/logout | Validate a post-logout redirect URI against the application's Allowed URLs. | Public |
POST | /api/v1/oauth/token | Exchange authorization code + PKCE verifier, or rotate a refresh token. | Public / server-to-server (X-App-Public-Key) |
POST | /api/v1/oauth/revoke | Revoke a refresh token during external-app logout. | Public / server-to-server (X-App-Public-Key) |
GET | /api/v1/oauth/userinfo | Return the OIDC user profile for a valid access token. | Bearer access token |
Authorization requests are sent to the UI browser endpoint configured as
OAUTH_AUTHORIZATION_ENDPOINT (default
http://localhost:3000/oauth/authorize). The token endpoint is the API
endpoint:
POST /api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded
X-App-Public-Key: -----BEGIN PUBLIC KEY-----\nMIIB...\n-----END PUBLIC KEY-----
grant_type=authorization_code&client_id=hrm&redirect_uri=https%3A%2F%2Fhrm.example.com%2Fcallback&code=<code>&code_verifier=<verifier>
Token response covering
The access_token, id_token, and refresh_token response fields are
returned with a lightweight cvr1 AES-GCM cover derived from the application
public key supplied in X-App-Public-Key. External app backends must uncover
those values with the same public key before JWT verification/use.
After uncovering, access and ID tokens are RS256 JWTs signed with the resolved
application's private key. The JWT header kid equals the application
public-key fingerprint and matches the key published by
/api/v1/oauth/jwks?client_id=<appId>.
The redirect_uri and post_logout_redirect_uri are validated against the
application's Allowed URLs. The user's company is resolved server-side from
the user's active memberships subscribed to the requested application —
clients do not pick the company.
Token creation rules
GETandPOST /api/v1/oauth/authorizecreate the authorization code.POST /api/v1/oauth/token(withgrant_type=authorization_codeorgrant_type=refresh_token) returns the access token, ID token, and refresh token.- The token endpoint requires the application public key in
X-App-Public-Key(or anapp_public_keyform/JSON field). The backend resolves the application byclient_id, confirms the public key matches the application on file, and signs with that application's private key. POST /api/v1/auth/loginandPOST /api/v1/auth/refreshdo not acceptX-App-Public-Keyand never produce application JWTs.
The authfy npm package wires this whole flow — including
PKCE, the token cover, silent refresh, and logout — into a Next.js app with
two files.