Skip to main content

Authorization Code Grant

The OAuth2 authorization-code grant is the primary flow O2ID supports. It issues a short-lived authorization code to the client after the user authenticates, which the client then exchanges for an access token and refresh token. PKCE is required.

Endpoints

MethodPathPurpose
GET/oauth2/authorizeStart the authorization flow
POST/oauth2/tokenExchange an authorization code for tokens
GET/oauth2/userinfoReturn user attributes for the token holder

Flow Overview

  1. Client starts the flow — redirect the user's browser to /oauth2/authorize with the required parameters.
  2. Login — if the user has no active session, O2ID redirects to the login portal. After the user authenticates, O2ID resumes the authorization request automatically.
  3. Authorization code issued — O2ID redirects back to the registered redirect_uri with a code query parameter and the original state value.
  4. Token exchange — the client posts the code, PKCE verifier, and client_id to /oauth2/token and receives tokens.
Browser O2ID Client App
│ │ │
│←── redirect to /oauth2/authorize ─────────────────│
│ │ │
│──── GET /oauth2/authorize ──►│ │
│ │ │
│◄── redirect to /login ───│ (no session) │
│ │ │
│──── POST /login ─────────►│ │
│ │ │
│◄── redirect to callback ─│ ?code=…&state=… │
│ │ │
│─── GET /callback?code=… ──────────────────────────►│
│ │ │
│ │◄── POST /oauth2/token ──│
│ │─── { access_token } ───►│

PKCE

PKCE (Proof Key for Code Exchange) is required for all authorization requests. Both plain and S256 challenge methods are supported. Use S256 in production.

Generate a verifier and challenge:

CODE_VERIFIER="a-long-random-string-at-least-43-chars"

# S256 challenge
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" \
| openssl dgst -sha256 -binary \
| openssl base64 -A \
| tr '+/' '-_' \
| tr -d '=')

Authorization Request

GET /oauth2/authorize

Required query parameters:

ParameterValue
response_typecode
client_idThe clientId of a registered application
redirect_uriA callback URL registered on the application
code_challengePKCE challenge derived from the verifier
code_challenge_methodS256 or plain

Optional query parameters:

ParameterDescription
scopeSpace-separated scopes, e.g. openid profile
stateOpaque string round-tripped to the redirect URI

Success response: 302 Found redirect to redirect_uri?code=…&state=…

Token Request

POST /oauth2/token

Content type: application/x-www-form-urlencoded

Required fields:

FieldValue
grant_typeauthorization_code
codeThe authorization code from the redirect
client_idSame clientId used in the authorization request
redirect_uriSame redirect URI used in the authorization request
code_verifierThe original PKCE verifier

Success response:

{
"access_token": "…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "…",
"scope": "openid profile"
}

Authorization codes are single-use. Reusing a code after a successful exchange returns invalid_grant.

Full curl Walkthrough

This walkthrough runs the complete flow from scratch using curl, jq, and openssl. Start O2ID before running these commands:

o2id serve --addr :8080

Create an application. The callback URL must match the redirect_uri used during authorization:

APP_JSON=$(curl -s -X POST http://localhost:8080/applications \
-H "Content-Type: application/json" \
-d '{"name":"Demo App","callbackUrls":["http://localhost:3000/callback"]}')

CLIENT_ID=$(printf '%s' "$APP_JSON" | jq -r .clientId)

Create a user with a password:

curl -s -X POST http://localhost:8080/users \
-H "Content-Type: application/json" \
-d '{"email":"alice@example.com","displayName":"Alice","password":"secret"}'

Log in and save the session cookie:

curl -i -c /tmp/o2id-cookies.txt -X POST http://localhost:8080/login \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "email=alice@example.com&password=secret"

Generate a PKCE verifier and S256 challenge:

CODE_VERIFIER="verifier-1"
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" \
| openssl dgst -sha256 -binary \
| openssl base64 -A \
| tr '+/' '-_' \
| tr -d '=')

Authorize the logged-in user:

curl -i -b /tmp/o2id-cookies.txt \
"http://localhost:8080/oauth2/authorize?response_type=code&client_id=${CLIENT_ID}&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&scope=openid%20profile&state=demo-state&code_challenge=${CODE_CHALLENGE}&code_challenge_method=S256"

The response is a 302 Found redirect to the registered callback URL. Copy the code query parameter from the Location header.

Exchange the authorization code for tokens:

AUTH_CODE="paste-code-from-location-header"

curl -s -X POST http://localhost:8080/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code=${AUTH_CODE}" \
--data-urlencode "client_id=${CLIENT_ID}" \
--data-urlencode "redirect_uri=http://localhost:3000/callback" \
--data-urlencode "code_verifier=${CODE_VERIFIER}"

Call the UserInfo endpoint with the access token:

ACCESS_TOKEN=$(printf '%s' "$TOKEN_JSON" | jq -r .access_token)

curl -s http://localhost:8080/oauth2/userinfo \
-H "Authorization: Bearer $ACCESS_TOKEN"

Supported Features

  • Login transaction creation for unauthenticated authorization requests
  • Embedded or external portal redirect before authorization
  • Session-cookie based authorization after login
  • Registered callback URL validation
  • Optional OAuth scopes
  • state round-tripping to the application redirect URI
  • Plain and S256 PKCE challenges
  • Single-use authorization codes
  • Bearer access token and refresh token responses
  • UserInfo endpoint returning sub, email, and name claims

See the API Reference for the full OpenAPI contract.