If you have worked on API authentication, then usual practice to create a unique hash for a client, store it in the database and verify that hash (present in cookie, head, or body of the request) against the incoming requests every single time. That means, we have to make a database query every single time client is accessing the restricted area for authentication. That’s not good for performance and user experience.
Hence, JWT was invented. It works in the same way I have explained above, except for the database query part. JWT or JSON Web Token is a string that is sent in the HTTP request (from client to server) to validate the authenticity of the client. But now, you don’t have to save JWT in the database. Instead, you save it on the client-side only.
JWT is created with a secret key and that secret key is private to you which means you will never reveal that to the public or inject inside the JWT token. When you receive a JWT from the client, you can verify that JWT with this that secret key stored on the server. Any modification to the JWT will result in verification (JWT validation) failure.
A JWT is simply a string but it contains three distinct parts separated with dots (.).
var HEADER_HASH = base64(header);
var PAYLOAD_HASH = base64(payload);
var SIGNATURE_HASH = base64(signature);var JTW = HEADER_HASH + '.' + PAYLOAD_HASH + '.' + SIGNATURE_HASH;
//JTW ~ xxxx.yyyy.zzzz
A JWT is three base64 encoded parts concatenated with dots.
header
header is simply a JSON string but it contains information about the algorithm of JWT encryption. Important fields in this JSON object are type and alg. type is always JWT. You can choose alg which stands for algorithm from HS256, RS256 and others of your choice.
var header = '{"typ":"JWT", "alg":"HS256"}';
In this case, we used a symmetric key algorithm HS256.
payload
payload is any data that you want to include into JWT, it is also a JSON string. This data is base64 encoded as you can see from the syntax of the JWT above, hence you should be absolutely sure that there is no sensitive information as anyone can decode it and read it. Information like password and email should not be passed into the payload.
You can add as many fields in the payload as you want but you should not add more than 5-6 fields to keep the size of JWT small. There are some standard fields that JWT specification recommends to add (but not necessary) like iss for issuer, sub for subject, exp for expiration time of token and there are a couple of others which you can read on . So a typical payload will look like below
var payload = '{"userId":"1101001", "name":"John Doe", "exp":"Sun Apr 25 2018 22:42:28 GMT+0530 (India Standard Time)"}';
payload also called as claims because when a client sends a JWT for verification, in a request, he is claiming that this information belongs to him/her. If this data is tampered with, JWT will be invalid. How do we know that? Let’s see that in the next section.
signature
signature is an encrypted string. Whatever algorithm you choose in the header part, you need to encrypt the first two parts of JWT which is base64(header) + '.' + base64(payload) with that algorithm. This is the only part of JWT which is not publically readable because it is encrypted with a secret key. Unless someone has the secret key, they can not decrypt this information.
It’s time to create a JWT on our own and check how it looks. There are many libraries available in different programming languages to create and verify JWTs but in this example, we will create JWT in node.js using jsonwebtoken () module. You can see recommended libraries for JWT creation and verification on .
npm install jsonwebtoken
jsonwebtoken module helps us in a lot of ways like we don’t have to create header part manually, it will do on its own based on some input configurations. Also encoding and encryption work will be done by it as well.
We are going to create a JWT using sign method provided by this module. The syntax for this method is as below.
jwt.sign(payload, secret, [options, callback])
callback should be the last parameter and it is optional. If callback is provided, sign becomes asynchronous and we get token inside callback function. If callback is not provided, it returns the token.
options is also optional and we can pass some extra metadata to the JWT. The important field of options object is algorithm which defaults to HS256. You can find other options . You should use options object which injects some of the recommended fields like expiresIn, subject etc. into JWT instead of adding manually into payload by yourself. Generated JWT will include an iat (issued at time) claim by default unless noTimestamp is specified in options. expiresIn field in options is used to add exp field in payload. // test.jsconst jwt = require('jsonwebtoken');
const SECRET = 'MY_SECRET_KEY';var token = jwt.sign({
userId: '110095',
name: 'John Doe',
roles: ['writer', 'moderator']
}, SECRET, { expiresIn: "7d" }); // default: HS256 encryptionconsole.log(token);
The above code will generate below JWT.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMTAwOTUiLCJuYW1lIjoiSm9obiBEb2UiLCJyb2xlcyI6WyJ3cml0ZXIiLCJtb2RlcmF0b3IiXSwiaWF0IjoxNTIzODE3OTk5LCJleHAiOjE1MjQ0MjI3OTl9.rrpyIW0ftJGna1c_SHH6Hus8hBd2-CUqofaSIUHj0C0
I hope you can clearly see 3 parts of JWT separated by (.) dots. As we know, the first two parts are base64 encoded, we should be able to see the content in it. For that, let’s go to a website that decodes JWT.
From the above screenshot, we see header and payload part but we can’t verify a JWT unless we have a signature that was used to encrypt JWT. Since this is just a test and we know the signature, let’s try to verify the JWT.
A JWT is verified using verify function and its syntax is as below.
jwt.verify(token, secret, [options, callback]);
callback(err, payload) function is optional. If not provided, verify method will either return the decoded payload or throw an error.
options object is optional and contains some fields like algorithm, issuer, subject etc. to add extra verification checks.
// test.jsconst jwt = require('jsonwebtoken');
const SECRET = 'MY_SECRET_KEY';var token = jwt.sign({
userId: '110095',
name: 'John Doe',
roles: ['writer', 'moderator']
}, SECRET, { expiresIn: "7d" }); // default: HS256 encryption// verify token
jwt.verify(token, SECRET, function (err, payload) {
if (err) {
return console.log('ERROR: ', err);
} console.log('JWT is valid and payload is\n', payload);
});
Above program outputs below result.
JWT is valid and payload is
{
userId: '110095',
name: 'John Doe',
roles: ['writer', 'moderator'],
iat: 1523818881,
exp: 1524423681
}
A not so clever hacker can get the payload (which is publically readable), change some data, base64 encode it and replace it in the token. But when the payload or header changes, it becomes inconstant with the signature. And since signature can be only constructed with the secret which is not publically available, hence JWT is one of the safest ways to authenticate HTTP requests.
If you backdate a JWT to simulate an expired token, this will also throw a JWT expired error. Remember, JWT is technically valid here because everything (header, payload, and signature) is consistent, this is a feature implemented by most JWT modules/libraries out there to check expired tokens.
userId: '110095',
name: 'John Doe',
roles: ['writer', 'moderator']
}, SECRET, { expiresIn: "-7d" }); // <== backdate
Asymmetric encryption
So far, we used HS256 encryption which uses the same key from encryption and decryption. HS256 is a perfectly safe encryption algorithm but that involves the same key for encryption and decryption. If a third party app wants to verify if a client is authorized on your website and also wants to read user data in the payload, you wouldn’t give them your secret key, would you?
Hence, you can use RS256 algorithm which uses the public and private key. You can use your private key to encrypt JWT and distribute a public key to third party apps to verify JWTs.
First, let’s generate sample public and private keys. I am going to use to generate RSA keys. -----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAIE7yK70OfXFcsnquMbsAPTbzZM92vQms88rSxnHoFhEiyZDPKa3
0wMb7xkf3m7SEfRCp7/HKQGauVV0CQhbSJ0CAwEAAQJAH2unqVHb3bN56znUXxj2
SpI+czQwzfey9AW0prnwdEUqWx/G8TASbOrsig/Z73vtq3FzvW61kS8oVCMPWKi9
yQIhAO4cuAUf/F3iCBVyZS2t2aoHMqWyz0YklNYrEjXPZELLAiEAivEnL+1xl01m
FgttJ5czQRZ+2KZUaW+qD3SePhmR7TcCIQDNynJcs41Ikq7L4meBuCxT4A6s2MJ9
a+ZaxzTg3tJXSwIgXv7SVCp75367tYbKcq8mE/JVd7sBK7V1CGwrZToGU7ECIEwS
gZtk538L7EQ3inp2HiySC9kQlO4OTzSnyuWLHg6U
-----END RSA PRIVATE KEY-----
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIE7yK70OfXFcsnquMbsAPTbzZM92vQm
s88rSxnHoFhEiyZDPKa30wMb7xkf3m7SEfRCp7/HKQGauVV0CQhbSJ0CAwEAAQ==
-----END PUBLIC KEY-----
Let’s save these keys into public.key and private.key files.
Now, instead of using SECRET string, we are going to use these keys. First, we encrypt JWT with our private key. Then, a third-party app can use its public key to verify the token and extract payload.
// test.jsconst fs = require('fs');
const jwt = require('jsonwebtoken');const PUBLIC_KEY = fs.readFileSync(__dirname + '/public.key');
const PRIVATE_KEY = fs.readFileSync(__dirname + '/private.key');var token = jwt.sign({
userId: '110095',
name: 'John Doe',
roles: ['writer', 'moderator']
}, PRIVATE_KEY, { algorithm: 'RS256' }); // RS256 algorithm// verify token
jwt.verify(token, PUBLIC_KEY, function (err, payload) {
if (err) {
return console.log('ERROR: ', err);
} console.log('JWT is valid and payload is\n', payload);
});
How to implement a JWT?
Most of the time, you would use JWT for API authentication. The best way would be to send a JWT when the user signs in, maybe in response body or header. A JWT should contain the user id to identify which user it is. Then on the web application, store that JWT in local storage or cookie.
While making restricted requests, use that JWT in the header, cookie, query-string, or request body. On the backend, you don’t need to authenticate the client, just the JWT. If the user id is changed, JWT verification will fail.
If you are serving front-end applications on Angular, React, or other SPA libraries, use the HTTP interceptor to add JWT automatically to restricted area requests. That way, you don’t have to add it manually each time user makes requests.