OAuth PKCE Flow ~5 min read
A detailed walkthrough of what happens when a user clicks Log in.
Step 1 — Browser requests auth URL
http
GET /api/auth/loginShell API internally:
typescript
// Generate PKCE verifier (32 random bytes → base64url)
const codeVerifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
// Compute challenge
const codeChallenge = base64url(await crypto.subtle.digest('SHA-256', encode(codeVerifier)));
// Generate CSRF state
const state = crypto.randomUUID();
// Store in Redis (TTL 600s)
await redis.set(`oauth:state:${state}`, JSON.stringify({
codeVerifier,
providerId: 'keycloak',
}), 'EX', 600);Response: 302 redirect to:
https://auth.venizia.ai/realms/veni-ai/protocol/openid-connect/auth
?client_id=veni-ai-platform
&redirect_uri=https://shell-api.venizia.ai/api/auth/callback
&response_type=code
&scope=openid profile email
&state=<uuid>
&code_challenge=<base64url-sha256>
&code_challenge_method=S256Step 2 — User logs in at Keycloak
Keycloak shows the login page. After successful auth, it redirects to:
https://shell-api.venizia.ai/api/auth/callback
?code=<authorization-code>
&state=<same-uuid>Step 3 — Shell API exchanges the code
http
GET /api/auth/callback?code=xyz&state=uuidShell API internally:
typescript
// Retrieve state from Redis
const stored = await redis.get(`oauth:state:${state}`);
// stored = { codeVerifier: '...', providerId: 'keycloak' }
// Exchange code for tokens (using INTERNAL Keycloak URL)
POST http://keycloak:8080/realms/veni-ai/protocol/openid-connect/token
grant_type=authorization_code
&code=xyz
&redirect_uri=...
&client_id=veni-ai-platform
&code_verifier=<retrieved-from-redis>Keycloak verifies that SHA256(code_verifier) == code_challenge from step 1.
Step 4 — Shell JWT issued
Shell API:
- Decodes the Keycloak
id_tokento getsub,email,given_name,family_name - Creates or updates the user in PostgreSQL
- Issues a Shell JWT signed with
APP_ENV_JWT_SECRET:
json
{
"sub": "user-uuid",
"email": "user@example.com",
"username": "veni",
"firstName": "Veni",
"lastName": "AI",
"roles": ["admin"],
"organizationId": "org-uuid",
"exp": 1700000000
}Response: 302 redirect to Shell UI with the Shell JWT in the URL fragment or session.
Step 5 — Token distribution
Shell UI stores the Shell JWT and broadcasts both tokens to remote micro-frontends:
typescript
const channel = new BroadcastChannel('veni-auth');
channel.postMessage({
type: 'TOKEN_UPDATE',
shellToken: '<shell-jwt>',
keycloakToken: '<keycloak-access-token>',
});Remote services exchange the Keycloak token for a service-scoped JWT via POST /api/auth/exchange-keycloak.