Authorization using JWT Access Tokens

Sending your payload in a JWT Token is like sending your object in a transparent box locked with a highly secure shared key.

If someone changes your box, you wont be able to open it.

If you are able to open it, you can safely say that the object is genuine- no one has changed it on the way.

JSON Web Token (JWT) is an open standard where two parties can exchange JSON payloads in a trusted way. Both parties can trust each other on the exchanged payload because it is digitally signed using a shared secret key or a public\private key.

In this article, we will see how this trusted exchange of payload in the JWT is useful in implementing a token based authorization system. We will use a node.js based POC to understand the token generation and its verification steps. But, before that let us have a look at the authorization process.

How does the authorization process work?

The below diagram clearly shows the authentication steps, but here is a brief explanation of the same.

Generating the token:

When a user needs access to protected resources in an application server, the login process redirects him to the authorization server. After a successful verification of the credentials, the server generates an access token. The token includes the user detail in a JSON payload and a signature signed using a shared secret key.

We will discuss more on the token content and the verification mechanism later, but the signature ensures only the target server with the shared secret key will be able to verify it.

The server sends the access token to the client, so that he can store and use it multiple times.

JWT Access Token

 

Verifying the token:

After receiving the token, the client browser makes a request to the application server adding the token in its header.

Since, the server has the shared secret key, its able to match the signature to verify the token . Moreover, because the verification has succeeded, it uses the user detail in the payload with an assurance that no one has modified.

Thus in the process the user is able to access the resource from the application only with a digital token.

 

A few important points to note:

First, with this token based system, the user can have one password and trust one authorization server, to get access to so many applications.

Second, we can share the secrets or the public keys in different ways. For example, we can use same secret key for multiple applications to have sso like implementation. Or, we may choose to use separate secret keys for each one.

Third, the access tokens are safer compared to the passwords, because they have access only to specific servers. Besides this limited access, we always provide a limited validity to these tokens based on the security needs of the targeted application.

Fourth, the authentication server is just a logically different entity. It can very well be a separate module inside an application requiring token based authorization. In such cases, the first attempt to a protected resource will need your credentials whereas the subsequent attempts will use the tokens.

 

POC to show how the JWT tokens work

The POC demonstrates the basic concepts of generating and verifying the tokens as we have discussed above. We are using ‘jsonwebtoken‘ library for working on the tokens.

The POC has two methods :

  • generateToken – Its for generating the signed access tokens which we use on the authentication server.
  • verifyToken – Its for verifying and retrieving user details which we use on the client application side.
const jwt = require('jsonwebtoken');

const accessTokenSecretApp01 = 'accessToken-secret-app-01';
const accessTokenSecretApp02 = 'accessToken-secret-app-02';

const validity = '5s';

const adminUser = { "username" : 'testAdmin', "role" : "admin"};

// Generate a signed token using authenticated user detail
function generateToken(user, tokenSecret, validity){
        console.log("Token created for user:" + JSON.stringify(user));

        const jwtToken = jwt.sign({ username: user.username, role: user.role },tokenSecret, { expiresIn: validity });

        console.log("JWT token created using "+tokenSecret +":\n" + JSON.stringify(jwtToken));
        return jwtToken;
}

// Verify and retrieve the user detail using the correct secret key
function verifyToken(token, tokenSecret){
        jwt.verify(token, tokenSecret, (err, user) => {
            if (err) {
                console.error("Verified with "+tokenSecret+" :" + err);
                return;
            }
            console.log("Verified with "+tokenSecret+" :"+JSON.stringify(user));
        });
}

//Test generation and verification of the GWT access tokens
console.log("\nStep 1 : Token generation at the authentication server :\n ")
let accessToken = generateToken(adminUser, accessTokenSecretApp01, validity);

console.log("\nStep 2: Verification & user detail retrieval at the client application :\n ")
verifyToken(accessToken,accessTokenSecretApp01);
verifyToken(accessToken,accessTokenSecretApp02);
setTimeout(()=>{console.log("After expiry ........: ");
                verifyToken(accessToken,accessTokenSecretApp01);}, 10000)

 

To summaries, the output of the POC shows :

  1. After we authenticate an user, we can create an access token using his details.
  2. Since it is signed, only the intended applications that has the key can verify and use it. For example, the verification by the application 02 using ‘accessToken-secret-app-02’, has failed.
  3. Last line in the output and point 2 above shows, unlike the user id and password, these tokens carry a limited scope and have limited life period. Hence, these tokens carry a much lesser risk.
F:\stutz\jwt-auth>node jwt-access-token-poc.js

Step 1 : Token generation at the authentication server :

Token created for user:{"username":"testAdmin","role":"admin"}
JWT token created using accessToken-secret-app-01:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RBZG1pbiIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTYwNjk1MDQ5MywiZXhwIjoxNjA2OTUwNDk4fQ.Vfr4M31jkqT5u4BqbDqNQaEtZ1Sqbwwy7zS7jw94CC0"

Step 2: Verification & user detail retrieval at the client application :

Verified with accessToken-secret-app-01 :{"username":"testAdmin","role":"admin","iat":1606950493,"exp":1606950498}
Verified with accessToken-secret-app-02 :JsonWebTokenError: invalid signature
After expiry ........:
Verified with accessToken-secret-app-01 :TokenExpiredError: jwt expired



 

Refresh Token – To avoid re-using credentials

As we have seen so far, we usually give a limited scope and life to our access tokens for the security reasons. The expiry time depends on our use case. And, it may vary from few minutes to several days

But, in case our usage is longer compared to these access tokens, we need to get new access tokens. Moreover, we also require separate access tokens for accessing separate systems.

To address the above needs, we can use another kind of token called refreshTokens. These are similar to the access tokens but, with a much longer lifetime. These are not meant for any other clients, but only for our authentication sever.

Instead of the credentials, we can send the refresh token to the authentication server. Then the server can verify the refresh token, for extracting the user detail and, thereby, sending us a new access token.

Understanding the Token & the Signature

If we look at the token we have generated above, it has 3 sections- Header, PayLoad and Sign, as shown below.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3RBZG1pbiIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTYwNjk1MDQ5MywiZXhwIjoxNjA2OTUwNDk4fQ.Vfr4M31jkqT5u4BqbDqNQaEtZ1Sqbwwy7zS7jw94CC0

 

We can easily decode the content of the header and the pay load as these are Base64 encoded.

  • The header contains the encryption algorithm used in the signing process.
  • The payload includes the user detail, his role and usually an expiry time for the token.

But, in any case, clearly the token does not protect the content!

Why should we trust these tokens ?

These token does not protect the content but ensures it’s integrity. In other words, it ensures no one has tampered it. And, the below diagram shows how ?

JWT Access Token -Sign & Verification Process

As we can see the verifying server tries to re-create the signature on the message using the shared secret key. Hence, if any one tampers the message or the signature, the signatures won’t match and the verification will fail.

Again, the signature won’t match for anyone with a different key. Hence, we can not use a given access token for any server, other than the intended one.

 

What precaution should we take on these tokens ?
  • Any one can read the payload in the token. Hence, it should not have sensitive information like our password, SSN number etc.
    • We can also encrypt the token again to hide the payload.
  • Even though these tokens have limited scope, any one can utilize these limited scope if stolen. Hence, the browsers or the systems those use these tokens, should ensure they store it securely.
    • We should use an appropriate expiry time for these access tokens.
  • The log out mechanism should discard the refreshTokens from the server side, so that no one is able reuse these refreshTokens for renewing accessTokens.
    • We should also discard these refreshTokens on the server side with an appropriate expiry time.