The Token Connection

How JWS, JWK, and certificates play together

Dirk Bolte
7 min readMar 17, 2021
Different JWS and JWK incarnations
JWS, JWK, and certificate connections

JSON is widely used in many APIs as a format to exchange data. While being very universal to represent data, JSON has no built-in mechanism for ensuring data integrity or encryption, leaving it to the transport layer (TLS).

JWT or JWS are terms commonly used when it comes to authentication, but the concepts provide a much wider range of options which make them usable for many data exchanges. The JSON Object Signing and Encryption group defined a set of tokens that play nicely together to provide data layer integrity and encryption while offering the applicability of pure JSON.

The basic token

Illustrated JWT with header and claims
The basic JWT with header parameters and claims

A JSON Web Token (JWT, RFC 7519) has three parts: a header, the payload + an optional signature. The header specifies the type of token and how to interpret it. The optional signature is, well, the signature. If the token isn’t signed, it’s just left out.
The payload is a set of claims or a claim set. Basically, it is a JSON object with zero or more keys. It can contain any kind of JSON data. Some claims are predefined or registered (JWT ID, issued at, expiration time, audience, issuer, subject), but the structure can be freely extended, requiring producer and consumer to agree upon a data format.

For transporting a token, all parts are Base64 URL encoded and concatenated, separated by a . . The most basic JWT token with no signature and an empty payload looks like this:

eyJhbGciOiJub25lIn0.e30.

Adding token integrity

Illustrated JWS with header, claims and signature
JWS with header, claims, and signature

A basic JWT does not require having a signature, but it’s pretty common to add one, making it a JSON Web Signature (JWS, RFC 7515). To do so, the header now has to add the algorithm (alg) for the signature — and the signature in the end. That’s it. Everything else is optional. For exchanging it, its header, payload, and signature are again Base64 URL encoded and concatenated. A sample token would look like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U

This allows exchanging any JSON payload while allowing to verify the integrity. Various security recommendations should be taken into concern. Also, adding variable + random data like a creation timestamp as well as a JWT ID (jti) are helpful.

Verifying token integrity

When consumer and producer are the same instances, that instance would have access to all keys and would thereby know how to verify the integrity. But as soon as you do key rotation or consumer and producer are two different parties, this doesn’t work anymore. Luckily, JWS and the other parts of the JOSE suite provide many ways to deal with this.

The key ID

Next to the alg parameter, the producer of a JWS can add a key ID (kid). The consumer can now identify the key that should be used for verifying the key — given that the keys are accessible.

The JWK and JWK Set

Keys for various purposes can be exchanged in the JWK format (RFC 7517). Like the JWS, the JWK for a key can also have a kid parameter, indicating that the key with matching kid can e.g. verify a JWS. The key ID is an arbitrary string, so the producer of JWS and JWK has to ensure that there are no conflicts and that both match.
A service can offer various keys in a JWK Set, allowing consumers to access multiple keys at once in order to verify different tokens. Providing a jwks.json download URL is common practice, providing a complete JWK Set.

The JWK Set URL

JWS pointing to a JWK in a JWK Set
JWS and JWK Set

The URL for downloading the JWK Set can be shared upfront, e.g. using .well-known URIs (RFC 8615). A JWS can be more self-contained though by adding the JWK Set URL as jku parameter to the JOSE header. With this information, the consumer can:

  1. download the JWK Set based on the jku in the JWS
  2. search for the JWK matching the kid of the JWS
  3. verify the signature using the algorithm specified in alg , using the key generated from the JWK

With this, many libraries start to play out their power as they can automate all this.

An embedded key

Next to referring to a JWK Set, the JWS can contain the JWK directly in the jwk header parameter. This key can then be used to verify the JWS.

An embedded certificate

Instead of specifying a key and referring to a JWK set, the JWS can directly contain a corresponding X.509 certificate or even a certificate chain in the x5c header parameter. It is specified that the first certificate MUST contain the public key used to verify the JWS. The certificate or chain must be validated, allowing to ensure that the certificate was issued by an entity trusted by the consumer.

