When I started learning about JSON Web Tokens, there were some things that were straightforward to understand β and some concepts that felt like "hidden secrets" of JWT lore. π§ββοΈ
This article aims to demystify signing and validating JSON Web Tokens, with little need for security or cryptography knowledge.
Note: This article is a companion to my post on Authorization and Authentication for Everyone. I recommend that you read that post first.
JSON Web Token (JWT) Recap
JWTs are β in general terms β reasonably approachable. Information about them is readily available from many sources, chiefly covering:
- JSON Web Tokens (or JWT) are a compact, URL-safe way to transfer pieces of data between two parties (such as an authorization server and an application).
- The JWT format is defined by IETF specification RFC 7519 and is composed of three segments (a header, a payload, and a crypto segment.
- JWTs are signed with a key when they are generated and then validated with a key upon receipt so we can verify that they haven't been modified in transit.
The Challenge with Understanding JWT
There's a meme about being introduced to a concept and then there are steps missing while you're expected to be able to get to the end result without knowing the steps in between. The meme demonstrates "How to draw an owl":
To me, not being able to find approachable information about signing and validating JWT felt like the missing steps to draw the owl.
Most of the resources I dug up took me deep down the rabbit hole β until my head was swimming with mind-melting jargon, Alices and Bobs (placeholder names used in cryptography examples), and complex maths. Instead of learning simpler, incremental steps to aid in my understanding, it felt like I was being given ten more complex, complete owl drawings.
Why Don't We Need to Know How Signing and Validation Work?
Why is this? Why do most JWT resources simply say "and then you sign and validate" and leave it at that?
Think of it this way: for human beings to be effective or skilled with a tool, we don't need to know the intricacies of the tool's components.
π When you get into your car to drive to the grocery store, do you need to know how internal combustion works? You don't.
π When you charge your laptop, do you need to know what chemical reactions take place in a lithium ion battery to create the flow of electrons in a circuit to store and produce energy? Nope!
We can still be good drivers or great computer programmers without intimate knowledge of these things. We trust that the manufacturers have used their expertise, specifications and standards, and due diligence to make useful tools for us to be more effective at the jobs we need those tools for.
This is why you don't need to know the exact process for signing and validating JWT in order to effectively use them for authenticating and authorizing your applications and APIs.
Okay, but you're reading this because you still want to know the missing steps to draw the owl. While you generally should not sign and validate tokens yourself (seriously, leave this to the experts β identity providers and Identity-as-a-Service platforms!), knowing how it works can be helpful in making you feel more comfortable with using JWTs. π
Anatomy of a JSON Web Token
We covered the anatomy of JWT in depth in the previous blog article on authentication and authorization, but let's do a very brief recap.
JSON Web Tokens are composed of three URL-safe string segments concatenated with periods .
Header Segment
The first segment is the header segment:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
The header segment is a JSON object containing a signing algorithm and token type. It is base64Url
encoded (byte data represented as text that is URL and filename safe).
Decoded, it looks something like this:
{
"alg": "RS256",
"typ": "JWT"
}
Payload Segment
The second segment is the payload segment:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
This is a JSON object containing data claims, which are statements about the user and the authentication event. This is the information that the JWT is conveying from one entity to another. Data claims might look something like this:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
This is also base64Url
encoded.
Crypto Segment
The final segment is the crypto segment, or signature. JWTs are signed so they can't be modified in transit. When an authorization server issues a token, it signs it using a key.
When the client receives the ID token, the client validates the signature using a key as well. (If an asymmetric signing algorithm was used, different keys are used to sign and validate; if this is case, only the authorization server holds the ability to sign tokens.)
The Rest of the Owl Signature
This is where the missing steps of the owl JWT signing / validation process are. Let's go through it step-by-step.
RS256 Signature
For this article, I'm going to assume use of an RS256 signing algorithm. RS256 is an RSA Digital Signature Algorithm with SHA-256. RSA256 is an Asymmetric Key Cryptography algorithm, which uses a pair of keys: a public key and a private key to encrypt and decrypt. In this case, the private key is used by the token issuer (authorization server), and the public key is used by the application receiving the token in order to validate it.
If this feels jargon-laden and confusing right out of the gate, don't worry!Β Let's break it down into smaller, distinct steps.
The JWT Crypto Segment: Revisited
Let's explore what the crypto segment actually is.
Signing Input
First we take the first two segments of the JWT (the header and the payload). In practice, that looks something like this:
In other words, this is the base64Url
encoded header and the base64Url
encoded payload, concatenated with a .
period:
This is what we call the signing input.
Hashing
We then hash the signing input using the SHA-256 hashing algorithm. Hashing converts one value into a different value. A hash function uses a mathematical algorithm to generate a new value from an existing one.
- Hashing is irreversible. Once we have hashed an input, we cannot get back to the original input again.
- Hashing will always produce the same output for the same input.
- No two different hashing inputs will produce the same output.
At this point, we have a hash of the header and payload segments β which we could compare to other hashes, but cannot reverse to return to the original signing input.
Encryption
Next we encrypt the hashed signing input. Unlike hashing, encryption is reversible: we can decrypt encrypted results (called ciphertext) to decipher the original input (plaintext).
For this step, the token issuer (authorization server) scrambles the hashed signing input using a private encryption key to produce an output (ciphertext).
Note: I mentioned earlier that this example uses RSA, which is an asymmetric signing algorithm and uses one key to encrypt tokens, and another key to decrypt them. To learn more about this topic in an approachable, practical way, check out Public Key Cryptography by Computerphile on YouTube.
This final output (the hashed, encrypted, encoded header and payload) is the crypto segment β or signature β of the JWT.
Note: If you'd like to dig much deeper into specifics of the RSA encryption algorithm itself, I recommend these great YouTube videos from Eddie Woo: The RSA Encryption Algorithm.
There you have it. That's the signature of a JSON Web Token. π
Validating the Signature
Okay, we can start to understand how the token was signed β but that's only half of the story! The entire point of signing the token is so that whoever receives the token can validate that this JWT contains data that hasn't been tampered with.
How do applications validate JWTs?
What Are We Working With?
First, let's look at what information is available to the application that receives the token. We have access to the JWT itself: the header, the payload, and the signature (aka, the crypto segment). We also have access to a public key, which β as per its moniker β is freely available to the world.
Note: How are public keys made available? JSON Web Key (JWK) RFC 7517 defines a specification for JSON representation of a cryptographic key. If you use an authorization server platform, the public key will be provided β and often helpfully encapsulated in an SDK or library that handles JWT validation for you. You can read more in this blog article: Navigating RS256 and JWK.
Remember that the signature is the encrypted, hashed header and payload. Hashing is irreversible, but encryption can be decrypted (in this case, with the public key). Validation of the JWT is about getting to a point where we can effectively compare what we received to what we expect.
Decode Claims
The application can decode the header and payload to get some information. Recall that these two segments are base64Url
encoded to make them URL safe. There is nothing cryptographically magical or secure about decoding these segments; in fact, you can do so with a simple online base64 decoding tool. Once they're decoded, we can easily read the information in them. For instance, we could decode the header segment to see what algorithm the JWT says it was signed with.
On the other hand, the signature's purpose is to validate that the information in the other segments is the same information that the authorization server sent β and that it hasn't been changed along the way.
From the decoded header, we can see:
{
"alg": "RS256",
"typ": "JWT"
}
Our received token says that the algorithm is RS256. Our application should be configured to expect the algorithm that our authorization server (token issuer) uses. When we read the algorithm in the JWT's header, we should verify that it matches our configured expectation. If it doesn't match, we should reject the token outright.
Hashing (Again)
If the algorithm in the token matches our expectation of RS256, we know we need to generate the SHA-256 hash of the header and payload segments. This will reproduce the signing input hash (again). Remember that hashing is irreversible, but the same input will always produce the same output. If we generate the hash from the header and payload we received, it should match the header+payload hash that is encrypted in the signature.
Therefore, we will hash the concatenated, base64Url
encoded header and payload. Now we have the signing input hash freshly calculated on the application side.
Decryption
The hashed signing input is also in the signature, but it's been encrypted by the authorization server (token issuer) with a private key. The application has the public key, so we can decrypt the signature. Once this is done, we have access to the original hash: the one generated by the authorization server when the token was first generated.
Compare Hashes
Now we can compare the decrypted hash to the calculated hash. If they are the same, then we've verified that the data in the JWT header and payload segments has not been modified between the time the authorization server created the token and the time our application received it. π
+ Verifying Token Claims
Once we've validated the signature, there's more to verifying the JSON Web Token's data. Claims in the payload segment should also be validated, because they contain information about the token issuer, token expiration, intended audience for the token, information binding the token to the authorization request, and more.
This data gives the receiving application vital details that the signature validation alone does not. For instance, examination of claims can reveal that a technically valid token was actually intended for a different application or user, has expired, came from an issuer that the application has no affiliation with, etc.
JWT Signing & Validation: Wrapping Up
We've now covered signing JWT and validating JWT signatures.
My hope is that you feel confident your understanding of JWT signatures and validation has a few more steps filled in now:
It's still important to reiterate that, as a developer, you most likely will never need to implement these processes yourself. And in fact, unless your engineering focus is security and identity, you probably shouldn't.
Identity platforms like Auth0, Okta, Ping Identity, and more do all of this for you: issuing and signing tokens on the authorization server side, and providing SDKs and libraries for validation and token management on the application or API side.
Resources and What's Next?
As I mentioned at the beginning, this article is a companion and supplement to Authorization and Authentication for Everyone, which is a much more comprehensive resource on the history of OAuth, OpenID Connect, authorization servers, JSON Web Tokens, API authorization, delegation with scopes, and more. Please check it out if you'd like to go further into identity topics.
Learn More
In an effort to create a digestible introduction to signing and validating JWTs that is widely approachable (and is the introduction I wished I'd had), I have simplified topics that are incredibly rich and complex.
π§ To learn much more, try starting with some of the following resources:
- Public Key Cryptography
- What Are Encryption Keys and How Do They Work?
- The RSA Encryption Algorithm - Part 1
- The RSA Encryption Algorithm - Part 2
- Learn Identity
- JWT.io
Thank You!
If you'd like to chat, I'm available on Twitter at @KimMaida, and I also speak at conferences and events β at least, I did before COVID-19. I hope to see you sometime, and thank you so much for reading!
Top comments (16)
Awesome! Very short, undestandable, and informative!
This article really is a big help to me. So, I enrolled on this site to thumb up your article!!
This is fantastic, great work! The combination of hashing plus encryption makes for a lot of confusion for folks getting started down this road, regardless of dev experience.
One thing to note on the algorithm in the header ... I would recommend including that in the configuration of the app that is doing the validation. So instead of asking the token βhow should I verify your signature?β the application checks that the header matches the signature type it is expecting, rejects the token if itβs different, then uses the key it has to verify.
Great note, thanks Josh! I will update the article to reflect this. And that's generally done by the SDK / library you'd be using to validate, yes?
Generally, yes. You should be able to tell that library βhey, I only want to validate RS256 tokensβ and the library should reject everything else.
Great article, really fantastic. But I just have a small doubt. I don't think we can decrypt the signature obtained in JWT using the public key provided by the authentication server.
Please let me know if I am wrong.
The purpose of the public key is indeed to decrypt the signature so that the client can validate the token by comparing the hashes. The private key is used by the authorization server to sign tokens.
This is a very old response now, but for others who hit this page - there is a confusion of terms here. The content isn't encrypted, but rather cryptographically signed. mritunjay is correct that you cannot decrypt with a public key, but you can verify a cryptographic signature with a public key.
Really, fantastic explanation, especially for juniors and students (almost like me), thank you!
Dear Author @kimmaida, one little note
This typo can lead mind-reader to the wrong understanding. Could you please correct?
Great catch, thank you so much! This has been corrected.
Your post was stolen by another person and posted here:
freecodecamp.org/news/how-to-sign-...
Thank you so much for bringing this to my attention!
The tutorial is great, it explains a lot that is bypassed in other postings.
Thank you!
Anyway I have some doubts regarding the validation procedure: does it really make sense to reconstruct the public key from its constituents as explained?
Why not take the certificate ("x5c" in the JWKS), check its validity and only then (if valid) retrieve the public key from it (which is easier)? If done in the way proposed in the tutorial, the certification of the public key is not checked which could e.g. mean that I would use an expired or not yet valid public key as valid. One could also check the issuer of the certificate.
Google even rank the article before the original!
@kim
So true about the draw the owl thing and thank you for the post!
Some comments may only be visible to logged-in visitors. Sign in to view all comments.