Want to learn more about OAuth 2.0 and OpenID Connect?
Save yourself days of digging through dozens of specs with this online courseMore information
The hard parts of JWT security nobody talks about
In spite of the popularity of JWTs, their security properties are often unknown or misunderstood. How do you choose the signature scheme for a JWT? What other properties should you verify before trusting a JWT? How do you handle key rotation and key management? Read on for a deep-dive into JWT security.
The popularity of JSON Web Tokens
Today, virtually every web developer uses JSON Web Tokens (JWTs) one way or another. OAuth 2.0 and OpenID Connect use them to exchange information between parties. Modern applications use them to keep track of state between requests. Backend services use them to propagate authorization information in a microservice architecture. In this article, we go beyond the typical narrative of using JWTs. We look at the hard parts nobody ever talks about. In the end, we also provide a cheat sheet on JWT security, to keep track of the best practices we covered.
If you are not familiar with the basics of JWTs, we suggest you read an introduction to JWT before diving into this article.
Symmetric JWT Signatures
The screenshot below shows the well-known decomposition of a JWT. As you can see, the middle part of the token contains the actual data. The header includes metadata about the token, and the signature is there to ensure the integrity. The signature is essential to detect unauthorized tampering with a token.
How it works
When a service generates a JWT, it also creates a signature. Traditionally, this signature is an HMAC, which uses a particular type of cryptographic functions. Such an HMAC algorithm is indicated with the
HS prefix, as shown in the sample token above.
The HMAC takes the header, the payload and a secret key as input, and returns a unique signature over all three inputs. This process is illustrated in the left part of the figure below.
When a service receives an inbound JWT, it needs to verify the integrity before using the embedded data. To do so, the service uses the same secret key to calculate the HMAC of the JWT. If the resulting HMAC is the same as the signature in the token, the service knows that all three inputs to the HMAC function were the same as before.
However, if the HMACs do not match, something has changed. The secret key is unlikely to change, so something in the inbound JWT has changed. The service does not care what changed. It merely rejects the JWT altogether. This process is shown on the right in the figure above.
This signature scheme is straightforward. It is also the typical scheme used to explain JWTs to developers. Unfortunately, symmetric signatures prevent the sharing of the JWT with another service. To verify the JWT’s integrity, all services would need to have access to the same secret key. However, possession of the secret key is enough to generate arbitrary JWTs with a valid signature.
Sharing the HMAC secret with a third-party service creates a significant vulnerability. Even sharing the secret between different services within a single architecture is not recommended. If all services share the same key, the compromise of a single service immediately becomes an architecture-wide problem.
Instead of sharing the secret HMAC key, you can opt for using asymmetric signatures.
Asymmetric JWT Signatures
An asymmetric signature uses a public/private key pair. Such a key pair possesses a unique property. A signature generated with a private key can be verified with the public key. And just as the name implies, the public key can be shared with other services. The figure below shows a JWT with an asymmetric signature,
How it works
The figure below shows the process of generating a signature on the left. The method of verifying the signature is shown on the right.
As you can see, the issuer of the token only uses the private key. This implies that the private key can be kept in a confidential location, only known to the issuer of the JWT tokens. The public key can be widely distributed, so every consumer of the token can verify its integrity. The algorithms that generate such a signature are indicated with the “RS” prefix, as stated in the sample token shown earlier.
Asymmetric signatures in OpenID Connect
OpenID Connect is one of the most common protocols that uses this signature scheme. For example, when you use a “Login with Google” feature, you are using OpenID Connect. At the end of that process, Google provides an identity token to the application. This identity token contains information about your identity with Google. As a result, it tells the application who you are, making OpenID Connect an authentication protocol.
In the OpenID Connect scenario, Google uses their private key to sign the identity token. The application uses Google’s public key to verify its integrity, before relying on the embedded data.
Similarly, enterprise deployments using OpenID Connect for Single Sign-On (SSO) also rely on JWTs. The SSO service signs the tokens with a private key. Every consuming application verifies the integrity using the public key.
The benefits of asymmetric signatures
Asymmetric signatures are not limited to OpenID Connect. Every distributed scenario using JWTs should use this signature scheme. For example, in a microservice architecture where JWTs are exchanged, each service should have a public/private key pair. Compared to symmetric signatures, this scheme significantly reduces the impact of a breach of a single service in this architecture.
JWT Validation beyond signatures
Using JWTs securely goes beyond verifying its signature. Apart from the signature, the JWT can contain a few other security-related properties. These properties come in the form of reserved claims that can be included in the body of the JWT.
The most crucial security claim is the
exp claim. The issuer uses this claim to indicate the expiration date of a JWT. If this expiration date lies in the past, the JWT has expired and should not be used anymore. A typical example use case is an OpenID Connect identity token, which expires after a set period.
A second related claim is the
iat claim. This claim indicates when the JWT has been issued. It is often used to enable the consumer of the JWT to decide if the token is fresh enough. If not, the consumer can reject the JWT in favor of a newly issued one.
Third, JWTs can contain the
nbf claim. This abbreviation stands for “not before”. It indicates the point in time when the JWT becomes valid. A JWT should only be accepted if this timestamp lies in the past.
The fourth security-relevant reserved claim is
iss. This claim indicates the identity of the party that issued the JWT. The claim holds a simple string, of which the value is at the discretion of the issuer. The consumer of a JWT should always check that the
iss claim matches the expected issuer (e.g., sso.example.com).
The fifth relevant claim is the
aud claim. This abbreviation stands for audience. It indicates for whom the token is intended. The consumer of a JWT should always verify that the audience matches its own identifier. The value of this claim is again a string value, at the discretion of the issuer. In OAuth 2.0 and OpenID Connect scenarios, this value typically contains the client identifier (e.g., api.example.com).
Note that all of these claims are optional. Nonetheless, it is highly recommended to use them when issuing JWTs. Doing so can help prevent abuse when the JWT is exposed one way or another.
Below is a code example of how to verify these claims using the
java-jwt library. As you can see, the library offers dedicated functions to verify these claims. Check your libraries to find out how to optimally handle these claims.
Verifying reserved claims with the java-jwt library
Algorithm algorithm = Algorithm.HMAC256(HMAC_KEY); JWTVerifier verifier = JWT.require(algorithm) .withIssuer("sso.pragmaticwebsecurity.com") .withAudience("api.pragmaticwebsecurity.com") .build(); DecodedJWT verifiedJWT = verifier.verify(token); // Get the subject verifiedJWT.getSubject();
Cryptographic key management
One of the most challenging parts of using JWT is handling the cryptographic key material. Cryptographic keys used for signing and encryption need to be rotated frequently. Performing too many operations with a opens the application up to cryptanalysis attacks. Unfortunately, key rotation is often overlooked in applications using JWTs.
Key rotation is not an easy problem to solve. Rotating keys implies that multiple keys might be in use at the same time. Additionally, it requires the issuer and consumer to retrieve keys dynamically. So let’s take a look at how to handle key management for JWTs in practice.
Identifying a key
The suite of specifications on JWT provisions a few different options to identify particular cryptographic keys. The most straightforward mechanism is the “kid” claim. This claim can be added to the header of the token. It is intended to contain a string-based key identifier. An example is shown below.
With the key identifier, the consumer of a JWT can retrieve the proper cryptographic key to verify the signature. This simple mechanism works well when both the issuer and consumer have access to the same cryptographic key store. For example, a set of services running within one trust zone can all access the same key vault. The key identifier then identifies either a key used for an HMAC or a public/private key pair used for asymmetric signatures.
However, many scenarios require a more dynamic configuration. Imagine having to verify the identity token issued in an OpenID Connect flow. The keys used by the issuer will rotate frequently. If the issuer resides in a different trust zone, it is unlikely that both issuer and consumer have access to the same trusted key store. For those cases, the specification provides a more dynamic configuration mechanism.
A first option to distribute a public key is by embedding it directly into the header of the JWT. The consumer can retrieve the key and use it to verify the signature. The specification provides two mechanisms here. The first is the
jwk claim, which is designed to hold a public key in the JSON Web Key format. The second is the
x5c claim, intended to hold a public key in the format of an X509 certificate.
Embedding the key within the token is a straightforward way to enable key distribution. To ensure the security of this mechanism, the consumer of the JWT should restrict which keys it accepts. Failure to do so allows an attacker to generate tokens signed with a malicious private key.
An overly permitting consumer would merely use the embedded public key to verify the signature, which will be valid. To avoid such issues, the consumer should match the key used against a set of explicitly whitelisted keys. In case the key comes in the form of an X509 certificate, the consumer can use the certificate information to verify the authenticity.
The second option to distribute keys dynamically is using key URLs. The
jku claim is intended to hold a URL, which points to a file containing keys in the JSON Web Key format. Similarly, the
x5u claim is intended to do the same for X509-formatted keys.
The use of a key URL yields more flexibility, as well as smaller token sizes. Similar to embedding keys, the consumer will have to take a few precautions into account. The consumer should ensure that the key URL or keys belong to a trusted party. One option is to whitelist an entire key URL, or at least the domain of a key URL. Similar to embedded keys, x509-formatted keys can be authenticated using the properties embedded in the certificate.
Note that a key URL points to a file hosted on a server somewhere. Such a file may contain more than one key definition. To ensure the consumer can identify the right key, a key URL is typically combined with the
kid claim. In that case, the key identifier points to one of the keys in the key file.
Finally, the consumer may opt to use a caching mechanism for the key URLs, to improve performance.
Using JWTs in practice
So far, we have talked about the technical properties of JWT tokens. However, using JWTs in practice requires careful consideration of the security properties of a JWT.
In most cases, JWTs are used in the form of bearer tokens. A bearer token is a token that can be used by anyone that possesses it. Consequentially, obtaining a JWT suffices for an attacker to start abusing the privileges associated with that token. In this final section, we will briefly highlight a few use cases.
JWTs in OpenID Connect
We have mentioned the use of JWT in OpenID Connect before. The provider issues an identity token to the client. That identity token contains information about the user’s authentication with the provider. The identity token is a JWT token, signed with the provider’s private key.
OpenID Connect went through great lengths to improve the security properties of the identity token. For example, the protocol mandates the use of the
aud claims. Additionally, the token includes a nonce to prevent replay attacks. Because of these requirements, abusing a stolen identity token becomes hard or even impossible.
JWTs as OAuth 2.0 access tokens
An OAuth 2.0 access token is another good use case of a JWT. Such an access token gives a client application access to a protected resource, such as an API. When the access token comes in the form of a JWT, it contains all the authorization information as the payload. To protect the data, the issuer signs the token using a private key.
OAuth 2.0 access tokens are bearer tokens. The original specification considers this to be a significant risk. Consequentially, it mandates that access token should have a short lifetime (i.e., 5 to 10 minutes). The short lifetime prevents the window of abuse in case an access token is stolen. Additionally, modern additions to the specification address the bearer token properties by introducing proof-of-possession mechanisms.
JWTs as session objects
Protocols such as OpenID Connect and OAuth 2.0 actively try to address the weaknesses of JWTs. Unfortunately, we also observe many applications that incorporate JWTs into their architecture, without taking these precautions into account.
A concrete example is an application using JWTs to store authorization state on the client. This enables the use of a stateless backend, which makes deployment significantly easier.
However, such a client-side token is a bearer token. If it becomes compromised, it can be used without restrictions by whoever possesses it. Not having a short lifetime or revocation mechanism in place makes such a scenario extremely vulnerable.
Diving into all these details would take us too far for this article. If you want to know more about these issues, we recommend reading through this post on using JWTs for sessions, and this post an using reference tokens.
As you have seen in this article, there’s a lot more to JWTs than merely using a symmetric signature. Most JWT deployments require the use of asymmetric signatures to ensure security.
Additionally, verifying the signature of an incoming JWT is only the first step. Next, the consumer should check the reserved
nbf claims to ensure that the JWT is valid. The
aud claims need to be verified to ensure the JWT is used in the proper context.
Finally, using JWTs requires to setup proper cryptographic key management. JWTs offer a variety of options to manage keys. One way is to identify a particular key using an identifier. More advanced options enable the embedding of a key in the token, or the retrieval of a key from a dedicated key URL. Regardless of the mechanism, the consumer always needs to verify the validity of the key before trusting it.
We have covered much ground in this article. This JWT Security Cheat Sheet provides an overview of all these best practices. It allows you to keep track of these things while you’re building your application.
Want to learn more about OAuth 2.0 and OpenID Connect?
Save yourself days of digging through dozens of specs with this online courseMore information
Dr. Philippe De Ryck
Hi, I'm Philippe, and I help developers protect companies through better web security. As the founder of Pragmatic Web Security, I travel the world to teach practitioners the ins and outs of building secure software.
Talks and workshops
Security is often about small nuances. In my articles, I dive deeper into various security topics, providing concrete guidelines and advice. My articles also answer questions I often get while speaking or teaching.
Getting security right is all about knowledge. I strongly believe in sharing that knowledge to move forward as a community. Among my resources, you can find developer cheat sheets, recorded talks, and extensive slide decks.
Subscribe to the Pragmatic Web Security mailing list to stay up to date on the latest activities and resources.