DEV Community

Linas Spukas
Linas Spukas

Posted on

Handling Authentication With JWT

JSON web token (JWT) is a way for securely transmitting information as a JSON object. The information is digitally signed using a secret key, can be validated and trusted.
Many applications are using JWT to identify client after the successful authentication for further request.

Tokens are created only on a server, during successful authentication, and usually carries information, related to users identity. On the server, this information is signed using a secret key and can be validated or detected if it was changed on the client. This prevents attackers from tampering user characteristics and identities. For example, if a server signs a payload { right: 'read' } and send to a client, it expects to receive identical information to verify the same payload. So if you change the payload to { right: 'write' } and send back to the server, it will detect changes and reject the request.

Structure of JWT

JWT consist of three parts of encoded information separated by a dot: header.payload.signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. // header
eyJzdWIiOiIxMjM0NSIsInJvbGUiOiJhZG1pbiJ9. // payload
bi_wAbm4vOKxM8zjDYEeiseRPfKtum_7S2H-DmpDDwg // signature
Enter fullscreen mode Exit fullscreen mode

Header information includes the type of the token, which is JWT and the algorithm, used to encode, like HMAC SHA 256 or RSA.
So if encode header { alg: 'HSA256', typ: 'JWT' } we would get eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.

Payload contains additional information, encoded in Base64Url, about the user identity or characteristics, such as identified number, role, issue or expiration date. But do not include sensitive information into the payload, as it can be easily retrieved using window atob() method for decoding a base-64 encoded strings. Try to decode payload from the example above by writing atob('eyJzdWIiOiIxMjM0NSIsInJvbGUiOiJhZG1pbiJ9') in the browser console and you'll get parsed object with additional data.

Signature consists of a concatenation of encoded header, payload and secret key. Then we encode concatenated information with an algorithm specified in a header and get a signature. Signature is used to verify if the message was not changed during the transitions.

Secret Key

JWT security depends on the strength of the secret key, used to sign a token. A secret key ideally should be unique and strong, at least 64 characters, generated by a cryptographically secure function to be as random as possible.
If an attacker can get a valid JWT, they can attempt to crack the secret with an offline attack. If successful, they will be able to modify the token and sign again with the retrieved secret key.
Furthermore, if all the tokens were signed with the same secret key and it was cracked by attackers, that will compromise other user accounts.
To make the best of a secret key, an idea is to make unique secret keys for each authentication. This can be done by concatenating a piece of a hashed user password and constant randomly generated secret.

Storage on a Client

Usually, JWT are stored in a browsers Cookies or localStorage container. Both are very convenient, as Cookies are sent automatically by the browser with every request to the server, and localStorage container has no expiration for the tokens unless you do it manually.
Nevertheless, tokens in Cookies or localStorage can be retrieved by an XSS attack.
To make the most out of it, storing JWT in sessionStorage Container is advised. It is similar to localStorage, except sessions are created for each browser and tab individually, and after closing it, the sessions are cleared.
Session storage is also exposed to XSS attacks, but it is time-framed and isolated to the singular tab of the browser, that makes it harder to access the token.
Also, take into consideration additional security measures:

  • Add a token as a Bearer HTTP Authentication header to all request to the server
  • Add fingerprint to the token (randomly generated string; add to Cookies as a raw text and a hashed version to the token)

Example of Implementation

I will use axios library as a browser and Node.js HTTP client and jasonwebtoken library for handling JWT. And always use JWT libraries that are trusted. You can find a list of JWT libraries in www.jwt.io

The code to store tokens after successful authentication:

function handleAuthentication() {
  axios
    .post('/authenticate', {
      email: 'test@example.com',
      password: 'test'
    })
    .then(function(res) {
      if (res.status === 200) {
        sessionStorage.setItem('token', res.data.token);
      } else {
        sessionStorage.removeItem('token');
      }
    })
    .catch(function(error) {
      sessionStorage.removeItem('token');
    });
}
Enter fullscreen mode Exit fullscreen mode

The code for authentication and generating JWT on a server:

const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');

const SECRET_KEY = 'strongUniqueAndRandom';

function authenticate(req, res) {
  const { password } = req.data;
  const isValid = bcrypt.compareSync(password, hashedUserPasswordFromDb);

  if (isValid) {
    const payload = {
      sub: '1234', // user identifying information, such as an Id from database
      iat: new Date().getTime()
    };
    const token = jwt.sign(payload, SECRET_KEY);
    res.status(200).json({ token });
  }
}
Enter fullscreen mode Exit fullscreen mode

The code to include JWT as Authentication header:

function handleTokenValidation() {
  const token = sessionStorage.getItem('token');
  const config = {
    headers: {
      Authorization: `Bearer ${token}`
    }
  };
  axios
    .post('/validate', {}, config)
    .then(function(response) {
      // do something if response is valid
    })
    .catch(function(error) {
      // handle failed token validation
      // navigate user to login page
    });
}
Enter fullscreen mode Exit fullscreen mode

The code for verifying JWT on a server:

const jwt = require('jsonwebtoken');

const SECRET_KEY = 'strongUniqueAndRandom';

function validate(req, res, next) {
  const bearer = req.headers.authorization;
  const [, token] = bearer.split(' ');

  const payload = jwt.verify(token, SECRET_KEY);

  // If payload was decoded, that means the token was valid
  // Further payload validation can be done to identify user
  if (!!payload) {
    res.json(true);
  }
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Top comments (5)

Collapse
 
stevemullen profile image
SteveMullen

This is a super helpful article - thank you.

Question - you say "storing JWT in sessionStorage Container is advised". If using sessionStorage, how do you pass the jwt on page load - like when a user clicks a link in the nav to go to a new page?

I know this will work if I store the jwt as a cookie, but is there some way to make this work with sessionStorage?

Collapse
 
hanipcode profile image
Muhammad Hanif

Nice explanation about jwt @spukas . By the way for implementation I create a simple library to make it easier to implement jwt in node app, check it out dev.to/hanipcode/creating-a-simple...

Collapse
 
rishpoddar profile image
Rishabh Poddar

Thanks for this article. I have a few points though:

To make the best of a secret key, an idea is to make unique secret keys for each authentication

If we do this, then the user will have to do a db call each time token verification is required. Which beats the point of using JWTs in the first place. Instead, I recommend to use the same key for all users, and do keep changing that key over time as explained in this article: supertokens.io/blog/the-best-way-t...

tokens in Cookies or localStorage can be retrieved by an XSS attack.

If we use httpOnly cookies, any javascript running on the client side cannot read those cookies, thereby minimising the risk of XSS.

Collapse
 
tycodes profile image
Bolatito Adeoye

Nice workπŸ™Œ

Collapse
 
hardikjoshi51 profile image
hardikjoshi51

Super useful explanation. Thanks @Linas.
One easy way to lookup to content of jwt token is online tools such as jwt.io or webtoolz.online/encoding/jwt-decode/