Self-contained JWTs
On this page
Overview
This article will explain some alternative ways in which APIs can validate JWT access tokens, and the related use cases. The general pattern is to include additional key information in the JWT header, which the API can then read and supply to a JWT validation library.
Standard JWT Validation
The most common option for an API is to receive a JWT header with fields similar to the following:
{kid: '359322535',alg: 'RS256'}
The API then makes a request to the JWKS Endpoint of the Authorization Server over a trusted SSL connection, after which the key material received is trusted. The response provides JSON web keys (JWK) as described in RFC7517, and the below example payload is for the widely used RS256 algorithm:
{"keys": [{"kty": "RSA","kid": "359322535","use": "sig","alg": "RS256","n": "0gPWzqkjREVjJa9w6qjhYkijDGR ...","e": "AQAB","x5t": "tQ_s8QFqkpcuzWXTvsWHocNqiOQ"}]}
The API then looks up the key that matches the received kid
field and supplies it to the JWT library, which derives the public key from the n
(modulo) and e
(exponent) fields. The JWT library then uses the public key to verify the signature of the received JWT, to ensure it is cryptographically correct and has not been tampered with.
Once JWT validation is complete, the API caches the JWK and uses the cached value for future API requests whose JWT header has the same kid
field. This avoids excessive calls to the JWKS endpoint, which could otherwise lead to performance problems.
Alternative JWT Use Cases
There are a couple of important scenarios where the standard option may not meet your requirements, and these are the most common:
Use Case | Behavior |
---|---|
Serverless / Cloud Native | In some API deployment scenarios a new instance of the API is spun up on every API request, or the API must remain stateless, so that calling the JWKS endpoint per request is prohibitive in performance terms |
Financial Grade | It is possible for APIs to make additional trust checks against tokens using a Public Key Infrastructure (PKI), and in some setups this can ensure that trust brokering relationships between parties are verified |
JWT Header Fields
Your Authorization Server should enable you to include additional fields so that token signing and trust details are made available in a Self-contained manner. As an example, the Curity Identity Server enables you to configure one or more Token Issuers, where these options can be activated. In the following sections we will drill into how the following fields are used when validating JWTs:
Header Field | Description |
---|---|
x5t | This is a SHA-1 hash of an X.509 certificate. This “thumbprint” of the certificate is why this header is referred to as an x5t for short. |
x5t#s256 | This is a SHA-256 hash of an X.509 certificate , and preferred over x5t since it is less likely to result in collisions. |
x5c | This is also related to X.509 certificates, but the “C” in this header refers to the chain of certificates of the one used to sign the JWT. |
jwk | A JSON web key provides token signing details and can also convey trust information via x5t, x5t#s256 and x5c fields. |
x5t and x5t#s256
JWTs can be validated by deploying one or more token signing certificates with the API, and then loading the certificate that matches the received x5t
or x5t#s256
value. In this setup the API has everything it needs locally, but the JWT is not really Self-contained since it does not contain the public key used for validation.
Once loaded, the certificate is then used to validate the JWT in the standard way. The API does not check in with the Authorization Server however, so is unable by default to detect JWTs signed with revoked token signing keys.
x5c
The x5c field can be included in the JWT header, to provide a Self-contained JWT with token signing certificate details. The format of this field is described in detail in RFC7515, and it can optionally include a full trust chain, where the token signing certificate itself is the first entry:
{kid: '-1614245140',x5t: 'lDdNIsb3FxulMcYdAXxYJ_Z5950',x5c: ['MIIDqjCCApKgAwIBAgIESLNEvDA ...','MIICwzCCAasCCQCKVy9eKjvi+jA ...','MIIDTDCCAjSgAwIBAgIJAPlnQYH...'],alg: 'RS256'}
Using the x5c field to validate tokens verifies the full trust chain of the token using a Public Key Infrastructure, and can support sophisticated trust brokering setups. Private PKI setups are common these days, in which case the x5c verification proves that the JWT was signed with a private key issued by the company.
The PKI verification can include revocation checks to detect compromised token signing keys, so that those tokens are not accepted by the API. This can include the use of a Certificate Revocation List (CRL) or Online Certificate Status Protocol (OCSP) responder, to provide revoked key details.
jwk
This option is typically used in scenarios where the x5c certificate chain only contains certificate authorities and not the token signing certificate itself. The public key is then provided in the JWT header as a jwk
object, which can optionally include trust information in its x5c
field:
{jwk: {kty: 'RSA',alg: 'RSA',n: 'hLjc255V6xkm3zBhh1tlTj14B ...',e: 'AQAB',x5c: ['MIIDqjCCApKgAwIBAgIESLNEvDA ...','MIICwzCCAasCCQCKVy9eKjvi+jA ...','MIIDTDCCAjSgAwIBAgIJAPlnQYH...'],x5t: 'lDdNIsb3FxulMcYdAXxYJ_Z5950'},alg: 'RS256'}
When relying on a JWK to verify a JWT you must ensure that it was not issued by a malicious party, and there are two alternative mechanisms here:
- Using OpenID Connect Discovery, to verify that the JWT's
iss
claim maps to an https URL whose certificate chain the API trusts - Using PKI to verify x5c certificates included with the JWK, which many companies consider more established, and which avoids the need for the API to make any https calls
Validating Self-contained JWTs
When using Self-contained JWTs, an additional step is required, so that an attacker cannot create a JWT with their own certificate and send it to your API:
Step | Description |
---|---|
Verify the Trust Chain | Ensure that the received token signing public key is provided by a trusted issuer, as detailed in RFC5280 |
Standard JWT Validation | Then verify the digital signature of the JWT, and also check the expiry, issuer and audience |
Certificate Trust Chains
The trust chain of the token signing certificate may look like this, where the CA certificates are long lived and can be deployed with your API, as a trust store
:
Certificate | Role | Lifetime |
---|---|---|
Signing Certificate | Used to digitally sign the JWT | 6 months |
Intermediate CA | A certificate authority used to issue the token signing certificate | 10 years |
Root CA | A certificate authority used to issue the intermediate certificate | 10 years |
In this type of setup the API will not have to be concerned with key management issues for 10 years or so. It also never has to make any outgoing calls, to get data from endpoints such as JWKS, Metadata or Webfinger endpoints. This provides a very simple, safe, and maintainable setup.
Integrating with JWT Libraries
The high level steps to validate a Self-contained JWT should not require much code, since security libraries will do the main work, as in the following example Javascript code:
const receivedCertChain = getCertChain(x5c);const trustStore = getWhitelistedCAs();verifyTrustChain(receivedCertChain, trustStore);const tokenSigningPublicKey = createKey(receivedCertChain[0]);const options = {algorithms: ['RS256'],issuer: 'https://login.example.com/oauth/v2/oauth-anonymous',audience: 'api.example.com',};verifyJwt(accessTokenJwt, tokenSigningPublicKey, options);
JWT validation libraries usually support supplying the token signing details in a variety of ways, so that validating a Self-contained JWT does not require much code:
Option | Mechanism |
---|---|
As a JWKS download | API supplies a JWKS endpoint to the library |
As a JWK | API supplies a jwk object to the library |
As a Certificate | API supplies the first certificate from the x5c array to the library |
Video
This video provides further information on architectural choices around APIs and JWTs:
Conclusion
Self-contained JWTs are useful in APIs that cannot cache JSON web keys, and they can also be used to make additional X509 trust checks, such as in financial grade APIs or advanced trust brokering setups. For a fully worked example on verifying JWTs containing x5c
, jwk
or x5t
fields, see the Serverless API Code Example.
Gary Archer
Product Marketing Engineer at Curity
Join our Newsletter
Get the latest on identity management, API Security and authentication straight to your inbox.
Start Free Trial
Try the Curity Identity Server for Free. Get up and running in 10 minutes.
Start Free Trial