Intro
Have you ever wondered how you're able to stay logged in after you've closed and re-opened an application on your smartphone? One thing's for sure, the app (hopefully) doesn't store your credentials and include them into every single request that is made to their API. Instead, the famous JSON Web Tokens (or JWTs as we lovingly call them) are used.
In this post, we'll have a look at the characteristics, structure, lifecylce as well as some possible security issues attached to a basic JWT implementation.
๐ณ๏ธโ๐ Love Is Equality
Before we get started, I quickly want to explain the gifs in this post. I know, pride month is already over (it took me longer than anticipated to write this story ๐ ) but there's always the right time to spread equality, right? As a member of the LGBTQ+ community ๐ณ๏ธโ๐, I am proud of the progress we've achieved over the last few decades. However, we're still not done yet. There are still people being harassed because of their sexual orientation - everyday. That must change.
Therefore, today's gifs are handpicked snippets from Taylor Swift's LGBTQ+ anthem You Need To Calm Down (video integrated at the end) which is part of her seventh studio album, Lover. On that note, SHADE NEVER MADE ANYBODY LESS GAY my fellow devs ๐ณ๏ธโ๐
Characteristics
JSON Web Tokens are used to securely transmit data between a client and the server. In order to avoid third parties modifying the transmitted data, every token is digitally signed using public/private key pairs (RSA or ECDSA) or secrets (HMAC algorithm).
As the public/private key pair approach is extremely common, this post focuses on it henceforth.
If some third-party now catches one of the used tokens and modifies them, the signature changes. If the token is then sent to our API, it will be rejected as the signataure can't be verified. However, security is only enforced as long as your private keys stay private - they are used to create the signatures. If a malicious party ever gets a hold of your secret key, it's possible for them to freely modify the payload of tokens without the signature becoming invalid. In other words, this would be a disaster.
Public keys on the other hand are used to validate incoming tokens. They cannot be used to create token signatures, wherefore they are less sensitive.
You don't have to worry too much about the signature mechanisms though - implementing JWT only requires you to have a private/public keypair and one of JWT.io's official libraries.
โ Keep in mind: Although JWTs are protected from unauthorized modification, the carried data is freely readable. Therefore, never put passwords or other secret data inside the token's payload unless you've properly encrypted it.
Structure
JSON Web Tokens consist of three parts: header, payload and signature - each Base64Url encoded and seperated by a dot.
Therefore, the structure of JWTs looks something like this: xxxxx.yyyyy.zzzzz
Header
This is the first part of our token. The header usually consists of two parts, the type typ
and the algorithm alg
. Thereby, the type field describes the type of the token (which is JWT in our case) and the algorithm field the used signing algorithm (e.g. HMAC SHA256 or RSA).
Example:
{
"alg": "RS256",
"typ": "JWT"
}
The Base64Url encoded JSON then serves as the first part of our token.
Payload
This is the middle part of our token. The payload's JSON fields are referred to as claims.
There are three different types of claims:
- registered claims
- public claims
- private claims
Registered claims: a set of predefined claims which aren't mandatory but recommended. These are issuer iss
, expires at exp
, issued at iat
and others. As these claims play a special role, you want to make sure that there aren't any naming collisions when creating private claims.
Public claims: can be freely defined by the users of JWTs (you/your API/the thing that generates the tokens). However, in order to avoid naming collisions, it is recommended to stick to the names provided in the IANA JSON Web Token Registry.
Private claims: are neither registered nor public claims and are used to share information between parties that agree on using them. In other words, these are claims that don't match any of the claims provided by the IANA registry mentioned above.
As you can see, the difference between public and private claims is small. It's really just a convention thing and nothing too bad will happen if you don't stick to them. However, this does not apply to registered claims, so always make sure you don't misuse them with, for example, user data.
Example:
{
"exp": "2020-07-10T16:13:07.182Z",
"sub": "1313131313",
"name": "Taylor Swift",
"locale": "US",
"faveDish": "Cupcakes"
}
Assigning the above claim categories to our example, exp is a registered claim and faveDish a private claim (doesn't match any of IANA's claims). All the other ones are public claims.
The Base64Url encoded JSON then serves as the second part of our token.
Signature
This is the last part of our token and really simple to create if you're using one of JWT.io's official libraries. In order to sign the token, the token header & payload, a secret as well as the specified signing algorithm are needed. In our case, this will be RS256 (RSA Signature with SHA-256) as we specified that in the header.
Example:
RSASHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
Signing a token with the jwt Node.js library:
const jwt = require("jsonwebtoken");
const access_token = jwt.sign(accessTokenPayload, used_access_secret, {
expiresIn: 3600,
algorithm: "RS256",
});
Lifecycle
Very nice. We've already learned what JWTs are and what they consist of. Now we'll discover the typical lifecycle of a JSON Web Token. Take a look at the graphic below which visualizes what I'll explain in this section.
In order to obtain a new JSON Web Token, our client makes a login request to our server, providing the necessary credentials in the request body. If the credentials are correct, the server will create a new token pair consisting of access and an refresh token. Now, what's the difference between those?
Access Tokens are used for regular requests (e.g. post something or change profile picture) and contain the claims that are required for those. Refresh Tokens, on the other hand, are only used in order to obtain a new token pair in the event that the access token expires. Therefore, they normally have less claims inside the token's payload.
Usually, access tokens are short-lived (valid for less than 1 hour) and refresh tokens long-lived (multiple weeks or even months of validity).
Let's move on with our lifecycle.
The token pair has now been created and is sent back to the client which (hopefully) stores them safely. Using the access token, the client is now able to perform the necessary requests. The refresh token is now put aside for the time being.
After some time, the client again tries to perform a request. This time however, the access token has expired and the server returns a 401 - Unauthorized
error and a response body stating that the token has expired. Now it's time to grab our trusted refresh token and send it to the according endpoint on our server. After the token has been validated successfully, a new token pair is returned and the story starts all over.
Possible Security Issues
Did you notice something while reading above lifecycle? This default approach is full of possible exploits. Just imagine the event of token theft, where a third party gets a hold of a user's access or (even worse) refresh token. They would be able to illicitly make requests for that user. Sure, the access token is short-lived and damage might be limited - but what if somebody obtained the refresh token? Those are long-lived and can be valid for multiple months, thereby enabling malicious requests for a long period of time.
Another aspect that should be considered is that a user might want to log out from all sessions (just like with Netflix accounts, you know, the ones we all share with parasites โค). Without the ability to revoke tokens, revoking sessions is impossible.
Solving above security issues is quite complex and I'll write an entire article about it - for now just know that it's crucial to make tokens revokeable by assigning them unique IDs and keeping track of those in a database such as MongoDB.
Conclusion
Okay, so what can we conclude after reading this post? First of all, JWTs consist of three Base64Url encoded parts: the header, payload and signature. The data that is stored inside the tokens payload is protected from unauthorized modification, however freely readable for everybody. Therefore, make sure to never put secret information inside the payload unless you've encrypted them properly.
Additionally, JWT's usually come in pairs: access & refresh token. The access token is used for regular requests and the refresh token is required to obtain a new pair once the access token has expired. Unfortunately, using this approach leads to security vulnerabilities. Resolving them requires a more sophisticated approach which I'll cover in a future post.
Outro
Congrats, you've taken your knowledge of JSON Web Tokens to the next level ๐ I sincerely hope you had a nice read and learned something new. As always, if you have any questions, just comment them and I'll try my best to help you out :)
I would love to hear your feedback! Let me know what you liked or what could've been better with this tutorial - I always welcome constructive criticism! If you have any questions, just comment them and I'll try my best to help you out :)
In love with what you just read? Follow me for more content like this ๐
Sources
ยฉ GIFs from Giphy
๐ Characteristics & Structure inspired by JWT.io's documentation
๐ณ๏ธโ๐ SHADE NEVER MADE ANYBODY LESS GAY ๐ณ๏ธโ๐