JSON Web Tokens (JWTs)
JSON web tokens, simply referred to as JWTs (pronounced as “jot”) is an open standard that defines a mechanism to securely communicate between two parties. Before getting into JWTs, let’s understand the terminology behind the concept.
Path Towards JWT
Almost all of the web based applications exist today use internet protocols to communicate between the involved parties. That could be between a client browser and a server, or a client application and a server, or a client and an identity provider, or a server and an identity provider, etc. We know that http is stateless meaning each request is served independently without having any sort of knowledge about the previous requests that had been already served. Now consider a server utilizing stateless protocols which means the server doesn’t keep the state information saved. For a simple website this is fine as longer as it only needs to serve the requested web content. But what if the server needs to send a dynamic response depending on the requested or authenticated client? One of the most popular ways to tackle this problem is to use tokens. When authenticating, the server creates a token for you and you send it to the server with every subsequent request that you made. There are two popular token approaches widely used today namely; session tokens and json tokens.
Let’s first understand the session token approach. In layman’s terms, you login to a website and upon successful authentication, the server creates a session for you and stores it in the server. Then the server responds with a session ID associated with the session entry created. You send this session id with every subsequent request to the server and the server identifies who you are until the session is valid.
This approach works in many cases but has some drawbacks. First it assumes a single monolithic server structure which is no longer the case. To handle the load efficiently, most of the modern web servers follow a load balancer approach where the requests are being served from multiple server instances. Even Though the solutions like shared caches and sending the subsequent requests to the same server instance may work, those won’t scale well and may not be suitable for modern micro-service architectures.
Instead of storing the session information in the server and giving a session id to the client, what happens if we return all the session information to the client so that they can bring the session information with every subsequent request? This approach could work right? Serving requests from different server instances without authenticating each time will no longer be a problem. But how can we be certain about the integrity of the data? Some malicious client could modify the session data and try to access unauthorized information. To tackle this, we can securely sign the session data when sending to the client and verify it upon every subsequent request. The server trusts the session data only if it is valid. This is the basis for the JSON web token approach. Here, the session information or the token is returned to the client as a JSON object. If you are new to JWT, at this point you might be thinking storing and sending a JSON object with every subsequent request is very painful. Well!! JWT doesn’t really look like a JSON object. I recommend you to follow along with this blog post to understand the terminology and structure of the JWT.
JSON Web Tokens (JWT)
JSON web tokens are a compact way to represent claims in a space constrained environment. A JWT is defined as follows in the RFC 7519 specification. RFC 7519 is the standard or the specification which defines how a JWT should be structured and how we should use it.
“A string representing a set of claims as a JSON object that is encoded in a JWS or JWE, enabling the claims to be digitally signed or MACed and/or encrypted.”
A claim is a piece of information about a subject such as a user being authenticated. Claims are represented as key value pairs where the key is a string. According to the definition, JWTs represent a set of claims as a JSON object. In JWT, this JSON object is encoded as a one single string value. JWTs can be divided into two types namely; JWS (JSON Web Signature) and JWE (JSON Web Encryption). In this blog post, we will study the structure of the JWS which is the widely used JWT format.
JWT Structure
Below is a sample JWT value (more precisely a JWS).
As you can see, this is a string consisting of 2 periods rather than a JSON object. These two periods (“.”) separate the JWT into three sections namely; header, payload and the signature.
Header
“eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9”
First section of the token is the JWT header, which is a base64Url encoded JSON object. The JWT header contains information about how the json web token is being signed. It can also contain the information about the media or content type of the data in the token. Let’s decode this value and see the content. You can use any publicly available base64 decoding tool like https://www.base64decode.org/ or a JWT token decoder like jwt.io.
As you can see, the content of the JWT header (or any other section of the JWT) is visible to any observing party. Anyone can decode the JWT token and see the content. Now you might be thinking why we need to encode the content in base64Url encode? Why can’t we just use the plain JSON object or a JSON string as it is? Well! Here the base64 encoding is just for the convenience and compact it to a less space oriented format. This encoding is not to hide anything.
The following header parameters can be present in a JWT header section.
- “typ”: Type header parameter is used by JWT applications to define the media type of the complete JWT. The intention is to let the application know this is a JSON web token when values that are not JWTs could also be present in the application. This parameter can be ignored by JWT implementations.
- “alg”: Algorithm header parameter represents the cryptographic algorithm being used for the signing process. HS256, RS256 and HMAC are the commonly used singing algorithms. Some JWTs can also be created without a signature/ encryption and they are referred to as unsecured JWTs. In such cases, the algorithm header parameter should have the value “none”.
- “cty”: Content type header parameter is used to represent the structural information about the JWT. Usually this parameter is not recommended. However in cases where nested signing or encryption is applied, “cty” parameter must be present in the token header. The value should be “JWT” to indicate that a nested JWT is carried in the JWT.
Payload
“eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ”
The second part of the JSON web token is the payload, which contains the claims. As I already mentioned, claims are information about a subject and some other additional data. The payload section is also a base64Url encoded JSON object. One thing to keep in mind is not to put any confidential information about the user in this section as it is publicly visible to any outside party. The decoded value of the above JWT payload looks like below.
There can be three types of claims in a JWT; registered claims, public claims and private claims.
Registered Claims:
These are a set of registered claim names in the “IANA JSON Web Token Claims” registry which are not mandatory, but recommended as a starting point for a set of useful information.
- “iss”: The issuer claim represents the principal or the party that issued the JWT. This is a case-sensitive string containing a StringOrURI value.
- “sub”: The subject claim represents the subject of the JWT. This is a case-sensitive string containing a StringOrURI value.
- “aud”: The audience claim represents the recipient that the JWT is intended for. Each principal processing the JWT must identify itself within the value of the “aud” claim when it is present. This value can be a case-sensitive StringOrURI value or an array of case-sensitive StringOrURI values.
- “exp”: The expiration time claim represents the expiration time on or after the token should not be accepted for processing. Value of this claim should be a NumericDate value. Application implementations may usually provide some leeway to account for clock skews.
- “nbf”: The not before claim represents the time before the token must not be accepted for processing. Value of this claim should be a NumericDate value. Application implementations may usually provide some leeway to account for clock skews.
- “iat”: The issued at claim represents the time at which the JWT was issued. Value of this claim should be a NumericDate value.
- “jti”: The JWT identifier claim provides a unique identifier for the JWT token.
Public Claims:
Claim names can be defined at will by those who are using JSON web tokens. However to avoid name collisions, they should either be registered in the “IANA JSON Web Token Claims” registry or be defined as a collision resistant name (public name).
Private Claims:
Private claims are the claim names that are agreed upon to use by producers and consumers of the JWTs but are neither registered claims or public claims. These claims are subject to collisions.
Signature
“SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c”
The third part of the JSON web token is the signature which only the server can calculate. This is used to verify the authenticity of the token. Signature is calculated by combining the base64Url encoded JWT header with the base64Url encoded JWT payload using a period (“.”) and then encoding with the secret key which only the server has. The algorithm used to sign the token is defined in the header “alg” field.
JWT Flow
JWT flow is pretty much easy to implement, however, it is usually implemented with some other authentication mechanisms such as OpenID connect (OIDC). In this blog post I’m not going into the implementation details of JWT, rather focus on the high level flow.
As the first step, the client authenticates with the server using whatever the available methods in the authentication flow, usually the username and password. Upon successful authentication, the server creates a JWT for future authorization purposes. One important point to remember here is that the JWT is not for authentication, but for authorization.
Then the server signs the JWT using it’s private key and sends to the client. The client should store the JSON web token for future uses. How to store the token is dependent on the implementation details of the client application. Usually the web applications keep it in cookies, however JWTs can also be stored in local storage, session storage or any other preferred method.
JSON web tokens have to be passed with every subsequent requests from the client to the server. The JWT is sent in the http header as a key value pair.
Authorization: Bearer <JWT>
The server performs base64UrlEncode(header) + “.” + base64UrlEncode(payload) and calculates the signature and then verifies if it matches with the signature in the token.
Important Points
Below are some of the important details to keep in mind when implementing JSON web tokens.
- JWTs are pretty much open and the values are exposed to anyone. Therefore no confidential or sensitive information such as passwords, security numbers, NIC, etc should be included in the JWT.
- JWT is not for authentication, but for authorization. Authentication should be performed using whatever the available methods such as basic auth (username and password).
- What happens if a malicious user or an attacker alters the JWT payload and creates a new token calculating the signature or attaches the altered payload to the older token? This is pretty much possible since most of the required details including the signing algorithm is publicly visible and anybody can base64Url encode the JWT content. Well! First the malicious user cannot sign the JWT validly since the secret key is not publicly available. Hence the attack can be detected by validating the token at the client or server. In the client, this is performed using the public key of the server which is usually exposed through the jwks endpoint (in OIDC flows). Secondly, attaching the altered payload to the previous JWT will also fail the validation since the signature won’t match with the tampered payload.
- What happens if a malicious user steals someone else’s JWT and impersonates him/ her? This is technically possible and can get unauthorized access. This is the reason why usually the JWT is implemented in conjunction with other secure mechanisms such as OAuth and OIDC. All the network communications should be performed under https protocol.