UCAP
For businessCreate your profile

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/metadata

The 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 token

2. 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=S256

The 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"