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
| Method | Path | Purpose |
|---|---|---|
GET | /oauth2/authorize | Start the authorization flow |
POST | /oauth2/token | Exchange an authorization code for tokens |
GET | /oauth2/userinfo | Return user attributes for the token holder |
Flow Overview
- Client starts the flow — redirect the user's browser to
/oauth2/authorizewith the required parameters. - Login — if the user has no active session, O2ID redirects to the login portal. After the user authenticates, O2ID resumes the authorization request automatically.
- Authorization code issued — O2ID redirects back to the registered
redirect_uriwith acodequery parameter and the originalstatevalue. - Token exchange — the client posts the code, PKCE verifier, and
client_idto/oauth2/tokenand 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:
| Parameter | Value |
|---|---|
response_type | code |
client_id | The clientId of a registered application |
redirect_uri | A callback URL registered on the application |
code_challenge | PKCE challenge derived from the verifier |
code_challenge_method | S256 or plain |
Optional query parameters:
| Parameter | Description |
|---|---|
scope | Space-separated scopes, e.g. openid profile |
state | Opaque 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:
| Field | Value |
|---|---|
grant_type | authorization_code |
code | The authorization code from the redirect |
client_id | Same clientId used in the authorization request |
redirect_uri | Same redirect URI used in the authorization request |
code_verifier | The 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
stateround-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, andnameclaims
See the API Reference for the full OpenAPI contract.