diff --git a/design/encrypted-access-tokens.md b/design/encrypted-access-tokens.md new file mode 100644 index 0000000..9f6abc4 --- /dev/null +++ b/design/encrypted-access-tokens.md @@ -0,0 +1,1407 @@ +# Native Encrypted Access Tokens + +* **Status**: Draft #1 +* **JIRA**: None +* **GitHub Issue**: [keycloak/keycloak#19464 - Encrypt Access and Refresh Tokens](https://github.com/keycloak/keycloak/issues/19464) +* **Discussion**: [keycloak/keycloak#51721 - Native encrypted access tokens with single and multi-recipient JWE](https://github.com/keycloak/keycloak/discussions/51721) + +## Motivation + +Keycloak currently issues signed access tokens, but does not provide native support for encrypting access tokens using JSON Web Encryption (JWE). + +There are deployments where access tokens contain sensitive information, including personally identifiable information, authorization attributes, roles, entitlements, internal identifiers, or other claims that should not be readable by intermediaries or by the OAuth client itself. + +This requirement has existed in the Keycloak community for several years. Previous implementation attempts and proofs of concept include: + +* [keycloak/keycloak#6796](https://github.com/keycloak/keycloak/pull/6796) +* [keycloak/keycloak#5779](https://github.com/keycloak/keycloak/pull/5779) +* [keycloak/keycloak#7341 - KEYCLOAK-10761 Access-Token Encryption Support](https://github.com/keycloak/keycloak/pull/7341) +* Existing feature request [keycloak/keycloak#19464](https://github.com/keycloak/keycloak/issues/19464) + +The objective of this proposal is to define a modern architecture for native encrypted access tokens that: + +* Preserves existing behavior by default; +* Supports standard single-recipient JWE; +* Evaluates multi-recipient JWE; +* Supports resource-server and gateway use cases; +* Allows Keycloak itself to decrypt tokens when required; +* Works across OAuth 2.0 and OpenID Connect flows; +* Does not require browser-based public clients to hold private JWE encryption keys; +* Integrates with Keycloak's existing JOSE infrastructure instead of adding grant-specific token transformations. + +## Goals + +The proposed feature should eventually support: + +* Signed access tokens without encryption, preserving the existing behavior. +* Signed-then-encrypted access tokens using Nested JOSE. +* Single-recipient JWE using Compact Serialization. +* JWE JSON Serialization. +* Multi-recipient encryption using General JWE JSON Serialization. +* Independent encryption keys for different recipients. +* Keycloak as an optional JWE recipient. +* Resource-aware and audience-aware recipient resolution. +* Public clients and SPAs that treat encrypted access tokens as opaque values. +* Confidential clients and BFF architectures. +* API gateways and resource servers capable of decrypting access tokens locally. +* End-to-end user-token propagation. +* Token Exchange-based delegation. +* Client Credentials / M2M scenarios. +* All Keycloak grant types that issue access tokens. +* All Keycloak endpoints that consume access tokens. +* Correct behavior for `at_hash`, DPoP, mTLS-bound tokens, introspection, revocation, Token Exchange, UMA, and other features involving access tokens. +* Administration through the Keycloak Admin Console. +* Backwards compatibility for existing clients. +* Reference implementations and quickstarts. + +## Non-Goals + +The initial implementation does not necessarily need to include all capabilities described by this proposal in a single pull request. + +In particular, the following may be delivered as follow-up work: + +* Refresh Token encryption. +* Multi-recipient JWE. +* JWE JSON Bearer transport. +* Dynamic Resource Server registration. +* Protected Resource Metadata based key discovery. +* Updates to `keycloak-js`. +* New quickstarts. +* Additional key-management integrations such as HSM or external KMS providers. + +The design should nevertheless consider these capabilities before public configuration or APIs are finalized. + +## Terminology + +### Requesting Client + +The OAuth client requesting an access token. + +Examples: + +* SPA +* Mobile application +* BFF +* Backend application +* Service account + +### Resource Server + +The API or protected resource that consumes the access token. + +The requesting client and the Resource Server are different OAuth roles and must not be treated as equivalent. + +For example: + +```text +Requesting client: + js-web + +Resource: + payments-api +``` + +### JWE Recipient + +An entity that possesses a private key capable of recovering the JWE Content Encryption Key (CEK). + +Examples: + +* Resource Server +* API Gateway +* BFF +* Keycloak itself + +A JWE recipient is also not necessarily equivalent to an OAuth audience. + +```text +audiences: + api-a + api-b + api-c + +JWE recipients: + keycloak + api-gateway +``` +May be a valid deployment where a gateway performs cryptographic enforcement for multiple APIs. + +## Relevant Standards + +The design should follow the existing OAuth, OpenID Connect, and JOSE specifications whenever possible. + +* RFC 6749 - OAuth 2.0 Authorization Framework + * Defines OAuth access tokens and intentionally leaves the internal access-token representation to the Authorization Server. + * Access tokens should therefore be treated by OAuth clients as opaque credentials unless a profile explicitly establishes otherwise. +* RFC 6750 - OAuth 2.0 Bearer Token Usage + * Defines Bearer token propagation, including: + ```html + Authorization: Bearer + ``` + * The Bearer credential syntax is relevant when evaluating how JWE JSON Serialization can be transported. +* RFC 7515 - JSON Web Signature (JWS) + * Defines JWS and Base64URL encoding. +* RFC 7516 - JSON Web Encryption (JWE) + * Defines: + * JWE Compact Serialization + * Flattened JWE JSON Serialization + * General JWE JSON Serialization + * Multiple JWE recipients + * JWE Compact Serialization supports one recipient. + * General JWE JSON Serialization supports multiple recipients. +* RFC 7517 - JSON Web Key (JWK) + * Defines JWK and JWK Set representations. +* RFC 7518 - JSON Web Algorithms (JWA) + * Defines JOSE algorithms including: + * RSA-OAEP + * RSA-OAEP-256 + * A256GCM + * The implementation should expose algorithms through Keycloak's existing cryptographic provider mechanisms rather than hard-code one combination. + * A useful initial interoperable profile is: + ```text + alg = RSA-OAEP-256 + enc = A256GCM + ``` +* RFC 7519 - JSON Web Token (JWT) + * Defines JWT. + * An important distinction is that JWT uses Compact JOSE Serialization. + * A JWE General JSON Serialization containing a signed JWT should therefore not itself be described as a JWT representation. + * Instead it is a JWE JSON object whose encrypted plaintext may be a signed JWT. +* RFC 8725 - JWT Best Current Practices + * Security requirements relevant to this feature include: + * Strict algorithm validation; + * Prevention of algorithm confusion; + * Validation of every cryptographic layer in nested tokens; + * Explicit typing where appropriate; + * Key separation; + * Appropriate input validation. + * Successful JWE decryption must never be considered sufficient authentication of the token. + * The inner JWS must still be verified. +* RFC 9068 - JWT Profile for OAuth 2.0 Access Tokens + * Defines a profile for JWT access tokens. + * Encrypted access tokens are compatible with this model when JWE Compact Serialization is used. + * The requesting OAuth client should not depend on being able to inspect access-token claims. +* RFC 8707 - Resource Indicators for OAuth 2.0 + * Defines the resource request parameter. + * This specification is especially relevant because an Authorization Server may need to know the intended Resource Server in order to encrypt an access token using the Resource Server's key. + ```text + resource=https://payments.example + ``` + Can participate in determining: + ```text + encryption recipient: + payments-api + ``` +* RFC 8693 - OAuth 2.0 Token Exchange + * Relevant for delegated architectures. + ![token-exchange-representation](img/encrypted-access-tokens/token-exchange.png) +* RFC 7591 - OAuth 2.0 Dynamic Client Registration + * Defines mechanisms including: + * jwks + * jwks_uri + * These mechanisms should be evaluated for publication of encryption public keys. +* RFC 8414 - OAuth 2.0 Authorization Server Metadata + * May provide a standards-compatible place for advertising supported access-token encryption capabilities. +* RFC 9728 - OAuth 2.0 Protected Resource Metadata + * Should be evaluated as a standards-based source for discovering Resource Server public keys. + * It does not itself define Access Token encryption negotiation or serialization. + * Using Resource Server metadata may be preferable to introducing Keycloak-specific resource-key discovery. +* RFC 9449 - Demonstrating Proof of Possession (DPoP) + * Encrypted access tokens must remain compatible with DPoP. + * JWE provides confidentiality. + * DPoP provides sender constraint. + * These are different security properties. +* RFC 8705 - OAuth 2.0 Mutual TLS + * Certificate-bound access tokens must continue working when access-token encryption is enabled. + +## Cryptographic Construction + +The proposed construction is signed-then-encrypted. +![crypto-construction-representation](img/encrypted-access-tokens/cryptographic-construction.png) + +```text +inner: + alg = RS256 + typ = at+jwt when RFC 9068 is enabled +outer: + alg = RSA-OAEP-256 + enc = A256GCM + cty = JWT +``` + +Signing and encryption key pairs should have separate purposes. + +``` +Keycloak signing key: + use = sig + +Resource encryption key: + use = enc +``` +A single key pair should not be reused for signing and encryption. + +## Existing Behavior + +Current Keycloak clients must remain unaffected. + +Default: Access Token Encryption = Disabled + +Only clients/resources explicitly configured for encrypted access tokens should receive an encrypted representation. + +With no Access Token encryption configuration, Keycloak should preserve the existing Access Token behavior exactly as it does today. + +## Single-Recipient JWE + +The initial standards-based implementation should support JWE Compact Serialization. + +``` +protected-header +. +encrypted-key +. +iv +. +ciphertext +. +authentication-tag +``` + +This mode is directly usable as: + +``` +Authorization: Bearer +``` + +Example architecture: +![Single recipient JWE example architecture](img/encrypted-access-tokens/single-recipient-JWE.png) + +The SPA does not need the Resource Server's private key. + +## Keycloak as Recipient + +There are cases where the Resource Server must decrypt a token locally while Keycloak must also be able to process the same encrypted access token. + +Examples include: + +* UserInfo +* Token Introspection +* Token Exchange +* UMA +* Admin APIs +* Account APIs +* Revocation +* Other endpoints accepting Bearer access tokens + +Sharing the same private key between Keycloak and every Resource Server is undesirable. + +A proposed solution is to optionally make Keycloak another JWE recipient. + +Example: + +![keycloak-JWE-recipient-example](img/encrypted-access-tokens/keycloak-JWE-recipient.png) + +Keycloak and the API retain independent private keys. + +This requires a multi-recipient JWE representation. + +Whether Keycloak must be a recipient for every encrypted access token should remain configurable. + +## Multi-Recipient JWE + +RFC 7516 General JWE JSON Serialization allows multiple recipients to decrypt the same protected plaintext. + +Example: +```json +{ + "protected": "...", + "recipients": [ + { + "header": { + "kid": "keycloak-enc-2026" + }, + "encrypted_key": "..." + }, + { + "header": { + "kid": "payments-api-enc-2026" + }, + "encrypted_key": "..." + } + ], + "iv": "...", + "ciphertext": "...", + "tag": "..." +} +``` +The ciphertext is encrypted once using the CEK. + +The CEK is then independently protected for each recipient. + +Each recipient retains its own private key. + +### Security Consideration + +Every JWE recipient gains access to the complete plaintext. + +Adding recipients therefore expands the confidentiality domain. + +Recipients must not automatically be derived from every token audience. + +## JWE JSON Bearer Transport + +JWE General JSON Serialization cannot be inserted directly into an RFC 6750 Bearer credential because raw JSON contains characters that are not valid in the Bearer token syntax. + +The proposal should therefore evaluate a transport encoding. + +One possible transport profile is: + +```text +BASE64URL( + UTF8( + JWE General JSON Serialization + ) +) +``` + +The OAuth response remains: + +``` +{ + "access_token": "eyJwcm90ZWN0ZWQiOi...", + "token_type": "Bearer", + "expires_in": 300 +} +``` + +And propagation remains: +```http +Authorization: Bearer eyJwcm90ZWN0ZWQiOi... +``` + +The recipient would perform: +``` +Bearer value --> Base64URL decode --> JWE General JSON --> +Select recipient --> Decrypt CEK --> Decrypt ciphertext --> +Verify nested JWS +``` + +### Important + +This outer Base64URL layer: +* Does not provide confidentiality; +* Does not provide integrity; +* Is only a transport encoding. + +The actual protection remains JWE. + +### Open Question + +BASE64URL(JWE JSON) is not an RFC 7516 serialization. + +It would be a Keycloak-defined transport profile. + +Maintainer feedback is required before adopting this representation as public API. + +## Transport Identification and Versioning + +Opaque OAuth access tokens should not normally require format detection by clients. + +However, Keycloak and participating gateways need a deterministic way to distinguish supported server-side token representations. + +A possible versioned transport form could be: + +``` +kc-jwe-json-v1. +``` + +Benefits: + +* Deterministic representation detection; +* Avoids "try Base64URL, then try JSON" parsing heuristics; +* Allows future transport evolution. + +Drawback: + +* Introduces a Keycloak-specific profile marker. + +### Open Question + +Should the transport be: + +``` +BASE64URL(JWE_JSON) +``` +or: +``` +kc-jwe-json-v1.BASE64URL(JWE_JSON) +``` +Or should a different standards-compatible solution be used? + +## Serialization Model + +Internally, serialization and transport should be represented separately. + +Proposed model: + +``` +serialization: + JWS_COMPACT + JWE_COMPACT + JWE_JSON_FLATTENED + JWE_JSON_GENERAL + +transport: + NATIVE + BASE64URL +``` + +Initial valid combinations could include: + +``` +JWS_COMPACT + NATIVE +JWE_COMPACT + NATIVE +JWE_JSON_FLATTENED + BASE64URL +JWE_JSON_GENERAL + BASE64URL +``` + +## Resource Server vs OAuth Client + +ID Token encryption and Access Token encryption have different recipient semantics. + +For an ID Token: +``` +recipient = OAuth/OIDC client +``` +For an Access Token: +``` +recipient = Resource Server +``` + +Therefore Access Token encryption must not simply duplicate the existing ID Token implementation and always use the requesting client's key. + +``` +SPA client: + js-web + +Resource: + payments-api + +JWE recipient: + payments-api +``` + +The SPA can transport the encrypted token without decrypting it. + +### Recipient Resolution + +The implementation should evaluate several recipient-resolution strategies: + +* REQUESTING_CLIENT +* RESOURCE +* AUDIENCE +* EXPLICIT + +The preferred model should be resource-aware. + +Example: + +``` +POST /token + +resource=https://payments.example +``` + +Could resolve: + +``` +Resource: + payments-api + +Encryption key: + kid=payments-api-enc-2026 +``` + +### Open Question + +Should access-token encryption policy primarily belong to: + +* The requesting client; +* The Resource Server; +* The realm; +* A combination with clearly defined precedence? + +A proposed precedence is: + +``` +Resource mandatory security policy --> Realm policy/default --> Client preference/default +``` + +A client should never be able to downgrade a Resource Server requirement. + +## Resource Server Key Discovery + +Possible sources include: + +* Resource Server JWKS URI +* Inline JWK Set +* X.509 certificate +* Keycloak-managed resource encryption key +* Protected Resource Metadata +* Explicit administration + +Private keys belonging to external Resource Servers should not need to be stored in Keycloak. + +Keycloak should normally only require: +* Resource Server public encryption key + +The Resource Server retains: +* Resource Server private encryption key + +### Open Question + +Should RFC 9728 Protected Resource Metadata be the preferred standards-based mechanism for discovering Resource Server encryption public keys, while Access Token encryption negotiation and serialization +remain defined separately by Keycloak/OAuth configuration? + +## Keycloak Realm Encryption Key + +When Keycloak is included as a recipient, Keycloak should use a dedicated encryption key pair. + +For example: + +``` +use = enc +kid = realm-access-token-enc-1 +alg = RSA-OAEP-256 +``` +This key must be independent of the realm signing key. + +Key rotation must support overlapping keys so previously issued tokens can remain decryptable during their lifetime. + +## Public SPA Scenario + +A public SPA does not need to decrypt the Access Token. + +Example: +![single-recipient-JWE-representation](img/encrypted-access-tokens/single-recipient-JWE.png) + +The SPA: +* Can receive the token: yes +* Can propagate the token: yes +* Can decrypt the token: no +* Needs a JWE private key: no + +The Resource Server decrypts and validates the token. + +This preserves public-client semantics. + +## SPA User Interface Claims + +Some browser applications currently inspect Access Token claims to determine: +* User name; +* Navigation; +* Visible pages; +* Feature flags; +* Roles used for presentation; +* User information. + +Encrypted access tokens should not force all presentation logic to become server-side. + +However, browser applications should not require access to Resource Server authorization claims. + +UI/identity information may instead be provided through: +* ID Token; +* UserInfo; +* Application-specific /me endpoint; +* BFF. + +Client-side checks can continue to control user experience. + +They must never replace Resource Server authorization. + +## ID Token and UserInfo Encryption + +Encryption policies for: + +* ID Token +* UserInfo +* Access Token + +Must remain independent. + +Their intended recipients are different. + +``` +ID Token: + OIDC client + +UserInfo: + OIDC client + +Access Token: + Resource Server +``` + +SPA Configuration example: +``` +ID Token: + * Signed + * Not encrypted + +UserInfo: + * JSON or signed + +Access Token: + * Encrypted +``` + +This allows the SPA to obtain identity/UI claims while keeping Resource Server authorization information confidential. + +Example BFF configuration: + +``` +ID Token: + - Optionally encrypted to BFF + +UserInfo: + - Optionally encrypted to BFF + +Access Token: + - Encrypted to target Resource Server +``` + +## BFF Scenario + +The browser does not need to receive OAuth tokens. + +The BFF can securely manage: +* ID Token +* Refresh Token +* Access Token +* Private cryptographic material when required + +Access Tokens for downstream APIs may still be independently encrypted for those Resource Servers. + +## End-to-End User Token Propagation + +Some architectures propagate the same end-user Access Token through multiple API layers. + +``` +SPA --> API-A --> API-B --> API-C +``` + +If every API must independently decrypt the same access token, General JWE JSON could include: + +```text +recipients: + Keycloak + API-A + API-B + API-C +``` + +* This is technically possible but should be used cautiously. +* Every recipient receives the same token plaintext and authority. +* Resource-specific tokens should generally be preferred where feasible. + +## Gateway-Based Propagation + +Another common architecture is: + +``` +SPA + | + v +API Gateway --> API-A + | + +--> API-B + | + +--> API-C +``` + +The access token could have: +``` +aud: + API-A + API-B + API-C +JWE recipients: + Keycloak + API Gateway +``` + +* The gateway decrypts and validates the token. +* Downstream identity propagation then becomes an application/gateway architecture decision. +* This can avoid creating an encryption recipient for every internal microservice. + +## Token Exchange Scenario + +A resource-specific delegated architecture may use RFC 8693. + +The resulting token can preserve the end-user identity while identifying the acting service. + +Example claims: + +``` +{ + "sub": "user-123", + "aud": "api-b", + "act": { + "sub": "api-a" + } +} +``` + +The exchanged token can then be encrypted specifically for API-B. +This provides better audience restriction and least privilege than propagating a broad token throughout the service graph. + +## Client Credentials / M2M Scenario + +Another architecture intentionally ends the user authorization context. + +``` +SPA + | + | user access token + v +API-A + +--- user authorization boundary --- + +API-A + | + | client_credentials + v +Keycloak + | + | M2M access token + v +API-B +``` + +The M2M token can independently use: +* Signed JWT; +* Compact JWE; +* Multi-recipient JWE. + +## Token Propagation Is Not a Keycloak Policy + +Keycloak should not introduce configuration such as: propagation = TOKEN_EXCHANGE + +Because applications determine how tokens are propagated. + +Keycloak should instead provide the building blocks: +* Encrypted access tokens; +* Resource targeting; +* Audience targeting; +* Token Exchange; +* Client Credentials; +* Recipient resolution. + +## Access Token Issuance Pipeline + +Encryption should be centralized rather than implemented separately in individual grant types. +This is important to ensure consistent behavior across all grant types. + +## Grant Coverage + +Integration testing should cover at least: + +* Authorization Code +* Authorization Code + PKCE +* Client Credentials +* Direct Grant / Password +* Refresh Token +* Device Authorization +* CIBA +* Token Exchange +* UMA / Permission Grant +* JWT Authorization Grant +* Pre-Authorized Code where applicable + +Encryption should be applied by the common token issuance pipeline. + +## Access Token Consumption + +Keycloak itself consumes Access Tokens in several areas. + +Encrypted token handling should therefore be centralized. + +``` +Received access token + | + +---- JWS Compact + | | + | v + | verify + | + +---- JWE Compact + | | + | v + | decrypt + | | + | v + | verify nested JWS + | + +---- Encoded JWE JSON + | + v + Transport decode + | + v + parse JWE + | + v + Select KC recipient + | + v + decrypt CEK + | + v + decrypt ciphertext + | + v + verify nested JWS +``` + +An abstraction such as an Access Token decoder/parser should be preferred over endpoint-specific decryption. + +## Keycloak Consumers Requiring Review + +At minimum: + +* UserInfo +* Token Introspection +* Token Revocation +* Token Exchange +* UMA / Authorization Services +* Admin REST APIs +* Account APIs +* Bearer authentication +* DPoP validation +* mTLS token binding +* CIBA-related consumers +* Other internal consumers of AccessToken + +## at_hash + +OpenID Connect at_hash must be calculated using the final Access Token value returned to the client. + +Correct conceptual order: + +``` +AccessToken + | + v +JWS + | + v +JWE + | + v +transport encoding + | + v +final access_token + | + v +at_hash +``` + +Generating at_hash before access-token encryption would produce a hash of a value different from the value actually delivered to the client. + +## DPoP + +JWE and DPoP provide different protections. +* JWE: + * Confidentiality +* DPoP: + * Sender constraint + +They should work together. + +The DPoP ath value must use the exact Access Token string actually propagated by the client. + +When the Access Token is DPoP-bound, the token is presented using the `DPoP` authorization scheme rather than the `Bearer` scheme. The `ath` claim is calculated from the exact Access Token value presented to the +Resource Server. + +If the Access Token uses an additional transport encoding, the final transported representation is the value that must participate in the DPoP binding. + +## mTLS-Bound Access Tokens + +Existing confirmation claims such as: cnf x5t#S256, remain inside the signed token. They must continue being validated after JWE decryption. + +## Admin Console + +The existing Fine Grain OpenID Connect configuration already contains cryptographic options for ID Tokens and UserInfo. + +Access Token encryption should integrate consistently into the same model. + +Potential UI: + +``` +Access Token Encryption: [ Enabled ] + +Serialization + [ JWE Compact ] + [ JWE JSON Flattened + Base64URL ] + [ JWE JSON General + Base64URL ] + +Key Management Algorithm: [ RSA-OAEP-256 ] + +Content Encryption Algorithm: [ A256GCM ] + +Recipient Resolution: [ Resource ] + +Include Authorization Server: [ On ] +``` + +Exact UI and configuration attributes should not be finalized until the design is reviewed. + +## Resource Server Configuration + +Resource Servers may require their own encryption policy. + +Example: +``` +Resource: + payments-api + +Resource Identifier: + https://api.example/payments + +Require encrypted Access Tokens: + yes + +Encryption algorithms: + RSA-OAEP-256 + A256GCM + +Public-key source: + JWKS URI +``` + +The Resource Server requirement should prevent a client from requesting a weaker representation. + +## Capability Metadata + +A future implementation may advertise Access Token encryption capabilities through Authorization Server Metadata. + +Possible concepts: + +``` +{ + "access_token_encryption_alg_values_supported": [ + "RSA-OAEP-256" + ], + "access_token_encryption_enc_values_supported": [ + "A256GCM" + ] +} +``` + +Exact metadata names should only be introduced after maintainer review because they become public protocol surface. + +## keycloak-js + +`keycloak-js` currently operates in an ecosystem where Access Tokens are commonly parseable JWTs. + +Encrypted Access Tokens require the adapter to support tokens that are opaque to the browser. + +Token lifetime management should not require an exp claim from the Access Token. +OAuth token-response metadata such as: expires_in should be sufficient for scheduling token refresh. + +Identity/UI claims should come from: ID Token and UserInfo, rather than requiring Access Token inspection. + +Browser-based clients should not be required to possess static JWE private keys. + +## keycloak-admin-client + +The JavaScript Keycloak Admin Client should also support non-parseable Access Tokens. + +Token expiration and automatic refresh should not depend exclusively on decoding exp from the Access Token. + +OAuth response metadata should instead be tracked. + +## Security Considerations + +### Fail Closed + +If encryption is configured as mandatory and no valid recipient key can be resolved: token issuance must fail. + +Keycloak must never silently fall back to an unencrypted Access Token. + +### Nested JOSE Validation + +After successful JWE decryption: inner JWS signature must still be validated. + +### Algorithm Validation + +JOSE algorithms must be constrained by configured policy. + +The implementation must not blindly trust alg or enc values from token headers. + +### Private Key Isolation + +External Resource Server private keys should not be stored in Keycloak unless an explicit integration requires it. + +The normal architecture should be: + * Resource: Owns private key + * Keycloak: Receives only resource public key + +### Recipient Expansion + +Every additional recipient gains access to the complete plaintext. + +Recipient selection therefore has security implications and must not be automatic solely because an entity appears in aud. + +### Compression + +JWE compression should not be enabled by default. + +### Token Size + +Multi-recipient JWE can substantially increase token size. + +Each recipient adds: + * Encrypted CEK; + * kid; + * Recipient headers; + * JSON structure. + +Base64URL transport adds further size overhead. + +Real infrastructure limits must be considered, including: + * Reverse proxies; + * API gateways; + * OpenShift ingress/router; + * HAProxy; + * NGINX; + * Envoy; + * WAFs; + * Application servers. + +The implementation should consider configurable maximum token size and recipient count. + +### Input Size and Parsing Limits + +Encrypted Access Tokens, especially multi-recipient JWE JSON +representations, can be significantly larger and more complex than +traditional compact JWTs. + +To prevent resource-exhaustion and denial-of-service attacks, Keycloak +must validate input limits before performing expensive decoding, +parsing, key resolution, or cryptographic operations. + +The implementation should enforce limits for at least: + * Maximum encoded Access Token size; + * Maximum decoded JWE JSON size; + * Maximum number of JWE recipients; + * Maximum JOSE header size; + * Maximum supported nesting/structure complexity where applicable. + +For example, the server should reject an oversized Bearer token before +attempting Base64URL decoding, and reject an excessive recipient list +before attempting recipient key resolution or JWE decryption. + +Exact default values should be defined during implementation and should +take realistic HTTP header limits and deployment environments into +consideration. + +### Logging + +Access Tokens must never start appearing in logs or tracing attributes merely because their representation no longer resembles a conventional JWT. + +## Key Rotation + +Recipient key rotation should support overlapping keys identified by kid. + +New tokens use the active key. + +Existing tokens remain decryptable with previous keys during their lifetime. + +## JWKS Retrieval + +Remote key retrieval must consider: +* Caching; +* Rotation; +* Timeouts; +* SSRF protection; +* Failure behavior. + +## Reference Implementations + +Reference applications should demonstrate correct architectural usage of the feature. + +The official keycloak-quickstarts repository should eventually include scenarios such as the following. + +### SPA + Encrypted Access Token + +Demonstrate: + * Access Token not readable by SPA. + * Identity claims from ID Token. + * Resource Server JWE decryption. + * Nested JWS verification. + +### SPA + Multi-Recipient JWE + +Recipients: + * Keycloak + * API + +Demonstrate: + * General JWE JSON; + * Transport encoding; + * API decryption; + * Keycloak UserInfo; + * Introspection. + +### BFF - Backend for frontend + +``` +Browser + | + | HttpOnly session cookie + v +BFF + | + | encrypted downstream token + v +API +``` + +### End-to-End User Token + +``` +SPA --> API-A --> API-B --> API-C +``` +Demonstrate multiple JWE recipients. + + +### Token Exchange + +``` +API-A ── subject_token + resource=https://.../api-b ──> Keycloak --| +API-A <──────── exchanged access token ─────────────── Keycloak <--| +API-A ───────── resource-specific token ─────────────> API-B +``` + +### Client Credentials / M2M + +``` +Service-A + | + | client_credentials + v +Keycloak + | + | encrypted token + v +Service-B +``` + +### DPoP + JWE + +Demonstrate: + * Authorization Code + PKCE + * DPoP + * Encrypted Access Token + +## Testing Requirements + +### Core + +Test: + * Encryption disabled by default; + * Supported alg; + * Supported enc; + * Nested JWS; + * kid; + * Missing keys; + * Invalid keys; + * Unsupported algorithms; + * Fail-closed behavior. + +### Serialization + +Test: + * JWS Compact; + * JWE Compact; + * JWE Flattened JSON; + * JWE General JSON; + * Proposed Bearer transport. + +### Recipients + +Test: + * Single recipient; + * Keycloak recipient; + * Resource recipient; + * Multiple recipients; + * Unknown kid; + * Wrong private key; + * Key rotation. + +### Grants + +Test all supported access-token issuing grants. + +### Endpoints + +Test: + * UserInfo; + * Introspection; + * Revocation; + * Token Exchange; + * UMA; + * Admin APIs; + * Account APIs. + +### Clients + +Test: + * SPA with opaque Access Token; + * keycloak-js; + * keycloak-admin-client; + * Expiration based on token response metadata. + +### Compatibility + +Test: + * Existing JWS client; + * Encrypted client; + * Both in same realm; + * Resources with different policies. + +## Proposed Implementation Milestones + +This feature should be delivered incrementally. + +### Milestone 1 - Core Single-Recipient Encryption + - Access Token encryption configuration model. + - Compact JWE. + - Existing JOSE provider integration. + - Nested signing/encryption. + - Backwards compatibility. + - Integration/functional tests, with unit tests only for isolated utilities where appropriate. +### Milestone 2 - Keycloak Token Consumption + - Centralized Access Token decoding. + - Compact JWE decryption. + - UserInfo. + - Introspection. + - Revocation. + - Bearer authentication. + - Other server consumers. +### Milestone 3 - Admin Console + - Access Token encryption settings. + - Algorithm selection. + - Recipient configuration. +### Milestone 4 - Full Grant Coverage + - All token issuing flows. + - Integration tests. + - at_hash. + - DPoP. + - mTLS. +### Milestone 5 - Resource-Aware Encryption + - RFC 8707 integration. + - Resource Server encryption policy. + - Resource Server public-key resolution. + - Protected Resource Metadata evaluation. +### Milestone 6 - Multi-Recipient JWE + - General JWE JSON Serialization. + - Keycloak as additional recipient. + - Multiple independent Resource Server recipients. + - Recipient limits. +### Milestone 7 - Bearer Transport for JWE JSON + - Transport encoding. + - Deterministic representation detection. + - Interoperability tests. + - token/header-size testing. +### Milestone 8 - Client Libraries + - keycloak-js. + - keycloak-admin-client. +### Milestone 9 - Reference Implementations + - SPA. + - BFF. + - M2M. + - Token Exchange. + - end-to-end propagation. + - DPoP. + +## Open Design Questions + +Maintainer feedback is particularly requested for the following points. + +1. Initial Scope + +Should the first implementation support only JWE Compact single recipient and add multi-recipient support afterward? + +2. Multi-Recipient JWE + +Should General JWE JSON Serialization be supported for Access Tokens? + +3. JWE JSON Bearer Transport + +Would a BASE64URL(JWE General JSON) be an acceptable OAuth Bearer transport profile? + +4. Transport Versioning + +If a Keycloak-specific transport profile is required, should it use a versioned marker such as: kc-jwe-json-v1. + +5. Policy Ownership + +Should Access Token encryption requirements primarily belong to: + - client; + - Resource Server; + - realm; + - Combination of these? + +6. Resource Identification + +Should RFC 8707 resource be the preferred mechanism for determining the cryptographic recipient? + +7. Resource Key Discovery + +Should RFC 9728 Protected Resource Metadata and Resource Server jwks_uri be the preferred public-key discovery mechanism? + +8. Keycloak as Recipient + +Should Keycloak be optionally added as an independent recipient so that server-side Access Token consumers can decrypt tokens without sharing Resource Server private keys? + +9. Multiple Audiences + +How should recipient resolution behave when an Access Token targets multiple audiences/resources? + +10. Browser Clients + +Should keycloak-js explicitly treat encrypted Access Tokens as opaque and move token lifecycle management away from locally decoded Access Token claims? + +11. Refresh Tokens + +Should Refresh Token encryption be addressed separately after Access Token encryption, even though the existing feature request covers both? + +12. Interoperability + +Should support for General JWE JSON be considered an experimental capability until a stable Bearer transport profile is agreed upon? + +## References +* [RFC 6749 - The OAuth 2.0 Authorization Framework](https://www.rfc-editor.org/rfc/rfc6749) +* [RFC 6750 - OAuth 2.0 Bearer Token Usage](https://www.rfc-editor.org/rfc/rfc6750) +* [RFC 7515 - JSON Web Signature (JWS)](https://www.rfc-editor.org/rfc/rfc7515) +* [RFC 7516 - JSON Web Encryption (JWE)](https://www.rfc-editor.org/rfc/rfc7516) +* [RFC 7517 - JSON Web Key (JWK)](https://www.rfc-editor.org/rfc/rfc7517) +* [RFC 7518 - JSON Web Algorithms (JWA)](https://www.rfc-editor.org/rfc/rfc7518) +* [RFC 7519 - JSON Web Token (JWT)](https://www.rfc-editor.org/rfc/rfc7519) +* [RFC 7591 - OAuth 2.0 Dynamic Client Registration Protocol](https://www.rfc-editor.org/rfc/rfc7591) +* [RFC 8414 - OAuth 2.0 Authorization Server Metadata](https://www.rfc-editor.org/rfc/rfc8414) +* [RFC 8693 - OAuth 2.0 Token Exchange](https://www.rfc-editor.org/rfc/rfc8693) +* [RFC 8705 - OAuth 2.0 Mutual TLS](https://www.rfc-editor.org/rfc/rfc8705) +* [RFC 8707 - Resource Indicators for OAuth 2.0](https://www.rfc-editor.org/rfc/rfc8707) +* [RFC 8725 - JSON Web Token Best Current Practices](https://www.rfc-editor.org/rfc/rfc8725) +* [RFC 9068 - JWT Profile for OAuth 2.0 Access Tokens](https://www.rfc-editor.org/rfc/rfc9068) +* [RFC 9449 - OAuth 2.0 Demonstrating Proof of Possession (DPoP)](https://www.rfc-editor.org/rfc/rfc9449) +* [RFC 9728 - OAuth 2.0 Protected Resource Metadata](https://www.rfc-editor.org/rfc/rfc9728) +* [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) +* [Keycloak #19464 - Encrypt Access and Refresh Tokens](https://github.com/keycloak/keycloak/issues/19464) +* [Keycloak PR #6796 - Add support for encrypted access tokens](https://github.com/keycloak/keycloak/pull/6796) +* [Keycloak PR #7341 - KEYCLOAK-10761 Access-Token Encryption Support](https://github.com/keycloak/keycloak/pull/7341) +* [Keycloak PR #5779](https://github.com/keycloak/keycloak/pull/5779) \ No newline at end of file diff --git a/design/img/encrypted-access-tokens/cryptographic-construction.png b/design/img/encrypted-access-tokens/cryptographic-construction.png new file mode 100644 index 0000000..18c408d Binary files /dev/null and b/design/img/encrypted-access-tokens/cryptographic-construction.png differ diff --git a/design/img/encrypted-access-tokens/keycloak-JWE-recipient.png b/design/img/encrypted-access-tokens/keycloak-JWE-recipient.png new file mode 100644 index 0000000..6cd0562 Binary files /dev/null and b/design/img/encrypted-access-tokens/keycloak-JWE-recipient.png differ diff --git a/design/img/encrypted-access-tokens/single-recipient-JWE.png b/design/img/encrypted-access-tokens/single-recipient-JWE.png new file mode 100644 index 0000000..9ef070f Binary files /dev/null and b/design/img/encrypted-access-tokens/single-recipient-JWE.png differ diff --git a/design/img/encrypted-access-tokens/token-exchange.png b/design/img/encrypted-access-tokens/token-exchange.png new file mode 100644 index 0000000..e973e53 Binary files /dev/null and b/design/img/encrypted-access-tokens/token-exchange.png differ