IDENTOS PBAC implements a broad set of open standards for OAuth 2.0, OpenID Connect, and User-Managed Access. Protocol compliance is handled at the endpoint layer, while authorization logic and policy enforcement are delegated to an external OPA policy engine — keeping the two concerns cleanly separated.
Compliance summary
| Standard | Description | Status |
|---|
| OAuth 2.0 / OIDC | OAuth 2.0 (RFC 6749), OAuth 2.1, OpenID Connect Core 1.0 | Implemented |
| RFC 9449 | OAuth 2.0 Demonstrating Proof of Possession (DPoP) | Implemented |
| RFC 7591 | OAuth 2.0 Dynamic Client Registration | Implemented — with and without software statements |
| RFC 8693 | OAuth 2.0 Token Exchange | Partial — basic exchange done; actor token planned |
| RFC 8707 | Resource Indicators for OAuth 2.0 | Implemented |
| RFC 9396 | OAuth 2.0 Rich Authorization Requests (RAR) | Fully implemented |
| draft-ietf-oauth-client-id-metadata-document | Client ID Metadata Document (CIMD) | Implemented |
| UMA 2.0 | User-Managed Access 2.0 Grant | Partial — resource reg, delegation done; RPT flow planned |
| OIDC / UserInfo | OpenID Connect Core 1.0 UserInfo + Discovery detail | Fully implemented |
| MCP Authorization Specification (draft) | OAuth 2.1 profile for Model Context Protocol | Compliant — see below |
All endpoints follow the OAuth 2.0 framework (RFC 6749/6750) and are extended with the standards above. Policy enforcement at each endpoint is handled by OPA, keeping protocol compliance separate from authorization logic.
OAuth 2.0 and OpenID Connect
This section describes the OAuth 2.0 and OpenID Connect Core 1.0 compliance posture of the PBAC Authorization Server, covering supported grant types, PKCE enforcement, token handling, discovery metadata, and the OAuth 2.1 hardening requirements.
References:
OAuth 2.0 / OAuth 2.1
Supported Grant Types
| Grant Type | Value | Status | Notes |
|---|
| Authorization Code | authorization_code | Implemented | PKCE required per OAuth 2.1 |
| Client Credentials | client_credentials | Implemented | Confidential clients only (§4.2.2) |
| Refresh Token | refresh_token | Implemented | |
| Token Exchange | urn:ietf:params:oauth:grant-type:token-exchange | Implemented | RFC 8693; endpoint wired, policy-evaluated |
| Implicit | response_type=token | Removed | Rejected per OAuth 2.1 §1.9 |
| Resource Owner Password | password | Removed | Rejected per OAuth 2.1 §1.9 |
Token Types
| Type | Status | Notes |
|---|
| Bearer tokens | Implemented | RFC 6750; Authorization: Bearer |
| DPoP | Implemented | RFC 9449; proof validation at token endpoint, cnf.jkt at introspection, policy-driven enforcement via OPA |
Client Authentication Methods
| Method | Status | Notes |
|---|
client_secret_basic | Implemented | HTTP Basic auth at token endpoint |
client_secret_post | Implemented | Credentials in POST body |
private_key_jwt | Implemented | RFC 7523; validated via ClientAssertionValidator |
none | Implemented | Public clients (PKCE required) |
PKCE (RFC 7636 / OAuth 2.1 §9.8)
PKCE is enforced for all authorization code flows. Public clients must always supply a code_challenge. Confidential clients may omit it, but the challenge is validated when present.
| Requirement | Status | Notes |
|---|
| S256 method supported | Implemented | code_challenge_method=S256 |
plain method | Accepted (not recommended) | Spec permits it; S256 is preferred |
Missing code_challenge for public clients | Rejected | invalid_request with PKCE-related description |
Wrong code_verifier rejected | Implemented | invalid_grant / invalid_request |
Authorization Code Lifecycle (OAuth 2.1 §4.1.2)
| Requirement | Status | Notes |
|---|
| Single-use codes | Implemented | Second use returns invalid_grant |
| Codes expire ≤ 10 minutes | Implemented | |
| Redirect URI exact matching (§3.1.2) | Implemented | String comparison; prefix/partial matches rejected |
Token Endpoint Behaviour
| Requirement | Status | Notes |
|---|
Content-Type: application/json response | Implemented | |
Cache-Control: no-store | Implemented | Set on all token responses |
Pragma: no-cache | Implemented | |
grant_type required | Implemented | invalid_request if absent |
Fragment component rejected in resource | Implemented | RFC 8707 §2 |
OpenID Connect Core 1.0
ID Tokens
ID tokens are issued in the authorization code flow when openid is included in the granted scopes.
| Requirement | Status | Notes |
|---|
iss, sub, aud, exp, iat required (§2) | Implemented | All claims present in issued tokens |
Signed with JWS; alg ≠ none (§2) | Implemented | RS256 via NimbusJwtEncoder |
aud contains client_id (§2) | Implemented | |
nonce echoed when present in request (§2, §3.1.2.1) | Implemented | Stored in AuthorizationCode, forwarded to ID token |
auth_time included when max_age requested (§3.1.2.1) | Implemented | |
Not issued when openid scope absent (§3.1.2.1) | Implemented | |
UserInfo Endpoint (§5.3)
| Requirement | Status | Notes |
|---|
GET /userinfo with Bearer token | Implemented | RFC 6750 |
POST /userinfo | Implemented | §5.3.1 |
sub claim required in response (§5.3.2) | Implemented | |
sub matches sub in ID token (§5.3.2) | Implemented | |
Content-Type: application/json (§5.3.2) | Implemented | |
401 with WWW-Authenticate for invalid/missing token (§5.3.3) | Implemented | |
Cache-Control: no-store on response | Implemented | |
profile scope → name claims (§5.4) | Implemented | name, preferred_username |
email scope → email claims (§5.4) | Implemented | email, email_verified |
Prompt Parameter (§3.1.2.1)
| Value | Status | Notes |
|---|
none | Implemented | Returns login_required redirect when no active session |
none combined with other values | Rejected | Returns invalid_request |
login, consent, select_account | Passed through to upstream IdP | |
Discovery (OpenID Connect Discovery 1.0)
The discovery document is served at GET /.well-known/openid-configuration. Its content is derived from the pbac.oidc.issuer configuration and live policy data.
| Metadata Field | Required | Status | Value |
|---|
issuer | Yes | Implemented | From pbac.oidc.issuer |
authorization_endpoint | Yes | Implemented | {issuer}/authorize |
token_endpoint | Yes | Implemented | {issuer}/token |
jwks_uri | Yes | Implemented | {issuer}/.well-known/jwks.json |
response_types_supported | Yes | Implemented | ["code"] |
subject_types_supported | Yes | Implemented | ["public"] |
id_token_signing_alg_values_supported | Yes | Implemented | ["RS256"]; none excluded |
userinfo_endpoint | Recommended | Implemented | {issuer}/userinfo |
scopes_supported | Recommended | Implemented | ["openid","profile","email"] |
claims_supported | Recommended | Implemented | ["sub","name","email","preferred_username","email_verified"] |
introspection_endpoint | Extension | Implemented | {issuer}/introspect |
authorization_details_types_supported | Extension (RAR) | Implemented | Derived from policy data at runtime |
JWKS Endpoint
- Path:
GET /.well-known/jwks.json
- Format: JWK Set (
keys array) with at least one RSA public key
- Key management: Auto-generated for dev; set
pbac.oidc.jwks-path for production
Compliance matrix
| Requirement | Spec Reference | Status | Notes |
|---|
| Authorization code flow | RFC 6749 §4.1 | Implemented | |
| Client credentials flow | RFC 6749 §4.4 | Implemented | |
| Refresh token flow | RFC 6749 §6 | Implemented | |
| Token exchange | RFC 8693 | Implemented | |
| Implicit grant removed | OAuth 2.1 §1.9 | Implemented | unsupported_response_type |
| ROPC grant removed | OAuth 2.1 §1.9 | Implemented | unsupported_grant_type |
| PKCE S256 mandatory (public clients) | OAuth 2.1 §9.8 | Implemented | |
| PKCE S256 supported (confidential clients) | RFC 7636 | Implemented | |
| Exact redirect URI matching | OAuth 2.1 §3.1.2 | Implemented | |
| Authorization codes single-use | OAuth 2.1 §4.1.2 | Implemented | |
| Authorization codes ≤ 10 min expiry | OAuth 2.1 §4.1.2 | Implemented | |
| Client credentials: confidential only | OAuth 2.1 §4.2.2 | Implemented | |
client_secret_basic auth | RFC 6749 §2.3.1 | Implemented | |
private_key_jwt auth | RFC 7523 | Implemented | |
| Bearer token usage | RFC 6750 | Implemented | |
Cache-Control: no-store on token responses | RFC 6749 §5.1 | Implemented | |
| OIDC ID token required claims (iss/sub/aud/exp/iat) | OIDC Core §2 | Implemented | |
| ID token signed RS256 | OIDC Core §2 | Implemented | |
| nonce echoed in ID token | OIDC Core §2 | Implemented | |
| auth_time when max_age requested | OIDC Core §3.1.2.1 | Implemented | |
| prompt=none handling | OIDC Core §3.1.2.1 | Implemented | |
| UserInfo sub claim | OIDC Core §5.3.2 | Implemented | |
| UserInfo sub matches ID token sub | OIDC Core §5.3.2 | Implemented | |
| OIDC Discovery document | OIDC Discovery 1.0 | Implemented | |
| JWKS endpoint | OIDC Discovery 1.0 | Implemented | |
| DPoP sender-constrained tokens | RFC 9449 | Implemented | Proof validation at /token; cnf.jkt in introspection; policy-driven enforcement |
| Refresh token rotation (public clients) | OAuth 2.1 §4.3.1 | Implemented | New refresh token issued on each use; previous token invalidated |
| Pairwise subject identifiers | OIDC Core §8.1 | Not implemented | Public subject type only |
ID token at_hash | OIDC Core §3.3.2.11 | Not implemented | Optional for code flow |
Roadmap
- Pairwise subject identifiers (OIDC Core §8.1)
at_hash in ID token (optional per spec)
- Initial access token for protected registration (RFC 7591)
RFC 7591: Dynamic Client Registration
OAuth 2.0 Dynamic Client Registration Protocol compliance for the /register endpoint.
Reference: RFC 7591
Endpoint and protocol
| Requirement | Status | Implementation |
|---|
| POST to registration endpoint | Done | POST /register |
| Content-Type: application/json | Done | Spring MVC default |
| Accept: application/json | Done | Spring MVC default |
| TLS for transport | N/A | Deployment concern |
| Field | Required | Status | Notes |
|---|
| redirect_uris | Y (redirect flows) | Done | @NotEmpty validation |
| token_endpoint_auth_method | N | Done | none, client_secret_post, client_secret_basic, private_key_jwt |
| grant_types | N | Done | Default: authorization_code, refresh_token |
| response_types | N | Done | Default: code |
| scope | N | Done | |
| client_name | N | Done | |
| client_uri | N | Done | |
| logo_uri | N | Done | |
| contacts | N | Done | |
| tos_uri | N | Done | |
| policy_uri | N | Done | |
| jwks_uri | N | Done | |
| jwks | N | Done | |
| software_id | N | Done | |
| software_version | N | Done | |
| software_statement | N | Done | JWT string; signature verification configurable via PBAC_SOFTWARE_STATEMENT_VERIFY |
Response (201 Created)
| Field | Required | Status | Notes |
|---|
| client_id | Y | Done | AS-assigned UUID |
| client_secret | If confidential | Done | Random 32-char; hashed in DB |
| client_id_issued_at | N | Done | Unix timestamp |
| client_secret_expires_at | If secret issued | Done | 0 = never expires |
| All registered metadata | Y | Done | Echoed in response |
Error response (400)
| Error Code | Status | When |
|---|
| invalid_redirect_uri | Done | Validation (future) |
| invalid_client_metadata | Done | Validation, missing_explicit_declarations |
| invalid_software_statement | Done | Untrusted issuer, malformed JWT |
| unapproved_software_statement | Done | Denylist, policy deny |
Software statement
| Requirement | Status | Notes |
|---|
| JWT format | Done | header.payload.signature |
| iss claim | Done | Used for trust check (OPA) |
| Signature verification | Done | Configurable via PBAC_SOFTWARE_STATEMENT_VERIFY (default: false); when enabled, verifies via issuer JWKS URI |
Test references
| Scenario | Test |
|---|
| Valid RS registration | test_register_rs_allowed |
| Valid client registration | test_register_client_allowed |
| Denylist deny | test_register_denied_denylist |
| Step-up | test_register_step_up |
| Missing redirect_uris | test_register_missing_redirect_uris_returns_400 |
| Invalid metadata | test_register_invalid_token_endpoint_auth_method_returns_400 |
| Private key JWT registration | test_register_private_key_jwt_client (integration-tests/test_private_key_jwt.py) |
Plain DCR (no software statement)
When public_client_policy.enabled: true is set in policy data, clients may register without a software_statement. Grant types are validated against public_client_policy.dcr.allowed_grant_types. Both public (token_endpoint_auth_method: none) and confidential clients are supported.
Open items
redirect_uri format validation (RFC 6749)
- Initial access token support (protected registration)
RFC 8693: Token Exchange
OAuth 2.0 Token Exchange compliance for the /token endpoint.
Reference: RFC 8693
Endpoint and protocol
| Requirement | Status | Implementation |
|---|
Grant type urn:ietf:params:oauth:grant-type:token-exchange | Done | POST /token routes to token exchange flow |
| Confidential client required | Done | Public clients rejected |
| TLS for transport | N/A | Deployment concern |
Request parameters (Section 2.1)
| Parameter | Required | Status | Notes |
|---|
grant_type | Y | Done | Must be urn:ietf:params:oauth:grant-type:token-exchange |
subject_token | Y | Done | Validated against issued tokens; must be active and unexpired |
subject_token_type | Y | Done | Only urn:ietf:params:oauth:token-type:access_token supported |
actor_token | N | Planned | Not yet accepted; needed for delegation chain scenarios |
actor_token_type | N | Planned | Paired with actor_token |
resource | N | Done | Resolved to resource type for policy evaluation |
audience | N | Partial | Accepted but not enforced in policy |
scope | N | Done | Validated; must be subset of subject token scopes |
requested_token_type | N | Partial | Accepted and echoed in response; not validated against AS capabilities |
Response (Section 2.2.1)
| Field | Required | Status | Notes |
|---|
access_token | Y | Done | Newly issued token |
token_type | Y | Done | Bearer |
expires_in | Recommended | Done | Included in response |
issued_token_type | Y | Done | Echoed from request or defaulted |
scope | Conditional | Done | Included when granted scopes differ from request |
refresh_token | N | N/A | Not issued (correct per spec — SHOULD NOT for temporary credentials) |
Scope narrowing
Scope narrowing is enforced by OPA policy. When a token exchange is requested:
- The subject token's granted scopes are extracted.
- The requested scopes must be a subset of the subject token's scopes.
- If any requested scope exceeds what the subject token grants, the request is denied.
This ensures each step in a delegation chain can only maintain or narrow access — never expand it.
Status: Done
Act claim (Section 4.1)
| Requirement | Status | Notes |
|---|
act claim in issued token identifying the actor | Planned | Requires actor_token support |
Nested act for chained delegation | Planned | Depends on act claim implementation |
The act claim enables audit trails showing who acted on whose behalf. This is a key requirement for agentic delegation scenarios.
Error handling (Section 2.2.2)
| Error Code | Status | When |
|---|
invalid_grant | Done | Subject token not found, expired, or invalid type |
invalid_request | Done | Missing required parameters |
invalid_token | Planned | Spec-specific error for token validation failures |
invalid_target | Planned | Spec-specific error for unreachable resource/audience |
Currently, token validation failures return invalid_grant. Spec-aligned error codes (invalid_token, invalid_target) are planned.
Subject token types (Section 3)
| Token Type URI | Status |
|---|
urn:ietf:params:oauth:token-type:access_token | Done |
urn:ietf:params:oauth:token-type:refresh_token | Not supported |
urn:ietf:params:oauth:token-type:id_token | Not supported |
urn:ietf:params:oauth:token-type:saml1 | Not supported |
urn:ietf:params:oauth:token-type:saml2 | Not supported |
urn:ietf:params:oauth:token-type:jwt | Not supported |
Only access tokens are currently accepted as subject tokens. Additional types may be added based on deployment requirements.
Security considerations (Section 7)
| Requirement | Status | Notes |
|---|
| Subject token not invalidated by exchange | Done | Original token remains valid after exchange |
| Scope can only narrow, never expand | Done | OPA policy enforces subset check |
| Confidential client required | Done | Public clients rejected at token endpoint |
| Requirement | Status | Notes |
|---|
grant_types_supported includes token exchange | Planned | Not yet advertised in /.well-known/openid-configuration |
Open items
actor_token / actor_token_type parameter support and act claim generation
- Advertise token exchange in discovery metadata
- Spec-aligned error codes (
invalid_token, invalid_target)
- Audience enforcement in policy evaluation
- Validation of
requested_token_type against AS capabilities
RFC 8707: Resource Indicators
Reference: RFC 8707
IDENTOS PBAC supports RFC 8707 resource indicators via the resource parameter on authorization and token requests. The resource URI is resolved to a resource_type via database lookup and passed to OPA as input.request.resource and input.request.resolved_resource_type. When no DB match exists, callers can provide a resource_type hint parameter. Fragment components in the resource parameter are rejected per RFC 8707 §2.
RFC 9396: Rich Authorization Requests
OAuth 2.0 Rich Authorization Requests (RAR) compliance for the authorization and token endpoints.
Reference: RFC 9396
Overview
| Requirement | Status | Implementation |
|---|
| authorization_details = JSON array of objects | Done | Parse, validate in AuthorizeController, TokenController |
Each entry must have type (required) | Done | Validation on parse; reject with invalid_authorization_details |
| Mutual exclusivity with resource/scope | Done | Reject when both RAR and resource/scope present |
Authorization request (Section 3)
| Requirement | Status | Implementation |
|---|
| authorization_details as request parameter | Done | GET /authorize?authorization_details=[...] |
| JSON array of objects | Done | Jackson parse; validate structure |
| type required per entry | Done | Reject missing/invalid type |
| Mutual exclusivity | Done | invalid_request when both sent |
Error invalid_authorization_details (Sections 5, 8)
| Requirement | Status | Implementation |
|---|
| Use error code invalid_authorization_details | Done | TokenExceptionHandler, AuthorizeException |
| Unknown type | Done | oauth.rar.rego; reject type not in resource.types or rar.types |
| Invalid structure (missing type, wrong types) | Done | Controller validation |
| Unknown fields for known type | N/A | Policy may extend; baseline passes through |
Token request (Section 6)
| Requirement | Status | Implementation |
|---|
| authorization_details in token request | Done | POST /token with form param authorization_details |
| client_credentials: use form param | Done | TokenController parses authorization_details |
| authorization_code: use stored from code | Done | TokenService reads from AuthorizationCode |
| Mutual exclusivity | Done | invalid_request when both RAR and resource/scope |
Token response (Section 7)
| Requirement | Status | Implementation |
|---|
| MUST return authorization_details in token response | Done | TokenResponse.authorizationDetails |
| Return granted_authorization_details from PDP | Done | From oauth.rar.granted_authorization_details |
JWT access token (Section 9.1)
| Requirement | Status | Notes |
|---|
| authorization_details as JWT claim | N/A | Opaque tokens only; not implemented |
Introspection (Section 9.2)
| Requirement | Status | Implementation |
|---|
| Return authorization_details when AS included it | Done | IntrospectResponse; populated from pdp_output |
| Requirement | Status | Implementation |
|---|
| authorization_details_types_supported | Done | WellKnownController; union of resource.types keys and rar.types |
Policy layer
| Component | Purpose |
|---|
| oauth.rar.rego | Validates type, delegates entitlements for resource types, RAR-only types via rar.types |
| oauth.rar.ext | Extension point for custom deny (e.g. payment_initiation rules) |
| oauth.authorize.rego | Integrates RAR flow; mutual exclusivity; invalid_authorization_details |
| oauth.token.rego | Integrates RAR flow; granted_authorization_details in result |
Test references
| Scenario | Test |
|---|
| RAR client_credentials token | test_rar_client_credentials_token_returns_200 |
| Token response includes authorization_details | test_rar_token_response_includes_authorization_details |
| Introspect returns authorization_details | test_rar_introspect_returns_authorization_details |
| Mutual exclusivity (token) | test_rar_mutual_exclusivity_reject_both |
| Mutual exclusivity (authorize) | test_rar_authorize_mutual_exclusivity_reject_both |
| Unknown type → invalid_authorization_details | test_rar_unknown_type_returns_invalid_authorization_details |
| Resource type (urn:as:userinfo) with scopes | test_rar_resource_type_urn_as_userinfo_with_scopes |
| Resource type with locations | test_rar_resource_type_with_locations |
| Custom RAR-only type (payment_initiation) | test_rar_custom_rar_only_type_payment_initiation |
| RAR authorize full flow | test_rar_authorize_full_flow_code_then_token |
UMA 2.0
User-Managed Access (UMA) 2.0 Grant for OAuth 2.0 Authorization compliance.
References:
Resource registration (Section 3.2)
| Requirement | Status | Implementation |
|---|
| POST to create resource | Done | POST /resource_registration/resource |
| GET to read resource | Done | GET /resource_registration/resource/{id} |
| GET to list resources | Done | GET /resource_registration/resource |
| PUT to update resource | Done | PUT /resource_registration/resource/{id} |
| DELETE to remove resource | Done | DELETE /resource_registration/resource/{id} |
PAT required (Bearer with uma_protection) | Done | Validated on every request |
| Resource scopes ceiling enforcement | Done | Scopes validated against resource type definition |
| Owner tracking (user or client) | Done | User-owned via PAT sub claim; client-owned when no user context |
Protection API token (PAT)
| Requirement | Status | Notes |
|---|
PAT as Bearer token with uma_protection scope | Done | Required for resource registration and permission endpoints |
User PAT via authorization_code flow | Done | Carries sub claim for user-owned resources |
Client PAT via client_credentials flow | Done | No sub; used for client-managed resources |
Resource indicator urn:as:introspect | Done | Used in token request to obtain PAT |
Permission endpoint (Section 4)
| Requirement | Status | Notes |
|---|
| POST to create permission ticket | Done | POST /permission |
| PAT required | Done | Bearer with uma_protection scope |
| Returns 201 with opaque ticket | Done | Ticket stored with resource ID and requested scopes |
| Ticket expiry | Done | 5-minute TTL |
| Multiple permissions per request | Planned | Currently one resource per ticket |
Token endpoint — RPT issuance (Section 1, 4)
The full UMA grant flow requires the token endpoint to accept a permission ticket and issue a Requesting Party Token (RPT).
| Requirement | Status | Notes |
|---|
Accept ticket parameter at /token | Planned | Token endpoint does not yet consume permission tickets |
| Validate ticket existence and expiry | Planned | Tickets are stored but not redeemed |
| Issue RPT with granted permissions | Planned | No distinct RPT token type; access tokens serve this role today |
| Consume ticket after issuance (one-time use) | Planned | No consumption logic |
| Policy evaluation before RPT issuance | Planned | OPA integration for ticket-based grants |
The permission ticket → RPT exchange at the token endpoint is not yet implemented. Permission tickets can be created but are not consumed to issue RPTs. This is the primary gap in the UMA 2.0 flow.
Introspection (Section 5)
| Requirement | Status | Notes |
|---|
Introspect with PAT (Bearer uma_protection) | Done | Validates caller PAT and re-evaluates policy |
| Introspect with Basic auth | Done | For clients with introspection scope entitlement |
| RS-supplied context (resource, scopes, action) | Done | Merged into PDP re-evaluation |
permissions array in response | Planned | UMA spec requires resource_id + scopes per permission |
Delegation
PBAC extends UMA with an explicit Delegation Store for time-bound, revocable grants.
| Requirement | Status | Notes |
|---|
| User → Client delegation | Done | Grantee is client_id; stored with resource, scopes, expiry |
| User → User delegation | Done | Grantee is subject_id; same non-expansive constraints |
| Non-expansive enforcement | Done | Delegated scopes must be subset of resource scopes |
| Revocation | Done | Soft-delete with revoked_at timestamp |
| Expiry enforcement | Done | Expired grants are not evaluated |
| Delegation evaluated at token time | Done | OPA receives delegation context in PDP input |
| Delegation evaluated at introspect time | Done | RS can pass delegation metadata in context |
The Delegation Store is managed via the /delegation endpoint (requires delegation_management scope).
| Requirement | Status | Notes |
|---|
resource_registration_endpoint | Done | Advertised in /.well-known/openid-configuration |
permission_endpoint | Done | Advertised in /.well-known/openid-configuration |
introspection_endpoint | Done | Advertised in /.well-known/openid-configuration |
uma_profiles_supported | Planned | Not yet advertised |
Summary
The UMA 2.0 implementation provides:
- Complete resource registration, permission ticket creation, PAT handling, delegation management, and discovery metadata.
- Planned permission ticket → RPT exchange at the token endpoint,
permissions array in introspection responses, and full UMA profile advertisement.
The delegation model goes beyond the UMA baseline by supporting explicit user-to-user and user-to-client grants with non-expansive enforcement, revocation, and time-bound expiry.
Open items
- Permission ticket consumption at the token endpoint (RPT issuance)
permissions array in introspection response per UMA Section 5
uma_profiles_supported in discovery metadata
- Multiple permissions per ticket request
- Expired ticket cleanup
OIDC UserInfo and Discovery
This section maps OIDC Core 1.0 and OpenID Connect Discovery 1.0 requirements to the PBAC Authorization Server implementation.
OIDC Core 1.0
UserInfo Endpoint (Section 5.3)
| Requirement | Implementation |
|---|
| Protected resource, Bearer token | GET/POST /userinfo; validates Authorization: Bearer |
| 5.3.1 UserInfo Request: GET/POST, Bearer in Authorization | Both HTTP methods supported; token extracted from header |
| 5.3.2 Successful Response: sub REQUIRED, JSON, Content-Type | sub always returned; Content-Type: application/json; Cache-Control: no-store |
| 5.3.3 Error Response: 401, WWW-Authenticate | 401 with WWW-Authenticate: Bearer error="invalid_token" for missing/invalid/expired token |
| 5.4 Scope Values: profile, email request claims | profile → name, preferred_username; email → email, email_verified |
Token Response (Section 3.1.3.3)
| Requirement | Implementation |
|---|
| id_token when openid scope | id_token included in authorization_code token response when openid in granted scopes |
ID Token (Section 2)
| Requirement | Implementation |
|---|
| iss, sub, aud, exp, iat | All required claims in signed JWT |
| Signed with JWS | RS256; signed via NimbusJwtEncoder |
| nonce (when in auth request) | Passed from AuthorizationCode to id_token |
| auth_time (optional) | Supported when provided |
Data Minimization
- SubjectUserInfo stores only:
name, email, preferred_username (plus subject_id, identity_provider_id)
- Claims returned only when corresponding scope is granted
Security
- Token validation:
AccessTokenRepository.findByTokenWithClientAndIdentityProvider; checks expiresAt
- Requires
openid in granted_scopes for UserInfo
- Requires
subjectId (user context); client_credentials tokens return 401
Cache-Control: no-store on UserInfo responses
OpenID Connect Discovery 1.0
| Discovery Section | Requirement | Implementation |
|---|
| 4.1 Well-Known URI | /.well-known/openid-configuration | GET returns JSON |
| issuer | Required | From pbac.oidc.issuer |
| authorization_endpoint | Required | {issuer}/authorize |
| token_endpoint | Required | {issuer}/token |
| jwks_uri | Required | {issuer}/.well-known/jwks.json |
| response_types_supported | Required | ["code"] |
| subject_types_supported | Required | ["public"] |
| id_token_signing_alg_values_supported | Required (RS256) | ["RS256"] |
| userinfo_endpoint | Recommended | {issuer}/userinfo |
| scopes_supported | Recommended | ["openid","profile","email"] |
| claims_supported | Recommended | ["sub","name","email","preferred_username","email_verified"] |
JWKS Endpoint
- Path:
/.well-known/jwks.json
- Purpose: Expose public RSA key(s) for ID token signature verification
- Format: JWK Set with
keys array; each key has kty, kid, n, e
- Key management: Auto-generated for dev; configurable via
pbac.oidc.jwks-path for production
MCP Authorization Specification
The MCP Authorization Specification (draft) defines an OAuth 2.1 profile for securing Model Context Protocol servers. PBAC implements all normative requirements.
Reference: MCP Authorization Specification
Compliance summary
| Requirement | MCP Obligation | Status | Notes |
|---|
| Authorization code grant with PKCE | MUST | Implemented | S256 enforced for public clients |
| Refresh token rotation | MUST (public clients, §4.3.1) | Implemented | New token issued on every refresh; previous invalidated |
| Dynamic Client Registration (RFC 7591) | MAY | Implemented | Plain DCR enabled via public_client_policy |
| Client ID Metadata Document (CIMD) | SHOULD | Implemented | URL-format client_id in /authorize triggers CIMD resolution |
| Protected Resource Metadata (RFC 9728) | SHOULD | Implemented | GET /.well-known/oauth-protected-resource on MCP gateway |
registration_endpoint in discovery | SHOULD | Implemented | Present in /.well-known/openid-configuration |
client_id_metadata_document_supported in discovery | SHOULD | Implemented | Set to true |
| Implicit grant removed | MUST NOT | Implemented | unsupported_response_type returned |
| Client credentials grant | N/A | Implemented | Available for server-to-server flows |
CIMD flow
When an MCP client presents a URL-format client_id at /authorize:
- The AS fetches the JSON document at that URL.
- It validates
client_id and redirect_uris against the request parameters.
- A transient public client is created with
registration_type: cimd.
granted_resources are synthesized from data.oauth.public_client_policy.cimd.granted_resources in policy data.
- The authorization code flow proceeds normally.
The MCP gateway uses CIMD so that any compliant MCP client can connect without pre-registration.
Plain DCR flow
When public_client_policy.enabled: true is set in policy data, MCP clients that support Dynamic Client Registration can register themselves before starting the authorization flow:
curl -s -X POST https://pbac.example.com/register \
-H "Content-Type: application/json" \
-d '{
"redirect_uris": ["http://localhost:19876/callback"],
"client_name": "Claude Code",
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"]
}'
The registered client_id can then be used in subsequent authorization requests.
Next steps