After a successful /oauth/token exchange, your app receives three tokens. Each has a different purpose and lifetime.
Token types
| Token | Purpose | Lifetime | Storage |
|---|
| Access token | Call /oauth/userinfo and protected APIs | ~1 hour (expires_in: 3600) | Memory or secure server session |
| Refresh token | Obtain new access tokens without re-login | Long-lived, rotates on use | Secure server-side storage only |
| ID token | OIDC identity claims (JWT) | Same as access token | Parse once at login, do not store long-term |
Never store refresh tokens in browser localStorage or expose client secrets in frontend code. Browser apps should use a backend to exchange codes and refresh tokens.
Access token
Use as a Bearer token:
curl https://id.oakwall.mom/oauth/userinfo \
-H "Authorization: Bearer ACCESS_TOKEN"
When expired, you’ll get 401 invalid_token from /oauth/userinfo. Refresh or re-authenticate.
Refresh token rotation
OakID rotates refresh tokens on every use. The previous token is revoked immediately.
curl -X POST 'https://id.oakwall.mom/oauth/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=refresh_token' \
-d 'refresh_token=CURRENT_REFRESH_TOKEN' \
-d 'client_id=YOUR_CLIENT_ID' \
-d 'client_secret=YOUR_CLIENT_SECRET'
Response includes a new refresh_token. Persist it before discarding the old one.
Rotation failure
If you send an already-used refresh token:
{
"error": "invalid_grant"
}
The user must sign in again through /oauth/authorize.
Events that invalidate all refresh tokens for a client:
- Client secret regenerated in Developer Portal
- User sessions revoked for the app
- User password changed (if applicable)
ID token
The id_token is a JWT signed with HS256. Decode and validate before trusting claims:
| Claim | Check |
|---|
iss | Must equal your issuer (https://id.oakwall.mom) |
aud | Must equal your client_id |
exp | Must be in the future |
sub | Stable user identifier |
import jwt from "jsonwebtoken";
// Server-side only — you need the client secret or a shared validation endpoint
const payload = jwt.decode(idToken, { complete: true });
// Validate iss, aud, exp before using payload.payload.sub
For most apps, calling /oauth/userinfo with the access token is simpler than validating ID tokens locally.
Revocation
Revoke a token when the user signs out:
curl -X POST 'https://id.oakwall.mom/oauth/revoke' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'token=REFRESH_OR_ACCESS_TOKEN' \
-d 'client_id=YOUR_CLIENT_ID' \
-d 'client_secret=YOUR_CLIENT_SECRET'
Consent
OakID stores user consent per (user_id, client_id). After the first approval, subsequent logins skip the consent screen unless scopes change or consent is revoked.
Best practices
- Store refresh tokens server-side — never in the browser
- Persist rotated refresh tokens atomically — race conditions cause
invalid_grant
- Handle
invalid_grant gracefully — redirect to login
- Revoke on logout — call
/oauth/revoke with the refresh token
- Use short-lived access tokens — refresh silently in the background