OAuth and OIDC endpoints
Base URL: https://pbac.example.com (configured via PBAC_OIDC_ISSUER).
Responses from /token, /introspect and /revoke carry Cache-Control: no-store and Pragma: no-cache. The redirects from /authorize back to the client carry Cache-Control: no-store.
Discovery
GET /.well-known/openid-configuration
Returns the OIDC discovery document. No authentication required.
Response. 200 OK, application/json:
| Field | Description |
|---|---|
issuer | Authorization server issuer URL |
authorization_endpoint | /authorize |
token_endpoint | /token |
registration_endpoint | /register |
jwks_uri | /.well-known/jwks.json |
userinfo_endpoint | /userinfo |
introspection_endpoint | /introspect |
resource_registration_endpoint | /resource_registration/resource |
permission_endpoint | /permission |
response_types_supported | ["code"] |
id_token_signing_alg_values_supported | ["RS256"] |
grant_types_supported | authorization_code, client_credentials, refresh_token, urn:ietf:params:oauth:grant-type:token-exchange and urn:openid:params:grant-type:ciba |
subject_types_supported | ["public"] |
scopes_supported | ["openid", "profile", "email"], a fixed list |
claims_supported | sub, name, email, preferred_username and email_verified |
code_challenge_methods_supported | ["S256"] |
dpop_signing_alg_values_supported | Array of DPoP signing algorithms (e.g. ["ES256", "RS256"]) |
authorization_details_types_supported | The resource.types and rar.types keys of the resource type registry row |
client_id_metadata_document_supported | true |
revocation_endpoint | /revoke |
revocation_endpoint_auth_methods_supported | ["client_secret_basic", "client_secret_post"] |
token_endpoint_auth_methods_supported | ["client_secret_basic", "client_secret_post", "private_key_jwt", "none"] |
backchannel_authentication_endpoint | /bc-authorize |
backchannel_token_delivery_modes_supported | ["poll"] |
backchannel_user_code_parameter_supported | false |
curl https://pbac.example.com/.well-known/openid-configuration
GET /.well-known/oauth-authorization-server
RFC 8414 OAuth Authorization Server Metadata. Returns the same discovery document as the OIDC endpoint above.
curl https://pbac.example.com/.well-known/oauth-authorization-server
GET /.well-known/jwks.json
Returns the public JWK Set used to verify ID tokens (RS256). No authentication required.
Response. 200 OK, application/json:
{
"keys": [
{ "kty": "RSA", "kid": "...", "n": "...", "e": "AQAB" }
]
}
curl https://pbac.example.com/.well-known/jwks.json
Authorization
GET /authorize
Initiates the OAuth 2.1 / OIDC authorization code flow (RFC 6749 §4.1, OAuth 2.1). The AS redirects the user agent to the configured upstream IdP, then redirects back to the client's redirect_uri with a code (or an error).
PKCE is mandatory for public clients; strongly recommended for confidential clients.
response_type=token (implicit grant) and grant_type=password (ROPC) are not supported.
Query parameters:
| Parameter | Required | Description |
|---|---|---|
response_type | Yes | Must be code |
client_id | Yes | Registered client identifier |
redirect_uri | Yes | Must exactly match a registered redirect URI |
scope | No | Space-separated scopes (e.g., openid profile) |
state | No | Opaque value for CSRF protection |
nonce | No | Replay protection for ID tokens |
code_challenge | No* | PKCE challenge (Base64url-encoded SHA-256 of verifier); required for public clients |
code_challenge_method | No | plain is refused with invalid_request. Any other value, or none, is checked as S256 |
resource | No | Resource indicator (RFC 8707) |
resource_types | No | Space-separated resource type URIs |
authorization_details | No | JSON array of RAR objects (RFC 9396). When present it decides the grant, and resource and scope do not |
acr_values | No | Requested authentication context class reference |
prompt | No | none, login, or consent |
login_hint | No | Hint for the upstream IdP |
Success redirect. 302 Found to redirect_uri?code=<code>&state=<state>
Error redirect. 302 Found to redirect_uri?error=<code>&error_description=<desc>&state=<state>
Client ID Metadata Document (CIMD)
When client_id is a URL (starts with https://), the AS interprets it as a Client ID Metadata Document per draft-ietf-oauth-client-id-metadata-document. This is the mechanism used by the MCP Authorization Specification for zero-config client registration.
Behaviour:
- The AS fetches the JSON document at the
client_idURL. - It validates that the document's
client_idfield matches the URL exactly. - It validates that all
redirect_urisin the request are present in the document'sredirect_uris. - A public client record is created on the fly with registration type
CIMD. - What the client may do comes from the client entitlement bindings for
CIMDclients, described in Clients and access.
URL constraints: The URL must have a path other than /, and must not contain a fragment (#) or a dot segment (. or ..). An http:// URL is accepted only when PBAC_CIMD_ALLOW_INSECURE_LOCALHOST is true, and only for the hosts in PBAC_CIMD_ALLOWED_INSECURE_HOSTS (default localhost,127.0.0.1,[::1]).
# Browser-initiated; shown as an example URL
curl -G https://pbac.example.com/authorize \
--data-urlencode "response_type=code" \
--data-urlencode "client_id=my-app" \
--data-urlencode "redirect_uri=https://app.example.com/callback" \
--data-urlencode "scope=openid profile" \
--data-urlencode "state=random-state" \
--data-urlencode "code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM" \
--data-urlencode "code_challenge_method=S256"
Backchannel authorization
POST /bc-authorize
Starts a CIBA (OpenID Connect Client-Initiated Backchannel Authentication) request. The user approves out of band, and the client polls /token with grant_type=urn:openid:params:grant-type:ciba and the returned auth_req_id. Only the poll delivery mode is supported.
Content-Type: application/x-www-form-urlencoded. The client authenticates the same way as at /token. A public client is refused with unauthorized_client.
Request parameters:
| Parameter | Required | Description |
|---|---|---|
scope | No | Requested scopes |
resource | No | Resource indicator (RFC 8707) |
authorization_details | No | RAR details as JSON (RFC 9396); a malformed value returns invalid_request |
binding_message | No | Text shown to the approver |
login_hint | Yes | Identifies the user to approve. Send exactly one hint. id_token_hint and login_hint_token are refused with invalid_request, "Only login_hint is supported". |
Response. 200 OK, application/json, Cache-Control: no-store:
| Field | Description |
|---|---|
auth_req_id | Handle to poll /token with |
expires_in | Seconds until the request expires |
interval | Minimum seconds between polls |
curl -s -X POST "$BASE_URL/bc-authorize" \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d scope=openid \
-d login_hint=alice@example.com
Token
POST /token
Issues access tokens. Content-Type: application/x-www-form-urlencoded.
Client authentication: Authorization: Basic <base64(client_id:secret)>, form parameters client_id/client_secret, or client_assertion (private_key_jwt).
Common parameters:
| Parameter | Required | Description |
|---|---|---|
grant_type | Yes | See grant types below |
client_id | No* | Required when not using Basic auth |
client_secret | No* | Required for confidential clients not using client_assertion |
client_assertion_type | No | urn:ietf:params:oauth:client-assertion-type:jwt-bearer |
client_assertion | No | JWT signed with client's private key |
Success response. 200 OK, application/json:
| Field | Description |
|---|---|
access_token | Opaque bearer token |
token_type | Bearer, or DPoP for a token bound to a DPoP key |
expires_in | Lifetime in seconds |
scope | Granted scope (may differ from requested) |
refresh_token | Present when refresh token was issued |
id_token | RS256-signed JWT, present for OIDC flows |
authorization_details | RAR details array (RFC 9396), when applicable |
issued_token_type | Token exchange responses only (RFC 8693) |
Error response. 400/401, application/json:
{ "error": "invalid_client", "error_description": "..." }
A DPoP proof that fails validation is ignored for a client that is not required to use DPoP, and the response is a Bearer token. Check token_type in the response. The AS does not return invalid_dpop_proof.
grant_type=client_credentials
For confidential clients only (OAuth 2.1 §4.2.2). Issues an access token without a user context.
| Parameter | Required | Description |
|---|---|---|
scope | No | Requested scopes |
resource | No | Resource indicator (RFC 8707) |
resource_types | No | Space-separated resource type URIs |
authorization_details | No | JSON array of RAR objects (RFC 9396) |
curl -s -X POST https://pbac.example.com/token \
-u "my-service:s3cr3t" \
-d "grant_type=client_credentials" \
-d "resource=https://api.example.com/fhir" \
-d "scope=read"
grant_type=authorization_code
Exchanges an authorization code for tokens. PKCE verifier must be supplied if a challenge was included in the authorization request.
| Parameter | Required | Description |
|---|---|---|
code | Yes | Authorization code from /authorize redirect |
redirect_uri | Yes | Must match the URI used in the authorization request |
code_verifier | No* | Required when PKCE was used |
resource | No | Resource indicator (RFC 8707) |
resource_types | No | Space-separated resource type URIs |
curl -s -X POST https://pbac.example.com/token \
-u "my-app:s3cr3t" \
-d "grant_type=authorization_code" \
-d "code=SplxlOBeZQQYbYS6WxSbIA" \
-d "redirect_uri=https://app.example.com/callback" \
-d "code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
grant_type=refresh_token
Exchanges a refresh token for a new access token.
| Parameter | Required | Description |
|---|---|---|
refresh_token | Yes | Refresh token from a previous token response |
scope | No | Requested scopes (subset of original) |
resource | No | Resource indicator (RFC 8707) |
curl -s -X POST https://pbac.example.com/token \
-u "my-app:s3cr3t" \
-d "grant_type=refresh_token" \
-d "refresh_token=tGzv3JOkF0XG5Qx2TlKWIA"
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
RFC 8693 Token Exchange. Exchanges an existing token for a new access token with a different audience or scope.
| Parameter | Required | Description |
|---|---|---|
subject_token | Yes | Token to exchange |
subject_token_type | Yes | Type URI (see supported types below) |
requested_token_type | No | Desired output token type |
audience | No | Intended audience for the new token |
scope | No | Requested scopes |
resource | No | Resource indicator (RFC 8707) |
resource_types | No | Space-separated resource type URIs |
authorization_details | No | JSON array of RAR objects (RFC 9396) |
Supported subject_token_type values:
| Type URI | Description |
|---|---|
urn:ietf:params:oauth:token-type:access_token | Exchange an access token issued by this AS. The AS looks up the token. On 0.65.0 policy compares the request with the subject token only when the subject token names a resource, so an exchange can carry scopes the original lacks (known issue). |
urn:ietf:params:oauth:token-type:id_token | Exchange an id_token from a trusted external issuer. The AS verifies the JWT signature against the issuer's JWKS and extracts subject claims. The issuer's iss must be in trusted_issuers. |
Example, access_token exchange (delegation):
curl -s -X POST https://pbac.example.com/token \
-u "my-service:s3cr3t" \
-d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d "subject_token=<access_token>" \
-d "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
-d "scope=read"
Example, id_token exchange (cross-domain identity):
curl -s -X POST https://pbac.example.com/token \
-u "my-service:s3cr3t" \
-d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d "subject_token=<id_token_jwt>" \
-d "subject_token_type=urn:ietf:params:oauth:token-type:id_token"
Error responses for id_token exchange:
| Error | Description |
|---|---|
invalid_grant, "Subject token missing iss claim" | The id_token JWT has no iss claim |
invalid_grant, "Subject token missing sub claim" | The id_token JWT has no sub claim |
invalid_grant, "Subject token expired" | The id_token's exp is in the past |
invalid_grant, "Subject token signature verification failed" | JWT signature does not match the issuer's JWKS |
invalid_grant, "Unable to verify subject token issuer" | The issuer's JWKS endpoint is unreachable |
invalid_grant, "Subject token issuer is not trusted" | The id_token's iss is not in trusted_issuers. The server refuses it before it fetches the issuer's keys and before policy runs. |
Introspection
POST /introspect
RFC 7662 Token Introspection. Re-evaluates the token against OPA policy and returns its current active state and claims. Callers must be authorized resource servers.
Content-Type: application/x-www-form-urlencoded.
Authentication (one of):
Authorization: Bearer <token>, where the bearer token hasintrospectionoruma_protectionscopeAuthorization: Basic <base64(client_id:secret)>, where the client is entitled to introspectclient_assertionform parameters, forprivate_key_jwt
Request parameters:
| Parameter | Required | Description |
|---|---|---|
token | Yes | The token to introspect |
token_type_hint | No | access_token or refresh_token |
context | No | JSON object with RS-supplied context (e.g., resource_type, resource, scopes) merged into OPA input. resource_type is one type URI, not a list. See Introspection for the keys the gateway sends |
Response. 200 OK, application/json:
If the token is unknown, expired or revoked, the response is {"active": false, "inactive_reason": "invalid_token"}.
| Field | Description |
|---|---|
active | true if the token is valid and policy allows access |
scope | Granted scopes |
client_id | Client that obtained the token |
sub | Subject identifier |
token_type | Bearer, or DPoP for a token bound with DPoP |
exp | Expiration (Unix timestamp) |
iat | Issued-at (Unix timestamp) |
resource | Resource indicator (RFC 8707) |
resource_types | Array of granted resource type URIs |
authorization_details | RAR details array (RFC 9396) |
obligations | Policy-defined obligations map |
extensions | Additional PDP output claims |
JIT single-use tokens (flagged jit_single_use in PDP output) are deleted after the first successful introspection.
# Bearer auth
curl -s -X POST https://pbac.example.com/introspect \
-H "Authorization: Bearer <rs-access-token>" \
-d "token=<token-to-introspect>"
# With RS-supplied context
curl -s -X POST https://pbac.example.com/introspect \
-H "Authorization: Bearer <rs-access-token>" \
-d "token=<token-to-introspect>" \
--data-urlencode 'context={"resource_type":"urn:example:orders"}'
Revocation
POST /revoke
RFC 7009 token revocation. The client authenticates the same way as at /token.
- A client revokes only tokens that were issued to it. The response is
200 OKwith an empty body. - A wrong client secret returns
401withinvalid_client. - A token issued to another confidential client is left active, and the response is still
200. - A request with no client credentials revokes a token only when it was issued to a public client, because holding the token is the proof. Otherwise the token stays active and the response is
200. - An unknown token, or a request with no
token, returns200.
Content-Type: application/x-www-form-urlencoded.
| Parameter | Required | Description |
|---|---|---|
token | Yes | The access or refresh token to revoke |
token_type_hint | No | access_token or refresh_token |
curl -s -X POST "$BASE_URL/revoke" \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d token="$TOKEN"
Registration
POST /register
RFC 7591 Dynamic Client Registration. Issues a client_id (and optionally a client_secret) for the submitted client metadata.
Two registration modes are supported:
- Software-statement DCR. The request includes
software_statement(a signed JWT). OPA evaluates the statement to determine what grants and resources the client is entitled to. - Plain DCR. The request omits
software_statement. Allowed whenenabledistruein thepbac.operator.controls.public_client_policyrow, which a new instance ships. Both public clients (token_endpoint_auth_method: none) and confidential clients are supported. Requested grant types are checked againstdcr.allowed_grant_typesin the same row.
Content-Type: application/json. No authentication required (policy-gated).
Request body:
| Field | Required | Description |
|---|---|---|
redirect_uris | Yes | Array of allowed redirect URIs |
token_endpoint_auth_method | No | none, client_secret_post, client_secret_basic, or private_key_jwt (default: client_secret_basic) |
grant_types | No | Array of grant types the client intends to use |
response_types | No | Array of response types |
scope | No | Requested default scope |
client_name | No | Human-readable name |
client_uri | No | Home page URI |
logo_uri | No | Logo URI |
contacts | No | Array of contact email addresses |
tos_uri | No | Terms of service URI |
policy_uri | No | Privacy policy URI |
jwks_uri | No | URI of the client's JWK Set. With private_key_jwt, registration refuses a scheme other than HTTPS. With other methods the scheme is not checked |
jwks | No | Inline JWK Set (alternative to jwks_uri) |
software_id | No | Identifier of the software package, up to 256 characters |
software_version | No | Version string |
software_statement | No | Signed JWT asserting client metadata (RFC 7591 §2.3) |
dpop_bound_access_tokens | No | true to require DPoP-bound tokens for this client |
Response. 201 Created, application/json:
| Field | Description |
|---|---|
client_id | Assigned client identifier |
client_secret | Client secret (confidential clients only) |
client_id_issued_at | Unix timestamp of registration |
client_secret_expires_at | 0 (non-expiring) |
redirect_uris | Registered redirect URIs |
grant_types | Registered grant types |
token_endpoint_auth_method | Registered auth method |
# With software statement (federated trust)
curl -s -X POST https://pbac.example.com/register \
-H "Content-Type: application/json" \
-d '{
"redirect_uris": ["https://app.example.com/callback"],
"client_name": "My Application",
"token_endpoint_auth_method": "client_secret_basic",
"software_statement": "eyJ..."
}'
# Plain DCR, public client
curl -s -X POST https://pbac.example.com/register \
-H "Content-Type: application/json" \
-d '{
"redirect_uris": ["https://app.example.com/callback"],
"client_name": "My Public App",
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"]
}'
UserInfo
GET /userinfo (also POST)
OIDC UserInfo endpoint. Returns claims for the authenticated user. Requires a valid access token with openid scope.
Authentication: Authorization: Bearer <access-token>
Response. 200 OK, application/json:
{
"sub": "user-123",
"name": "Jane Smith",
"email": "jane@example.com",
"preferred_username": "jsmith",
"email_verified": true
}
Error. 401 Unauthorized with WWW-Authenticate: Bearer error="invalid_token".
curl -s https://pbac.example.com/userinfo \
-H "Authorization: Bearer <access-token>"
UMA 2.0
POST /permission
UMA 2.0 Permission endpoint (UMA §4). A resource server posts a permission request and receives a permission ticket. No grant at /token redeems a ticket. A request with grant_type=urn:ietf:params:oauth:grant-type:uma-ticket returns 400 with unsupported_grant_type, so the AS issues no RPT.
Authentication: Authorization: Bearer <PAT>, a token with the uma_protection or introspection scope.
Content-Type: application/json.
Request body:
| Field | Required | Description |
|---|---|---|
resource_id | Yes | UMA resource identifier |
resource_scopes | Yes | Array of requested scope strings |
Response. 201 Created, application/json:
{ "ticket": "<permission-ticket>" }
curl -s -X POST https://pbac.example.com/permission \
-H "Authorization: Bearer <pat>" \
-H "Content-Type: application/json" \
-d '{"resource_id": "res-abc123", "resource_scopes": ["read"]}'
POST /resource_registration/resource
UMA 2.0 Resource Registration API (UMA §3.2). Creates a resource record owned by the authenticated subject.
Authentication: Authorization: Bearer <PAT>, a token with the uma_protection or introspection scope.
Content-Type: application/json.
Request body:
| Field | Required | Description |
|---|---|---|
name | No | Human-readable resource name |
type | No | Resource type URI |
resource_scopes | No | Array of applicable scope strings |
Response. 201 Created, application/json:
{ "_id": "<resource-id>" }
Location header points to the new resource.
curl -s -X POST https://pbac.example.com/resource_registration/resource \
-H "Authorization: Bearer <pat>" \
-H "Content-Type: application/json" \
-d '{"name": "Patient Record 42", "type": "https://example.com/Patient", "resource_scopes": ["read", "write"]}'
GET /resource_registration/resource
Lists resource IDs owned by the authenticated subject.
Response. 200 OK: JSON array of resource ID strings.
GET /resource_registration/resource/{resourceId}
Returns the resource record.
Response. 200 OK: { "_id", "name", "type", "resource_scopes" }
PUT /resource_registration/resource/{resourceId}
Updates a resource record. Request body same as POST. Response. 200 OK.
DELETE /resource_registration/resource/{resourceId}
Deletes a resource record. Response. 204 No Content or 404 Not Found.
Delegation
POST /delegation
Creates a delegation grant, which allows the authenticated subject to delegate access to a resource to another subject or client. Requires a Bearer token with delegation_management scope.
Content-Type: application/json.
Request body:
| Field | Required | Description |
|---|---|---|
grantee_type | No | subject or client |
grantee_subject_id | No | Subject ID of the grantee (when grantee_type=subject) |
grantee_client_id | No | Internal client PK of the grantee (when grantee_type=client) |
resource_id | Yes | UMA resource ID to delegate |
scopes | No | Array of scope strings to delegate |
expires_at | No | ISO 8601 expiry timestamp |
Response. 201 Created:
{ "delegation_id": "<id>" }
GET /delegation
Lists all delegations owned by the authenticated subject. Response. 200 OK: JSON array of delegation objects.
GET /delegation/{delegationId}
Returns a single delegation. Response. 200 OK or 404 Not Found.
PUT /delegation/{delegationId}
Updates a delegation (currently supports updating expires_at). Response. 200 OK or 404 Not Found.
DELETE /delegation/{delegationId}
Revokes a delegation. Response. 204 No Content or 404 Not Found.
AuthZEN
POST /access/v1/evaluation
AuthZEN Access Evaluation endpoint. Evaluates whether a subject may perform an action on a resource, delegating to OPA.
Authentication (one of):
Authorization: Bearer <token>, where the token has theauthzenscopeAuthorization: Basic <base64(client_id:secret)>, where the client is entitled toauthzen
The X-Admin-API-Key header is not accepted here. A request that carries only the admin key returns 401.
Content-Type: application/json.
Request body:
| Field | Required | Description |
|---|---|---|
subject | Yes | Object with type (string), id (string), properties (object) |
action | Yes | Object with name (string), properties (object) |
resource | Yes | Object with type (string), id (string), properties (object) |
context | No | Arbitrary context map passed to OPA |
Response. 200 OK, application/json:
| Field | Description |
|---|---|
decision | true if access is granted, false otherwise |
context | Optional map with additional PDP context (obligations, etc.) |
curl -s -X POST https://pbac.example.com/access/v1/evaluation \
-H "Authorization: Bearer <authzen-token>" \
-H "Content-Type: application/json" \
-d '{
"subject": { "type": "user", "id": "alice@example.com" },
"action": { "name": "read" },
"resource": { "type": "https://example.com/Patient", "id": "patient-42" }
}'
Next steps
- Admin API guide for keys, paging and admin errors.
- Get, refresh and revoke tokens for each grant with commands.
- Standards status for what each specification row is checked against.
Send feedback about this page