Skip to main content

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:

FieldDescription
issuerAuthorization 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_supportedauthorization_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_supportedsub, name, email, preferred_username and email_verified
code_challenge_methods_supported["S256"]
dpop_signing_alg_values_supportedArray of DPoP signing algorithms (e.g. ["ES256", "RS256"])
authorization_details_types_supportedThe resource.types and rar.types keys of the resource type registry row
client_id_metadata_document_supportedtrue
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_supportedfalse
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:

ParameterRequiredDescription
response_typeYesMust be code
client_idYesRegistered client identifier
redirect_uriYesMust exactly match a registered redirect URI
scopeNoSpace-separated scopes (e.g., openid profile)
stateNoOpaque value for CSRF protection
nonceNoReplay protection for ID tokens
code_challengeNo*PKCE challenge (Base64url-encoded SHA-256 of verifier); required for public clients
code_challenge_methodNoplain is refused with invalid_request. Any other value, or none, is checked as S256
resourceNoResource indicator (RFC 8707)
resource_typesNoSpace-separated resource type URIs
authorization_detailsNoJSON array of RAR objects (RFC 9396). When present it decides the grant, and resource and scope do not
acr_valuesNoRequested authentication context class reference
promptNonone, login, or consent
login_hintNoHint 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:

  1. The AS fetches the JSON document at the client_id URL.
  2. It validates that the document's client_id field matches the URL exactly.
  3. It validates that all redirect_uris in the request are present in the document's redirect_uris.
  4. A public client record is created on the fly with registration type CIMD.
  5. What the client may do comes from the client entitlement bindings for CIMD clients, 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:

ParameterRequiredDescription
scopeNoRequested scopes
resourceNoResource indicator (RFC 8707)
authorization_detailsNoRAR details as JSON (RFC 9396); a malformed value returns invalid_request
binding_messageNoText shown to the approver
login_hintYesIdentifies 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:

FieldDescription
auth_req_idHandle to poll /token with
expires_inSeconds until the request expires
intervalMinimum 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:

ParameterRequiredDescription
grant_typeYesSee grant types below
client_idNo*Required when not using Basic auth
client_secretNo*Required for confidential clients not using client_assertion
client_assertion_typeNourn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertionNoJWT signed with client's private key

Success response. 200 OK, application/json:

FieldDescription
access_tokenOpaque bearer token
token_typeBearer, or DPoP for a token bound to a DPoP key
expires_inLifetime in seconds
scopeGranted scope (may differ from requested)
refresh_tokenPresent when refresh token was issued
id_tokenRS256-signed JWT, present for OIDC flows
authorization_detailsRAR details array (RFC 9396), when applicable
issued_token_typeToken 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.

ParameterRequiredDescription
scopeNoRequested scopes
resourceNoResource indicator (RFC 8707)
resource_typesNoSpace-separated resource type URIs
authorization_detailsNoJSON 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.

ParameterRequiredDescription
codeYesAuthorization code from /authorize redirect
redirect_uriYesMust match the URI used in the authorization request
code_verifierNo*Required when PKCE was used
resourceNoResource indicator (RFC 8707)
resource_typesNoSpace-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.

ParameterRequiredDescription
refresh_tokenYesRefresh token from a previous token response
scopeNoRequested scopes (subset of original)
resourceNoResource 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.

ParameterRequiredDescription
subject_tokenYesToken to exchange
subject_token_typeYesType URI (see supported types below)
requested_token_typeNoDesired output token type
audienceNoIntended audience for the new token
scopeNoRequested scopes
resourceNoResource indicator (RFC 8707)
resource_typesNoSpace-separated resource type URIs
authorization_detailsNoJSON array of RAR objects (RFC 9396)

Supported subject_token_type values:

Type URIDescription
urn:ietf:params:oauth:token-type:access_tokenExchange 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_tokenExchange 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:

ErrorDescription
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 has introspection or uma_protection scope
  • Authorization: Basic <base64(client_id:secret)>, where the client is entitled to introspect
  • client_assertion form parameters, for private_key_jwt

Request parameters:

ParameterRequiredDescription
tokenYesThe token to introspect
token_type_hintNoaccess_token or refresh_token
contextNoJSON 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"}.

FieldDescription
activetrue if the token is valid and policy allows access
scopeGranted scopes
client_idClient that obtained the token
subSubject identifier
token_typeBearer, or DPoP for a token bound with DPoP
expExpiration (Unix timestamp)
iatIssued-at (Unix timestamp)
resourceResource indicator (RFC 8707)
resource_typesArray of granted resource type URIs
authorization_detailsRAR details array (RFC 9396)
obligationsPolicy-defined obligations map
extensionsAdditional 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 OK with an empty body.
  • A wrong client secret returns 401 with invalid_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, returns 200.

Content-Type: application/x-www-form-urlencoded.

ParameterRequiredDescription
tokenYesThe access or refresh token to revoke
token_type_hintNoaccess_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 when enabled is true in the pbac.operator.controls.public_client_policy row, which a new instance ships. Both public clients (token_endpoint_auth_method: none) and confidential clients are supported. Requested grant types are checked against dcr.allowed_grant_types in the same row.

Content-Type: application/json. No authentication required (policy-gated).

Request body:

FieldRequiredDescription
redirect_urisYesArray of allowed redirect URIs
token_endpoint_auth_methodNonone, client_secret_post, client_secret_basic, or private_key_jwt (default: client_secret_basic)
grant_typesNoArray of grant types the client intends to use
response_typesNoArray of response types
scopeNoRequested default scope
client_nameNoHuman-readable name
client_uriNoHome page URI
logo_uriNoLogo URI
contactsNoArray of contact email addresses
tos_uriNoTerms of service URI
policy_uriNoPrivacy policy URI
jwks_uriNoURI 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
jwksNoInline JWK Set (alternative to jwks_uri)
software_idNoIdentifier of the software package, up to 256 characters
software_versionNoVersion string
software_statementNoSigned JWT asserting client metadata (RFC 7591 §2.3)
dpop_bound_access_tokensNotrue to require DPoP-bound tokens for this client

Response. 201 Created, application/json:

FieldDescription
client_idAssigned client identifier
client_secretClient secret (confidential clients only)
client_id_issued_atUnix timestamp of registration
client_secret_expires_at0 (non-expiring)
redirect_urisRegistered redirect URIs
grant_typesRegistered grant types
token_endpoint_auth_methodRegistered 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:

FieldRequiredDescription
resource_idYesUMA resource identifier
resource_scopesYesArray 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:

FieldRequiredDescription
nameNoHuman-readable resource name
typeNoResource type URI
resource_scopesNoArray 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:

FieldRequiredDescription
grantee_typeNosubject or client
grantee_subject_idNoSubject ID of the grantee (when grantee_type=subject)
grantee_client_idNoInternal client PK of the grantee (when grantee_type=client)
resource_idYesUMA resource ID to delegate
scopesNoArray of scope strings to delegate
expires_atNoISO 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 the authzen scope
  • Authorization: Basic <base64(client_id:secret)>, where the client is entitled to authzen

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:

FieldRequiredDescription
subjectYesObject with type (string), id (string), properties (object)
actionYesObject with name (string), properties (object)
resourceYesObject with type (string), id (string), properties (object)
contextNoArbitrary context map passed to OPA

Response. 200 OK, application/json:

FieldDescription
decisiontrue if access is granted, false otherwise
contextOptional 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​