My journey with Json Web Token(JWT)

My journey with Json Web Token(JWT)

When dealing with authorization in our application, JWT comes into play. In our stateless world authorize our contents or APIs is a crucial part. Today I want to share my knowledge with you what I've learned about JWT.

Why JWT

A backend service may have several kind of resources. Particular user may access particular resources. For example, Admin can view or modify the analytics. On the other hand sales users can view the analytics but not modify. A registered user can view his own order history while a guest user can't like so. When a client want to access a particular resource of our backend service, he/she should send some identity or credentials with the specific http request. But how can we protect those credentials from malicious bots or users. Lets find the answer.

What is JWT

JWT is an internet standard for creating data. JWT is a simple, compact but yet powerful standard which helps us to transfer and share our secrets.

In practical a JWT is a single string which has 3 substrings. Those substrings are separated by 2 dots. Here is a example JWT(newlines for readability). Also, one thing needs to mention that if a JWT has those 3 parts then it is called a signed JWT.

N.B: This example is taken from the official JWT website.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

But there is no meaning right. Because each strings are base64-URL encoded. Base64-URL is a variant of the base64. This encoding algorithm ensures the URL-safe thing. Now, let's decode this thing.

The first part of the string is called the header. The header may contain this,

{
  "alg": "HS256",
  "typ": "JWT"
}

The second part is called the payload. The payload may contain this,

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

The third part is the signature. This signature plays the main role. Before understanding this signature, let's understand the big picture. How JWT works. In this article, I want to give you an overview of a simple authentication and authorization process using JWT.

How JWT works in a stateless web application

In a typical stateless web applications, there are 2 parties. One is the client app. It can be a frontend JavaScript application or a mobile app. And another one is the backend or the service hosted in the web servers. In those applications the authentication steps are the following.

  • A registered user loads the login page and submits the login form. It is a POST request and the payload is attached in the body.

  • The payload contains the credentials. We should use the SSL/TLS for transporting the credentials securely.

  • Once the backend receives the body and parse the details, it validates the credentials and creates a JWT token.

  • Now the backend service sends this JWT token to the client.

  • Now the client saves this token in the local storage or cookie in browsers or mobile devices.

  • Now every time the client makes a request, it attaches the JWT token with the requests header.

  • The backend service first validates the signature part. If it is a valid signature then it validates the payload whether the requester(client) has the valid authorization for the request. If the requester has authority then it sends the appropriate response.

Details explanation of each part

Now lets familiar with each part of a JWT.

Header

In industry standard, each information is called as claims. Typically the header part can have 3 claims which are industry standard.

  • alg: 'alg' is the short form of algorithm. The algorithm which is used in the signature part for validating. In JWT all the claim is in short form like this 'alg'. If we want to use a unencrypted JWT then we should use like this:

      "alg": "none"
    

    The common algorithms are

    • HMAC

    • SHA

    • RSA

There are a lot of variants of algorithm. But I'm not going to explain those in this article.

  • typ: they media type of the JWT itself.

  • cty: The content type. This is used for nested JWTs.

Payload

In this part we should add the user data. It is important to remember that for the authorization purpose we should not add any sensitive secrets. If we do we should encrypt the payload. Otherwise malicious users will exploit it. We should include those data which are essential to validate a user to authorize the specific request. for example, user id and the admin flag.

{
    "id": 101,
    "admin": true
}

Also there are some common industry standard claims which are called as registered claims. Those are not mandatory but recommended. Lets talk about those

  • iss: The issuer. In our case, it is the application itself. For a microservice architecture, it can be different. This is a case-sensitive string or a URI. It should be unique.

  • sub: The subject. The statement about the issuer.

  • aud: The audience. This is mostly application specific.

  • exp: expiration time. The expiration datetime of our JWT token. If we want to build a secure application, the JWT tokens should be short lived. Once a token passed the expiration time, it is invalid. We need to create a new one.

  • nbf: Not before (time). Once issued, should be valid after the specified time.

  • iat: Issued at. The datetime when the token is issued or created.

  • jti: JWT ID. This is a unique identifier string. This is used to differentiate JWTs when we have similar contents.

Note: In JWT, all claim name are short. This keeps the tokens as small as possible.

All claims that are not a part of the registered claims are either private or public claims.

  • Public claims: Those are also registered claims but not mandatory. We can use those if we need to. For avoiding collision, use the IANA JSON Web Token Registry It has a versatile list of defined claims for our use. For example: name, email, gender, client_id etc.

  • Private claims: User defined custom claims.

Signature

