OAuth 2.0 + PKCE
UCap implements the standard OAuth 2.0 Authorization Code flow with PKCE (RFC 7636). Use it any time a consumer needs to authorize your app to read or write data on their behalf. For pure server-to-server access (no user), use client_credentials instead.
Discovery
Every OAuth-aware client should bootstrap from the metadata document. URLs and supported algorithms can change without notice — the metadata is the source of truth.
curl https://phase2.ucap.africa/api/public/v1/oauth/metadataThe flow at a glance
Consumer Your app UCap │ │ │ │ click "Connect" │ │ │ ─────────────────▶│ │ │ │ generate verifier │ │ │ + challenge (S256) │ │ │ │ │ redirect to /oauth/authorize?…challenge│ │ ◀─────────────────│ ────────────────────▶ │ │ │ approve scopes (consent screen) │ │ ──────────────────────────────────────▶ │ │ │ │ 302 → return_uri?code=…&state=… │ │ ◀────────────────────────────────────── │ │ │ │ │ │ POST /oauth/token │ │ │ code + verifier │ │ │ ──────────────────▶ │ │ │ ◀────────────────── │ │ │ access_token │ │ │ │ │ GET /me/profile │ │ │ Bearer … │ │ │ ──────────────────▶ │
1. Generate a PKCE pair
A code verifier is a random 43–128 character URL-safe string. The challenge is the base64url-encoded SHA-256 of the verifier.
import { generateCodeVerifier, deriveCodeChallenge } from "@ucap/sdk";
const verifier = generateCodeVerifier(); // store in session
const challenge = await deriveCodeChallenge(verifier);
const state = crypto.randomUUID(); // CSRF token2. Redirect to /oauth/authorize
https://phase2.ucap.africa/oauth/authorize
?response_type=code
&client_id=cli_xxx
&redirect_uri=https://yourapp.com/callback
&scope=consumer.profile.read+consumer.documents.read
&state=${state}
&code_challenge=${challenge}
&code_challenge_method=S256The consumer signs in (if needed), reviews the scopes, and approves. We redirect back to redirect_uri with ?code=…&state=…. Always verify state matches before exchanging the code.
3. Exchange the code for tokens
curl -X POST https://phase2.ucap.africa/api/public/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=${code}" \
-d "redirect_uri=https://yourapp.com/callback" \
-d "client_id=cli_xxx" \
-d "code_verifier=${verifier}"{
"access_token": "ey…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rft_…",
"scope": "consumer.profile.read consumer.documents.read"
}4. Call the API
curl https://phase2.ucap.africa/api/public/v1/me/profile \
-H "Authorization: Bearer ${access_token}"Refresh
curl -X POST https://phase2.ucap.africa/api/public/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=${refresh_token}" \
-d "client_id=cli_xxx"Refresh tokens are single-use and rotate on every exchange. Replay attempts invalidate the entire chain — store the latest token only.
Revoke
curl -X POST https://phase2.ucap.africa/api/public/v1/oauth/revoke \
-u "cli_xxx:${client_secret}" \
-d "token=${access_or_refresh_token}"Client credentials (no user)
For server-to-server calls — creating sessions, reading session payloads — use client_credentials. Scopes are limited to the sessions.* family.
curl -X POST https://phase2.ucap.africa/api/public/v1/oauth/token \
-u "cli_xxx:${client_secret}" \
-d "grant_type=client_credentials" \
-d "scope=sessions.create sessions.read"