Handling Authentication With JWT
Tags: security, javascript, jwt
Published: 2020-02-23 19:42:05

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**

```js
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. // header
eyJzdWIiOiIxMjM0NSIsInJvbGUiOiJhZG1pbiJ9. // payload
bi_wAbm4vOKxM8zjDYEeiseRPfKtum_7S2H-DmpDDwg // signature
```
**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:

```js
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');
    });
}
```

The code for authentication and generating JWT on a server:

```js
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 });
  }
}
```

The code to include JWT as `Authentication` header:

```js
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
    });
}
```

The code for verifying JWT on a server:

```js
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);
  }
  // ...
}
```