Nuxtstop

For all things nuxt.js

JWT how does it work and is it secure?

JWT how does it work and is it secure?
92 5

JWT stands for JSON web token

the common definition says that it is an open industry standard RFC 7519 method for representing claims securely between two parties

so lets break it up into a simpler logic to understand its utility and the way it works!
So JWT was built by some developers in Microsoft, they built it initially for information exchange, and later on it was repurposed for authorization.

In security processes, authentication validates a user's identity, it also grants that user permission to access a resource.
JWT is a stateless session, so it does not need to be saved in a database in the server-side like cookies, it only exists in the client side.

A JWT is composed by :

header . payload . signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

The Header is the metadata about the token, its the result of

const base64Url = require("base64-url") 
// used for Base64 and URL Encoding Decoding 
const header = base64Url.encode(
  JSON.stringify({
    alg :"HS256", // algorithm : none, HS256, RS256, PS256 etc ..
    type :"JWT",
    ...
  })
);

//outputs : eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9
Enter fullscreen mode Exit fullscreen mode

please notice that it is not encrypted it's just encoded which means you can use base64 decode and you will get the JSON object in clear.

the payload contains the message we want to send alongside with different information about the token itself

const base64Url = require("base64-url") 
// used for Base64 and URL Encoding Decoding 
const header = base64Url.encode(
  JSON.stringify({ 
    sub:"1234567890", //subject
    iss:"Darken", //issuer
    aud:"My API", //audience used for auth as well 
    exp:1633895355, //expiration datetime
    iat:1633895235, //issued at datetime
    ...
  })
);
//outputs : eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9
//lIiwiaWF0IjoxNTE2MjM5MDIyfQ
Enter fullscreen mode Exit fullscreen mode

Again it is not encrypted it's just encoded which means you can use base64 decode and you will get the JSON object in clear.
So far we are not securing information, so you may be wondering, how is this secure, and where is the authentication in all of this?
And that's where the signature plays it's role!

A signature is the result of some function that uses the header, the payload a secret key and hash function.
The secret key is the most important part, a good advice is to use a 256bit key and don't hard code it ( save it in process.env )
Please note that if we are using asymmetric encryption, when calculating the signature the algorithm uses both keys ( private and public )

So the signature is usually calculated like this :

const crypto = require("crypto") // cryptography library
const base64Url = require("base64-url") 
const secret = process.env.SECRET
//Again ! please use a 256bit secret key
const content = "${header}.${payload}"
//used for Base64 and URL Encoding Decoding 
const signature = base64Url.escape(
  crypto.createHmac('sha256',secret)
  .update(content)
  .digest('base64')
);
//outputs : SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Enter fullscreen mode Exit fullscreen mode

Now this creates an HMAC encryption (Hash-based message authentication code) a cryptographic technique that combines the key and a hash into a mix hackers can't unpack.

So the authentication part shows up here! Have the content of this message been manipulated?

Remember that the token is equal to :

const token = "${header}.${payload}.${signature}"
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwib
mFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fw
pMeJf36POk6yJV_adQssw5c
Enter fullscreen mode Exit fullscreen mode

Since the hacker can change the signature but can't guess the right signature( he doesn't know the secret key ) then when the attacker changes the payload or the header, the signature no longer matches the data.
So lets suppose the hacker decoded the payload and changed it to :

{
  "sub": "This was changed",
  "name": "AchrafAffes",
  "iat": 1516239022
}
//The payload encoded will then be changed to :
eyJzdWIiOiJUaGlzIHdhcyBjaGFuZ2VkIiwibmFtZSI6IkFjaHJhZkFmZmVzIiwiaW
F0IjoxNTE2MjM5MDIyfQ
Enter fullscreen mode Exit fullscreen mode

And again! since the hacker can't guess the right signature for the new encoded payload ( no secret key ) then when the server decodes the header and the payload, and recalculates the new signature it will be : do3cSS2wLRUM6cmqVqvFZVpCwJkeO0BieF0h0oTWaBE
which is impossible for the hacker to guess unless he knows the secret key ( remember when using single symmetrical key to use a 256 bit key) and here the server will predict that the payload or the header were changed and therefore it will ignore the request.

Now that you understand how the JWT works, how do we use it in action ?

For me I use it as following, the user logs in, the server checks for the credentials whether this user coords exists or not, if it does, the server generates a token and sends it to the user ( the server does not save a copy ) the user then saves the token in its localstorage ( the token should have a short expiration datetime since it is vulnerable for XSS attacks which I'll explain in another post in the future )
Whenever the user wants to access something, it sends the token in its header, and the server verifies it, if its verified then the server responds else the server responds with a 403 Forbidden error.

In some other solutions, we implement an authentication server(AS), the user passes by the AS first and then it is redirected to the resource server (API) which will verify the token with each request.

If you are working with nodeJs you can use the jsonwebtoken package, to easily implement the JWT

var jwt = require('jsonwebtoken');
const secret = 'secretkey'
//please make sure to use a 265bit key
const data= {username:"achraf",other:"stuffHere"}

//to generate the data we use
let token = jwt.sign(
  data,
  secret, 
  {expiresIn : '2 min'} //other options can be used
);

//and to verify it you can use
jwt.verify(token,secret, function(err, tokendata){
    if(err){
        console.log("Unauthorized request")
    }
    if(tokendata){
        console.log("verified")
    }
})
Enter fullscreen mode Exit fullscreen mode

so lets talk quickly about the most recommended algorithms that can be used :

HS256 : HMAC + SHA-265
(relies on the shared symmetrical key)

RS256 / RSASSA + SHA-256
(relies on private/public RSA key pair, but it may overload the network and uses more CPU for calculation )

ES256 : ECDSA using p-265 and SHA-265
(relies on private/public RSA key pair but much shorter )

I'll try to talk about these algorithms in details in the future

finally I wanna talk about the difference between cookies and JWT:

  1. Cookies need to be stored on the server-side while JWT are stateless
  2. since cookies require a database, this database will be queried on each client request
  3. cookies are vulnerable for both CSRF and XSS attacks, while JWT is only vulnerable to XSS attack.