DPoP access tokens in Connect2id server 12.2
Connect2id server 12.2 introduces support for DPoP tokens and configuring longer RSA keys. It also addresses several issues, described in the release notes.
DPoP access tokens
Single-page applications (SPA) can now request the issue of DPoP access
tokens from the Connect2id
server. This is a new kind of token, with stronger security properties than the
default Bearer access tokens
in OAuth 2.0. The DPoP token comes with a protection against unauthorised use
in case it suffers an accidental or malicious leak. This is achieved by binding
the token to a private key held by the client. To prevent a leak of the key
itself the client should store it behind an API that keeps its private
parameters inaccessible to application code. The
SubtleCrypto
(WebCrypto) API has this option, its
generateKey()
method for RSA an EC keys when called with extractable=false
will prevent
JavaScript application code from exporting the private key parameters from the
browser.
DPoP is an alternative to the conceptually similar client certificate bound tokens (RFC 8705), which ultimately are also bound to a private key. The difference is that in DPoP the binding is conveyed by means of a JWT instead of a client X.509 certificate.
Token type | Binding | Spec |
---|---|---|
Bearer | none | RFC 6750 |
Bearer with client certificate binding | via client X.509 certificate | RFC 8705 |
DPoP | via signed JWT | RFC 9449 |
The security properties of the client certificate binding are stronger because it involves the underlying secure transport layer (TLS), which made it the recommended method for applications, for example in the FAPI (financial-grade) OAuth 2.0 security profile. Browsers however don’t provide a JavaScript API to enable application code to deal client certificates and mutual TLS, and to work around this the DPoP binding was invented.
How can an SPA request a DPoP token?
-
At the start of a new session the SPA generates a new RSA or EC key pair, with disabled extraction (
extractable=false
) so the private key parameters cannot be exported from the browser. -
To request a DPoP access token the SPA generates a one-time-use JWT signed with the private key. The function of this JWT is to demonstrate possession of the key (hence the PoP acronym). Its header includes the public parameters of the signing key in JWK format. The claims include a unique JWT identifier (
jti
), the JWT issue time (iat
) and the HTTP method and URI of the token endpoint (htm
andhtu
).Example DPoP proof JWT header:
{ "typ" : "dpop+jwt", "alg" : "ES256", "jwk" : { "kty" : "EC", "crv" : "P-256", "x" : "l8tFrhx-34tV3hRICRDY9zCkDlpBhF42UQUfWVAWBFs", "y" : "9VE4jf_Ok_o64zbTTlcuNJajHmt6v9TDVrU0CdvGRDA" } }
Example DPoP proof JWT claims:
{ "jti" : "f352c2fa-2d06-47b9-8347-8cda8a70a17d", "htm" : "POST", "htu" : "https://c2id.com/token", "iat" : 1562262616 }
-
The SPA makes the usual token request to the Connect2id server, but to trigger issue of a DPoP access token the proof JWT must be included in a HTTP request header called
DPoP
.POST /token HTTP/1.1 Host: c2id.com Content-Type: application/x-www-form-urlencoded;charset=UTF-8 DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7... grant_type=authorization_code &code=SplxlOBeZQQYbYS6WxSbIA &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb &code_verifier=bEaL42izcC-o-xBk0K2vuJ6U-y1p9r_wW2dFWIWgjz-
-
If the DPoP proof is valid and signed with a supported JWS algorithms (the Connect2id server supports all standard
RSxxx
,PSxxx
andESxxx
algorithms) the token response will appear in the usual format, but with the token type set toDPoP
.HTTP/1.1 200 OK Content-Type: application/json Cache-Control: no-store { "access_token" : "tai1eeJ0eeNgiech.aing6aiJoopohsoh", "token_type" : "DPoP", "expires_in" : 600, "refresh_token" : "Ci8Fieshaikeiwaih8Tee5ZuaP5Iepho" }
How can an SPA access a protected resource with a DPoP token?
To access a protected resource with a DPoP token the client needs to generate a
new DPoP proof, with one additional string claim - ath
, set to the
BASE64URL-encoded SHA-256 hash of the access token value. The htm
(HTTP
method) and htu
(HTTP URI) claims must match those of the resource.
The UserInfo endpoint of the Connect2id server was updated to accept DPoP access tokens. The next section has pointers how to support DPoP tokens at a resource server.
Example DPoP proof JWT claims, for an HTTP GET to some protected resource at
https://api.example.com/accounts/123
and with the access token hash in ath
:
{
"jti" : "a6716edf-eb6a-4649-8f6d-ac0325d26738",
"htm" : "GET",
"htu" : "https://api.example.com/accounts/123",
"iat" : 1562262716,
"ath" : "fUHyO2r2Z3DZ53EsNrWBb0xWXoaNy59IiKCAqksmQEo"
}
The token is passed in a standard Authorization
header, but instead of
Bearer
we have the token scheme set to DPoP:
GET /accounts/123 HTTP/1.1
Host: api.example.com
Authorization: DPoP tai1eeJ0eeNgiech.aing6aiJoopohsoh
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7...
If the resource server finds the DPoP proof or the token to be invalid or
expired it will respond with a standard 401 Unauthorized and describe the error
in the WWW-Authenticate
header.
Example:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: DPoP error="invalid_token", error_description="Invalid / expired DPoP token", algs="RS256 PS256 ES256"
The algs
parameter is optional, but very useful as it allows the client to
find out which JWS algorithms for signing the DPoP proofs the resource server
is able to handle.
How to support DPoP access tokens in a resource server?
The validation of DPoP tokens is described in the spec and has five steps:
-
Verify the received access token in the usual way. For self-contained (JWT-encoded) tokens this means a local inspection by checking the JWT signature and claims; for an identifier-based token a call to the introspection endpoint of the Connect2id server.
-
Check if the token authorisation has a
cnf.jkt
(confirmation of JWK thumbprint) parameter. If it does, then this is a DPoP token, so proceed further. -
Make sure the HTTP request includes a DPoP header and validate the proof JWT in it according to the described procedure.
-
Compute the SHA-256 thumbprint of the public JWK found in the DPoP proof JWT and make sure it matches the JWK thumbprint (
cnf.jkt
) which the Connect2id server computed at the token endpoint when it minted the DPoP token. If the two thumbprints match this means we -
Finally, compute the SHA-256 hash of the access token and make sure it matches the
ath
claim of the DPoP proof JWT.
Resource servers may implement an additional check against DPoP proof replay,
by recording the proof jti
(JWT ID) and making sure it doesn’t appear again
within the time window determined by the acceptable iat
age of the proofs.
The Java open source OAuth 2.0 SDK includes a DPoP proof verifier to help resource servers with this task.
How to refresh a DPoP token?
If the Connect2id server issued a refresh token the client must generate a DPoP proof whenever the refresh token is used.
POST /token HTTP/1.1
Host: c2id.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7...
grant_type=refresh_token
&refresh_token=Ci8Fieshaikeiwaih8Tee5ZuaP5Iepho
3072 and 4096 bit RSA keys
The Connect2id server can now be configured with longer RSA keys for signing the issued tokens and receiving encrypted request objects. 2048 bits remains the default length of the generated RSA keys when using the provided jwkset-gen tool.
Download
Standard Connect2id server edition
Apache Tomcat package with Connect2id server 12.2: Connect2id-server.zip
SHA-256: 33b49db86b129a6d92f9ae9fe5cb9562e257e9f7b87f4c3bffa5e4b8c439ccdd
Connect2id server 12.2 WAR package: c2id.war
SHA-256: a85433faf9802b954b24c0251b899af6f8a9ca9a0ec4f8b94f2e091f9185b7cf
Multi-tenant edition
Apache Tomcat package with Connect2id server 12.2: Connect2id-server-mt.zip
SHA-256: a2cf8a27a6d3fbbc802203301273438104bf3c23404ae7efa3ebec41cc98974d
Connect2id server 12.2 WAR package: c2id-multi-tenant.war
SHA-256: 688417f2dde16deb5f7e1f388d2d02060e6f4e8294b8d3633e731deca58864ac
Questions?
Contact Connect2id support.
Release notes
12.2 (2021-08-26)
Summary
-
Supports issue of access tokens of type DPoP, where the token is bound to a private RSA or EC key generated and held by the OAuth 2.0 client, potentially in secure storage preventing the key’s extraction. Use of the DPoP token at a resource server requires proof of possession of the private key, preventing the use of an accidentally or maliciously leaked token by an unauthorised party. The original Bearer tokens in OAuth 2.0 offer no such protection to constrain their use.
DPoP is intended primarily for browser based applications (also commonly called single page applications, or SPAs) where the alternative standard method to constrain a token, by binding it to a client X.509 certificate (RFC 8705), is not suitable, due to the browsers’ poor support for dealing with client certificates and mutual TLS in JS application code.
SPAs should use the WebCrypto browser API to generate the necessary private keys (with disabled extraction) for signing the DPoP proofs and actual signing.
The Connect2id server accepts the following JWS algorithms for the signed DPoP proof JWTs: RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES256K, ES384 and ES512.
The Connect2id server will reject DPoP proof JWTs with an “iat” (issued-at time) that is more than 30 seconds behind or ahead of the current system time. Proof JWTs with a repeated “jti” (JWT ID) will also be rejected to prevent replay.
See OAuth 2.0 Demonstrating Proof-of-Possession at the Application Layer (draft-ietf-oauth-dpop-03).
-
The Connect2id server can now be configured with longer RSA signing and encryption keys, with lengths of 3072 and 4096 bits. The 2048 RSA key length remains the recommended for now and also the default for in provided jwkset-gen.jar tool for generating server JWK sets (the latest tool version is 1.22).
Configuration
-
/WEB-INF/jwkSet.json
- Supports RSA signing and encryption keys of lengths 3072 and 4096 bits, in addition to 2048-bit RSA keys.
Web API
-
/.well-known/openid-configuration, /.well-known/oauth-authorization-server
- dpop_signing_alg_values_supported – New metadata field, lists the accepted JWS algorithms for the DPoP proof JWTs: RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES256K, ES384 and ES512.
-
/token
-
Supports issue of access tokens of type DPoP for the following OAuth 2.0 grants:
- authorisation code
- refresh token
- client credentials
- resource owner password credentials
-
Supports issue of DPoP bound refresh tokens for public OAuth 2.0 clients.
-
-
/token/introspect
- Supports introspection of access tokens of type DPoP. Introspection success responses for a DPoP token will have the “token_type” member set to “DPoP” and will include a “cnf.jkt” member set to the BASE64URL encoded SHA-256 thumbprint of the JWK used to sign the DPoP proof JWT.
-
/userinfo
- Supports access tokens of type DPoP. Those must be included in the “Authorization” HTTP request header using the “DPoP” scheme.
-
/monitor/v1/metrics
- Adds new “dPoP.numCachedJTIs” metric of type gauge showing the number of locally cached DPoP proof JWT ID (jti) entries intended to ensure the single use of received DPoP proofs at the token and UserInfo endpoints.
SPI
-
Upgrades the Connect2id server SDK to com.nimbusds:c2id-server-sdk:4.38
-
com.nimbusds.openid.connect.provider.spi.tokens.AccessTokenAuthorization
- Adds default getJWKThumbprintConfirmation method to represent a DPoP JWK SHA-256 thumbprint confirmation.
-
com.nimbusds.openid.connect.provider.spi.tokens.MutableAccessTokenAuthorization
-
Implements AccessTokenAuthorization.getJWKThumbprintConfirmation
-
Adds withJWKThumbprintConfirmation setter method.
-
-
com.nimbusds.openid.connect.provider.spi.tokens.BaseSelfContainedAccessTokenClaimsCodec
- Updates the base abstract codec for JWT-encoded access tokens to support the “cnf.jkt” claim for DPoP.
-
com.nimbusds.openid.connect.provider.spi.tokens.introspection.BaseTokenIntrospectionResponseComposer
- Updates the base abstract composer for customer token introspection responses support the “cnf.jkt” member for DPoP.
-
Resolved issues
-
Logs invalid refresh token (AS0270), refresh token request client_id - encoded client mismatch (AS0274) and refresh token not permitted (AS0275) events (issue authz-store/188).
-
Improves the efficiency when loading issuer lookup cache entries in the tenant registry (issue/tenant-registry/5).
-
Fixes a bug in AuthorizationRequest.Builder(AuthorizationRequest) that prevented copy of OpenID authentication request parameters, which can affect modification of requests in AuthorizationRequestValidator and PARValidator SPI implementations (issue oidc-sdk/367).
-
Refactors and extends handling of corrupted JSON in long_lived_authorizations items in a AWS DynamoDB table for get, put-if-absent, replace and remove operations to prevent an internal server error (HTTP 500) and clean up the detected corrupted items where feasible (issues authz-store/186, 187).
Dependency changes
-
Upgrades to com.nimbusds:c2id-server-sdk:4.38
-
Upgrades to com.nimbusds:oauth2-oidc-sdk:9.15
-
Upgrades to com.nimbusds:nimbus-jose-jwt:9.12.1
-
Upgrades to com.nimbusds:c2id-server-jwkset:1.22
-
Upgrades to com.nimbusds:oauth2-authz-store:17.4.1
-
Updates to com.nimbusds:tenant-registry:5.3.4