Authorization Code + PKCE
Overview
Section titled “Overview”All client apps authenticate using the OAuth2 Authorization Code flow with PKCE (RFC 7636). This is the most secure browser-based OAuth2 flow — it prevents authorization code interception attacks without requiring client secrets in the browser.
Flow Diagram
Section titled “Flow Diagram”┌──────────┐ ┌──────────────┐│ Browser │ │ auth.beshoy │└────┬─────┘ └──────┬───────┘ │ │ │ 1. GET /oauth/authorize │ │ ?client_id=proj_xxx │ │ &redirect_uri=https://... │ │ &response_type=code │ │ &code_challenge=BASE64URL(SHA256(v)) │ │ &code_challenge_method=S256 │ │ &state=ENCRYPTED(verifier+returnTo) │ │ ─────────────────────────────────────────▶│ │ │ │ 2. Login page (branded) │ │ ◀─────────────────────────────────────────│ │ │ │ 3. User submits credentials │ │ ─────────────────────────────────────────▶│ │ │ │ 4. 302 → redirect_uri?code=xxx&state=yyy │ │ ◀─────────────────────────────────────────│ │ │┌────┴─────┐ ┌──────┴───────┐│ App │ 5. POST /oauth/token │ auth.beshoy ││ Server │ {code, code_verifier, │ ││ │ client_id, client_secret}│ ││ │ ─────────────────────────────▶│ ││ │ │ ││ │ 6. {access_token, │ ││ │ refresh_token} │ ││ │ ◀─────────────────────────────│ │└──────────┘ └──────────────┘Step 1: Authorization Request
Section titled “Step 1: Authorization Request”Redirect the user to the authorization endpoint with the required parameters.
Parameters
Section titled “Parameters”| Parameter | Required | Description |
|---|---|---|
client_id | Yes | Project ID (e.g., proj_gym) |
redirect_uri | Yes | Must exactly match a registered URI |
response_type | Yes | Must be code |
code_challenge | Yes | BASE64URL(SHA256(code_verifier)) |
code_challenge_method | Yes | Must be S256 |
state | Yes | Opaque string returned unchanged after auth |
Generating PKCE
Section titled “Generating PKCE”// 1. Generate random code verifier (32 bytes → 43 chars base64url)const codeVerifier = crypto.randomBytes(32).toString('base64url');
// 2. Compute S256 challengeconst codeChallenge = crypto .createHash('sha256') .update(codeVerifier) .digest('base64url');State Encryption
Section titled “State Encryption”We recommend encrypting the state parameter with AES-GCM to securely carry the code verifier and return URL through the redirect:
async function encryptState( data: { v: string; r: string }, secret: Uint8Array,): Promise<string> { const key = await crypto.subtle.importKey( 'raw', secret.slice(0, 32), 'AES-GCM', false, ['encrypt'], ); const iv = crypto.getRandomValues(new Uint8Array(12)); const plaintext = new TextEncoder().encode(JSON.stringify(data)); const ciphertext = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, key, plaintext, ); const combined = new Uint8Array(iv.length + new Uint8Array(ciphertext).length); combined.set(iv); combined.set(new Uint8Array(ciphertext), iv.length); return btoa(String.fromCharCode(...combined)) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/, '');}Example Request
Section titled “Example Request”GET https://auth.beshoy.ai/oauth/authorize ?client_id=proj_gym &redirect_uri=https://gym.beshoy.ai/api/auth/callback &response_type=code &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256 &state=eyJhbGciOi...Step 2: User Authentication
Section titled “Step 2: User Authentication”The auth service renders a branded login page with the methods enabled for the project. The page is server-rendered HTML — no JavaScript framework required on the auth side.
Step 3: Authorization Response
Section titled “Step 3: Authorization Response”On successful authentication, the auth service:
- Generates a one-time authorization code (stored in KV, 5-minute TTL)
- Associates the PKCE challenge with the code
- Redirects to
redirect_uriwithcodeandstateparameters
HTTP/1.1 302 FoundLocation: https://gym.beshoy.ai/api/auth/callback?code=abc123&state=eyJhbGciOi...Step 4: Token Exchange
Section titled “Step 4: Token Exchange”See Token Exchange for the full POST /oauth/token documentation.
Redirect URI Validation
Section titled “Redirect URI Validation”The auth service validates redirect URIs with exact string matching against the project’s registered URIs. No wildcards, no pattern matching.
// Registered URIs for proj_gym:[ "https://gym.beshoy.ai/api/auth/callback", "http://localhost:3001/api/auth/callback"]A request with redirect_uri=https://gym.beshoy.ai/api/auth/callback/evil would be rejected.
Error Responses
Section titled “Error Responses”| Error | Status | Cause |
|---|---|---|
Missing or invalid parameters | 400 | Missing required query params |
Only S256 code_challenge_method is supported | 400 | Plain PKCE attempted |
Invalid client_id | 400 | Project not found |
Invalid redirect_uri | 400 | URI not in project’s allowlist |