OAuth 2.0 — Step by Step
With the authorization-code flow + PKCE a third-party app accesses the API on behalf of an aYOUne user without knowing their password. The granted access is always limited to scope ∩ the user's rights ∩ package at consent time and frozen into the token — a third-party token never inherits the user's full rights.
1. Register your app
Register your app under Developer → Apps. You get a client_id (oac_...). For local development a public client (PKCE-only, no secret) is easiest. Set your redirect URI, e.g. http://localhost:8410/callback, and the scopes you need.
2. Generate PKCE
Per sign-in attempt, create a random code_verifier and derive the code_challenge (SHA-256, base64url). S256 is mandatory — plain is rejected.
import crypto from 'crypto';
const b64url = (b) => b.toString('base64').replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'');
const verifier = b64url(crypto.randomBytes(32)); // keep secret
const challenge = b64url(crypto.createHash('sha256').update(verifier).digest());
const state = b64url(crypto.randomBytes(16)); // CSRF protection
3. Authorize URL (browser)
Redirect the user to the consent screen. They sign in and approve the scopes.
https://login.ayoune.app/oauth/authorize
?response_type=code
&client_id=oac_...
&redirect_uri=http://localhost:8410/callback
&scope=crm.consumers.view openid
&state=<state>
&code_challenge=<challenge>
&code_challenge_method=S256
4. Exchange the code for a token
The provider calls your redirect URI with ?code=...&state=.... Verify state, then exchange the code (with the code_verifier) for a token.
curl -X POST https://auth.ayoune.app/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=authorization_code \
-d code=<code> \
-d code_verifier=<verifier> \
-d client_id=oac_... \
-d redirect_uri=http://localhost:8410/callback
# confidential clients also: -d client_secret=<secret>
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "eyJ...",
"scope": "crm.consumers.view openid"
}
5. Scope-limited API call
The access token carries only the granted scope. A call outside it returns 403.
curl -H "Authorization: Bearer <access_token>" \
https://crm-api.ayoune.app/consumers?limit=3
6. Refresh & revoke
# Refresh (rotation)
curl -X POST https://auth.ayoune.app/oauth/token \
-d grant_type=refresh_token -d refresh_token=<refresh_token> -d client_id=oac_...
# Revoke
curl -X POST https://auth.ayoune.app/oauth/revoke \
-d token=<access_or_refresh_token> -d client_id=oac_...
Discovery
All endpoints are self-describing (RFC 8414):
curl https://auth.ayoune.app/.well-known/oauth-authorization-server
Scopes
Scopes are aYOUne rights (module.entity.action, with wildcards crm.consumers.* / crm.*) plus the OIDC standard scopes openid / profile / email (→ /oauth/userinfo).
Reference app
A runnable minimal app (Node/Express) with the complete flow lives in the monorepo under tooling/oauth-reference-app/ — copy it, set your client_id, npm start.