A certificate URL

A JWS pointing to a certificate chain via its x5u parameter
JWS and Certificate Chain

Instead of embedding the certificate or whole certificate chain in the JWS itself, a URL can be specified in the x5u header parameter, pointing to a resource to get them from. The validation would be equivalent to the embedded certificate. To improve security when downloading the URLs, the certificate thumbprints/fingerprints can be added as well (x5t and x5t#S256 header parameters). So the consumer would:

  1. download the certificate (chain) based on the x5u in the JWS
  2. verify the thumbprint of the first certificate using x5t or x5t#S256
  3. if x5u point to a chain: verify the certificate chain
  4. verify that the certificate or chain is signed by an entity trusted by the consumer
  5. verify the signature using the algorithm specified in alg, using the key of the first certificate

Verifying key integrity

When verifying the signature of a JWS using a JWK, it requires the consumer of a token to have trust in the key. There are multiple ways to do so.

Server validation

When downloading the JWK set using the jku in a JWS, TLS should be used. Along with that, the identity of the server has to be validated. This is often done automatically by libraries in use as they validate server certificates against trusted root certificates. Another option would be to pin the certificate or public key of the server you communicate with.

An embedded certificate

Similar to the JWS, the JWK can contain a certificate or certificate chain in an x5c header parameter. The key in the first certificate thereby has to match the key of the JWK. The certificate or certificate chain must be validated. By verifying that the certificate or its chain was issued by a trusted party, it knows that it can trust the key.
This parameter can be present in a JWK in a JWK Set provided via a download URL as well as the JWK directly embedded in a JWS (jwk header parameter). The handling thereof would be the same.

A certificate URL

A JWS pointing to a JWK in a JWK set. The JWK points to a certificate chain.
JWS with JWK Set and certificate chain

Following the same pattern as JWS, a JWK can contain a URL to point to a certificate or chain (x5u), along with its checksums (x5t, x5t#S256). After downloading the certificate from the validated server, the actual certificate validation must be executed the same way as for an embedded certificate.
This parameter can be present in a JWK in a JWK Set provided via a download URL as well as the JWK directly embedded in a JWS (jwk header parameter). The handling thereof would be the same.

Real-life examples

Self-encoded access tokens

A JWS signed by an authorization server along with a limited lifetime (using an expiration) allows an API to verify access without requiring any interaction with the authorization server. They become self-encoded. An API can simply validate the signature and validity of the token based on the keys of the authorization server. This is also specified in the Health Relationship Trust Profile for OAuth2.0.

OpenID Connect ID Token and Userinfo

OpenID Connect is a very good real-life example where many parts of JWS come together. OpenID Connect allows to

  • create signed ID Tokens and UserInfo, as JWS
  • uses the kid to specify the key to use for verifying the JWS signature
  • provide a JWK Set download URL over a /.well-known service

OpenID favors the usage of a commonly known JWK Set URL instead of embedding the key (Set URL) or the certificate (URL) in the tokens.

Next to the OpenID connect server, the clients can upload keys in their client configuration or specify a JWK Set download URL.

Introspection and Libraries

Although there are many libraries for different languages and environments, the support of the whole JOSE suite varies drastically. You will often find some verification features missing, so you should check the library’s support for what you intend to use.

jwt.io allows to decode and create various tokens. Many examples in this article were created with it. The page also contains a listing of libraries for various programming languages that help you working with the various JOSE types.

mkjwk.org provides a convenient interface to create JWKs and JWK Sets along with self-signed certificates.

Summary

With JWS, JWK, and certificates, you can build an infrastructure that ensures the integrity of any data you exchange. The integrity is not limited to the TLS connection but to the actual data. Using JWKs and certificates, you can ensure that every data is provided by the entity you trust and make it verifiable wherever it’s consumed.

--

--