OAuth & OIDC
O2ID implements the authorization-code grant with PKCE and OpenID Connect ID token issuance.
| Method | Path | Description |
|---|---|---|
GET | /oauth2/authorize | Start the authorization flow |
POST | /oauth2/token | Exchange a code for tokens |
GET | /oauth2/userinfo | Return claims for a token holder |
POST | /oauth2/introspect | Report what an access token carries |
GET | /oauth2/jwks | Public keys for ID token verification |
GET | /.well-known/openid-configuration | OIDC discovery document |
See Authorization Code Grant for a full walkthrough, and the API Reference for the complete OpenAPI contract.
Authorize
GET /oauth2/authorize starts the authorization-code flow. It redirects
an authenticated user's browser back to the application with an
authorization code; if no session exists yet, it redirects to the login
portal first and resumes the authorization request once the user
authenticates.
| Parameter | Required | Description |
|---|---|---|
response_type | Yes | Must be code |
client_id | Yes | A registered application's clientId |
redirect_uri | Yes | A callback URL registered on the application |
code_challenge | Yes | PKCE challenge derived from a verifier |
code_challenge_method | Yes | S256 or plain |
scope | No | Space-separated scopes |
state | No | Opaque value round-tripped to the redirect URI |
See Authorization Code Grant for PKCE generation and a complete request-to-callback walkthrough.
Token
POST /oauth2/token is O2ID's single token endpoint. Every grant is
requested against this same path, distinguished by grant_type:
grant_type | Purpose | Reference |
|---|---|---|
authorization_code | Exchange an authorization code for tokens | Authorization Code Grant |
refresh_token | Exchange a refresh token for a new token set | Refresh Token Grant |
client_credentials | Machine-to-machine token issuance for an application or an AI agent | Client Credentials Grant |
urn:ietf:params:oauth:grant-type:token-exchange | Downscope or delegate an existing token | Token Exchange |
UserInfo
GET /oauth2/userinfo returns OIDC claims for the user identified by an access
token. Pass the token as a Bearer credential:
curl -s http://localhost:8080/oauth2/userinfo \
-H "Authorization: Bearer $ACCESS_TOKEN"
Response:
{
"sub": "9f8e7d6c5b4a39281726354433221100",
"email": "alice@example.com",
"name": "Alice"
}
| Field | Description |
|---|---|
sub | The user's internal ID |
email | User's email address |
name | Display name (omitted when not set) |
An invalid or expired token returns 401 Unauthorized with a
WWW-Authenticate: Bearer challenge.
Token Introspection
O2ID's access tokens are opaque random strings, not JWTs: there is no
header or payload to decode, and the token functions only as a lookup key
into server-side storage. Token introspection
(POST /oauth2/introspect, defined by
RFC 7662) is the mechanism for
determining what a given token carries — its subject, scopes, expiry, and
related fields.
Because introspection can reveal information about any token, not only
one the caller itself holds, the endpoint is not open. There's no
admin/operator path: the only caller who can ever introspect a token is
an application's own client_credentials
token (see
Managing Resource Servers),
and even then only for a token whose aud names a
resource server that application is
already authorized
for. No separate scope grant is needed for this — the resource server
authorization itself is the permission. Introspecting any other token, or
one with no aud at all, returns the same {"active": false} as a token
that doesn't exist, per RFC 7662 §2.2's opacity requirement below.
curl -s -X POST http://localhost:8080/oauth2/introspect \
-H "Authorization: Bearer ${RESOURCE_SERVER_APP_TOKEN}" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "token=${ACCESS_TOKEN}"
{
"active": true,
"scope": "users:read",
"client_id": "client_abc123",
"sub": "9f8e7d6c5b4a39281726354433221100",
"token_type": "Bearer",
"exp": 1735689600,
"aud": "https://api.example.com"
}
aud is what makes this introspectable at all — a token minted with no
resource/audience (see Token Exchange)
can never be introspected by anyone, since there's no resource server
standing to check it against.
If the introspected token was minted by
actor-token delegation, the
response also includes RFC 8693 §4.1's act claim. sub continues to
identify the human; act.sub names the AI agent currently acting on
their behalf:
{
"active": true,
"scope": "users:read",
"client_id": "client_abc123",
"sub": "9f8e7d6c5b4a39281726354433221100",
"token_type": "Bearer",
"exp": 1735689600,
"aud": "https://api.example.com",
"act": { "sub": "agent_e241a1c4447bdc04d755a707dd0dec76" }
}
Per RFC 7662 §2.2, an unknown, expired, or otherwise malformed token is
never treated as an error: the response is always 200 OK with exactly
{"active": false} and no additional fields. This prevents a caller from
distinguishing "this token does not exist" from "this token expired" by
response shape, and also makes introspection a suitable way to check
whether a token remains valid before use. Refresh tokens are not
supported by this endpoint; only access tokens can be introspected.
JWKS
GET /oauth2/jwks returns the public key used to verify ID token
signatures, as a JSON Web Key Set (RFC 7517):
curl -s http://localhost:8080/oauth2/jwks
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"kid": "o2id-1",
"alg": "RS256",
"n": "…",
"e": "AQAB"
}
]
}
O2ID signs ID tokens with a single RSA key pair, generated in memory each time the server starts. The key is not persisted across restarts: an ID token issued before a restart cannot be verified against the JWKS returned after one. This endpoint is what a relying party fetches to verify an ID token's signature independently of the token endpoint.
OIDC Discovery
GET /.well-known/openid-configuration returns the OIDC discovery
document, letting a relying party locate O2ID's other endpoints and
determine which capabilities are supported, without hardcoding paths:
curl -s http://localhost:8080/.well-known/openid-configuration
{
"issuer": "http://localhost:8080",
"authorization_endpoint": "http://localhost:8080/oauth2/authorize",
"token_endpoint": "http://localhost:8080/oauth2/token",
"userinfo_endpoint": "http://localhost:8080/oauth2/userinfo",
"jwks_uri": "http://localhost:8080/oauth2/jwks",
"response_types_supported": ["code"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"scopes_supported": ["openid", "email", "profile"],
"claims_supported": ["sub", "iss", "aud", "exp", "iat", "nonce", "email", "name"],
"code_challenge_methods_supported": ["S256", "plain"]
}
| Field | Description |
|---|---|
issuer | O2ID's issuer identifier, used as the iss claim in ID tokens |
authorization_endpoint, token_endpoint, userinfo_endpoint, jwks_uri | Absolute URLs for the endpoints described above |
response_types_supported | Always ["code"] — O2ID supports only the authorization-code response type |
subject_types_supported | Always ["public"] — the same sub value is returned to every client, not pairwise-unique per client |
id_token_signing_alg_values_supported | Always ["RS256"] |
scopes_supported | The OIDC scopes recognized by the authorization endpoint |
claims_supported | The claims O2ID may include in an ID token or the UserInfo response |
code_challenge_methods_supported | PKCE methods accepted at the authorization endpoint |
/oauth2/introspect is deliberately absent from this document today: RFC
8414 defines introspection_endpoint as an optional field, and O2ID does
not yet advertise it here.