A unsecured JWT has 2 parts, only the header and payload. But if we want to create a secure JWT, we need the signature part. At the end we will use this for validating our JWT. The process of checking the signature of a JWT is called the validating a token. Also, using these 3 parts is a RFC standard and it is called as JSON Web Signature(JWS). The process of constructing a signature is the following

  • Take the base64Url encoded header

  • Take the base64Url encoded payload

  • Concatenate both encoded strings with a dot(.)

  • Create the signature using the string created above and a shared secret or a private key and a signature algorithm.

The pseudocode of the singnature is

SIGNING_ALGORITHM (
    `${base64UrlEncode(header)}.${base64UrlEncode(payload)}`,
    SHARED_SECRET || PRIVATE_KEY
)

But how this will work? Using the signature algorithm, it creates a one-way hash. So, once the hash is created, no way of decoding. While logging in the server creates this hash. Now, anyone can temper the header and payload parts and creates a new signature. But it will create a new hash aside the hash collision thing. Also the malicious user needs the secret or the private key. Otherwise again a new signature will be created. When a user sends the JWT to the server, the server first generates another JWT using the header and payload claims and validate it with the signature attached with the JWT. If matches the server sends proper responses. Otherwise throw error responses.

Code implementation

There are many well known libraries for generating and validating JWT. Today I'm going to show my demo using Java. In Java, I've seen there are 7 libraries whose implements the JWT standards. Today I'm using one of them and it is Nimbus-JOSE-JWT. It supports verity of algorithms and specifications.

Create a Maven project. I'm using the IntelliJ idea for creating this project.

Generate a JWT using a shared symmetric secret

Using a shared secret technique means both the parties(token generator and verifier) needs a same secret. This is the secret which is used in the signature part.

First step is to create a shared key. In this article, I'm generating a random 256bit/32 byte shared key.

private byte[] generateSecret() {
    SecureRandom random = new SecureRandom();
    byte[] secret = new byte[32];
    random.nextBytes(secret);

    return secret;
}

Now generate a token. Using libraries, the way is much easier. In this example, I'm using the HMAC with SHA256 algorithm for signing. In short it is HS256. This is a symmetric algorithm.

Also, in this example, I've used registered claims like subject, issuer, expiration time and a private claim 'admin'. You can see we don't need to convert our header or claims to base64Url format. This library internally does this.

private String generateToken(byte[] secret) throws JOSEException {
    //create the signer algorithm
    JWSSigner signer = new MACSigner(secret);
    //create the header
    JWSHeader header = new JWSHeader(JWSAlgorithm.HS256);
    //create the payload or claims
    JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
            .subject("admin")
            .issuer("http://localhost:8080/app")
            .expirationTime(new Date(new Date().getTime() + 60*1000))
            .claim("admin", Boolean.TRUE)
            .build();
    SignedJWT signedJWT = new SignedJWT(header, claimsSet);
    signedJWT.sign(signer);

    String serializeJWT = signedJWT.serialize();

    return serializeJWT;
}

The token may look like this(newline for readability).

eyJhbGciOiJIUzI1NiJ9.
eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXBwIiwic3ViIjoiYWRtaW4iLCJhZG1pbiI6dHJ1ZSwiZXhwIjoxNzAxMDAzNzAyfQ.
Yf-C4VdJfjBOrwhKHUXnCJHW-xTIPwhGJh8IKSvCnFg

Now, when a client sends a JWT, backend service now can validate this way,

private boolean validateToken(byte[] secret, String token) throws ParseException, JOSEException {
    SignedJWT receivedSignedJWT = SignedJWT.parse(token);
    JWSVerifier verifier = new MACVerifier(secret);
    boolean isValid = receivedSignedJWT.verify(verifier);
    return isValid;
}

Now test this like this

byte[] secret = generateSecret();

try {
    String token = generateToken(secret);
    System.out.println(token);

    System.out.println(validateToken(secret, token));
} catch (JOSEException e) {
    throw new RuntimeException(e);
} catch (ParseException e) {
    throw new RuntimeException(e);
}

Generate a JWT using a asymmetric secret

Using this technique, we need 2 keys. Those are public and private keys. But first generate those keys. There are many ways or libraries to generate them. Today I'm using the OpenSSL. We need to construct the token using the private key and for validating we need only the public key. So, if anyone steal our public key, he/she can't construct a new one using the key.

There are many standard ways to generate the key pairs. One of them is using the OpenSSL. But I'm going to utilize the Nimbus-JOSE-JWT library to generate a RSA keypair.

RSAKey rsaKey = new RSAKeyGenerator(RSAKeyGenerator.MIN_KEY_SIZE_BITS).generate();

