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?

  1. 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.

  2. 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 and htu).

    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
  3. 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...
  4. If the DPoP proof is valid and signed with a supported JWS algorithms (the Connect2id server supports all standard RSxxx, PSxxx and ESxxx algorithms) the token response will appear in the usual format, but with the token type set to DPoP.

    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.


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:

  1. 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.

  2. 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.

  3. Make sure the HTTP request includes a DPoP header and validate the proof JWT in it according to the described procedure.

  4. 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

  5. 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...


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.


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


Contact Connect2id support.

Release notes

12.2 (2021-08-26)


  • 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).


  • /WEB-INF/jwkSet.json

    • Supports RSA signing and encryption keys of lengths 3072 and 4096 bits, in addition to 2048-bit RSA keys.


  • /.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.


  • 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