Here, the MIN_KEY_SIZE_BITS is the 2048. We can also hardcode this value instead of this constant. Also we can use the higher value of bits like 4096.

N.B: Don't use this code in production. This is for the example purpose. We should keep the keys in files not in the code. Also, we should not commit the keys or key storage in the code repo.

Let's generate the JWT from the keypair.

private String generateToken(RSAKey rsaKey) throws JOSEException {
    //create RSA-signer algorithm
    JWSSigner signer = new RSASSASigner(rsaKey);
    //create the header
    JWSHeader header = new JWSHeader(JWSAlgorithm.RS256);
    //create the payload or claims
    JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
            .subject("admin")
            .issuer("http://localhost:8080/app")
            .expirationTime(new Date(new Date().getTime() + 60*1000))
            .claim("admin", Boolean.TRUE)
            .build();
    SignedJWT signedJWT = new SignedJWT(header, claimsSet);
    signedJWT.sign(signer);

    String serializeJWT = signedJWT.serialize();

    return serializeJWT;
}

Here, I've used RSA with SHA-256(RS256) algorithm. Everything is same like the symmetric one.

Although, you can see that the RSASSASigner takes the whole key-pair as input. But If we navigate declaration of the RSASSASigner(my keyboard shortcut is F3), we will see this code.

public RSASSASigner(RSAKey rsaJWK, Set<JWSSignerOption> opts) throws JOSEException {
    this(RSAKeyUtils.toRSAPrivateKey(rsaJWK), opts);
}

So, ultimately we need the private key to construct the token.

The output of the token generation is

eyJhbGciOiJSUzI1NiJ9.
eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXBwIiwic3ViIjoiYWRtaW4iLCJhZG1pbiI6dHJ1ZSwiZXhwIjoxNzAxMDAzODY5fQ.
rbVWeX8oixdxE-2dmmZp1ACzNfSgNmzYOW1u4-lHGjyMDA4GJG-3Y4zIIxIAR8G1qy274d26QAWiROkZ6kDsVwegUHCixaddCS97DBwvu3Aly6r8f-79ZGgxAF2TOJLFq4Tq6vf2YnEY_IOQh_lLJpQSsHNdzweb9cYpZxzbTKqSNNNHLZcjfD8sjqBp7pBOhadZwgmW-4UcmQaBFBUJFuCw9z_2M8zD72B1rerrXNP70E4Ge8SI2T1gJbbadLCXR92pqKiGVgMwezU2g-N9FIwx9-047uM_9tD9i2FcQ7rHnaC8OXSDON-iY5ozppYdx2phcH4JX5adbC6WJy64Gg

Now, lets validate the token.

private boolean validateToken(RSAKey publicKey, String token) throws ParseException, JOSEException {
    SignedJWT receivedSignedJWT = SignedJWT.parse(token);
    JWSVerifier verifier = new RSASSAVerifier(publicKey);
    boolean isValid = receivedSignedJWT.verify(verifier);
    return isValid;
}

JWT debugger

In the official JWT website, we can see the JWT debugger. Let's put our encoded JWT in the encoded side. We can see our header and payload part. I've run my shared secret code and take token from the debugger. Next I put the token in the JWT debugger.

Here is the IntelliJ idea debugger window

And the JWT debugger window

JSON Web Encryption (JWE)

In our previous examples, we can see anyone can see our header and payload parts. But if our requirement is we have to encrypt the payload, we can do this using JWE. The process is the same as JWS. Here is an example of encoding and validating the hash. This is an example of using a shared key. In other word this the symmetric hash example.

private String encodePayload(byte[] secret) throws JOSEException {
    JWSSigner signer = new MACSigner(secret);

    JWSHeader header = new JWSHeader(JWSAlgorithm.HS256);
    Payload payload = new Payload(" a simple payload");
    JWSObject jwsObject = new JWSObject(header, payload);
    jwsObject.sign(signer);

    String serializeJWT = jwsObject.serialize();

    return serializeJWT;
}

private boolean verifyPayload(byte[] secret, String payload) throws JOSEException, ParseException {
    JWSObject jwsObject = JWSObject.parse(payload);
    JWSVerifier verifier = new MACVerifier(secret);

    boolean isValidPayload = jwsObject.verify(verifier);

    return isValidPayload;
}

Other common security consideration

  • Strong keys.

  • Safe Key management.

  • Configure CSRF(cross-site request forgery).

  • Mitigate XSS(cross-site scripting).

  • Always validate information inside code.

That's all for this article. I hope you like my journey of learning JWT. If you like this post, please like and if you have any query please leave a comment below. This will help me to improve my writing. Thank for reading. Happy coding.