Reading:
Generate JWT Token and Verify in Plain Java

Generate JWT Token and Verify in Plain Java

Metamug
Generate JWT Token and Verify in Plain Java

Generate a JWT token in Java

Bearer Authentication can be random tokens. They are secure and remove the need of jsession id. But they will be more useful if they can carry information along with them. A JWT token has 3 parts to it.

  1. Header - For agreeing on the algorithm for signing the message.
  2. Payload - For carrying user data.
  3. Signature - For Verification

Header and Payload both are JSON. They need to be Base64 encoded. The dot separates each part.

String signature = hmacSha256(base64(header) + "." + base64(payload), secret);
String jwtToken = base64(header) + "." + base64(payload) + "." + signature;

Here is an example

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0IiwiYXVkIjpbImFkbWluIl0sImlzcyI6Im1hc29uLm1ldGFtdWcubmV0IiwiZXhwIjoxNTc0NTEyNzY1LCJpYXQiOjE1NjY3MzY3NjUsImp0aSI6ImY3YmZlMzNmLTdiZjctNGViNC04ZTU5LTk5MTc5OWI1ZWI4YSJ9.EVcCaSqrSNVs3cWdLt-qkoqUk7rPHEOsDHS8yejwxMw

The standard algorithm for signing is HMAC + SHA256 also called has HS256. Here we will declare the header as a constant string. This header will be used during verification for checking the algorithm used.

private static final String JWT_HEADER = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";

Encoding

We need to encode the header and payload. For encoding, we use Base64 and URL encoding. Java 8 provides a method for Base64 URL encoding.

URL encoding makes the JWT safe to be sent as a part of the url.

import java.util.Base64;

//...

private static String encode(byte[] bytes) {
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}

So encoding the header will always give us constant string eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

Payload

The simplest information you can store in your payload is

  1. User ID (subject)
  2. Role (audience)
  3. Token Expiry Time (expiry)
private JSONObject payload = new JSONObject();

//...

payload.put("sub", sub);
payload.put("aud", aud);
payload.put("exp", expires);

Here is an example

{
  "sub": "13324",
  "aud": "admin",
  "exp": 1512203223
}

We must encode the payload since it will be in JSON format. After encoding it will become a compact string as follows. During verification, this string is decoded using the same Base64 URL decoding mechanism to retrieve the JSON payload.

eyJzdWIiOiIxMjM0IiwiYXVkIjpbImFkbWluIl0sImlzcyI6Im1hc29uLm1ldGFtdWcubmV0IiwiZXhwIjoxNTc0NTEyNzY1LCJpYXQiOjE1NjY3MzY3NjUsImp0aSI6ImY3YmZlMzNmLTdiZjctNGViNC04ZTU5LTk5MTc5OWI1ZWI4YSJ9

Setting Expiration Time for JWT Token

The expiration time exp is set into the JWT token as a timestamp. The server sets this time to match it against the current timestamp during verification. Expiration time is kept short to prevent against token highjacking. Expiration time is a hard-coded expiration time into the token. Once it is set inside the token it cannot be changed. Unless the token contains an issued time field, the server has no choice but to discard the token after the expiration time has reached.

Another alternative to exp is Issued At iat. iat is more flexible and server can decide the expiration time according to its policy.

HMAC SHA256 Signature

Header and payload are concatenated with a dot and signed with HMAC + SHA256 algorithm using a secret key. You need to maintain a configurable secret key somewhere.


private String hmacSha256(String data, String secret) {
    try {

        byte[] hash = secret.getBytes(StandardCharsets.UTF_8);
        Mac sha256Hmac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKey = new SecretKeySpec(hash, "HmacSHA256");
        sha256Hmac.init(secretKey);

        byte[] signedBytes = sha256Hmac.doFinal(data.getBytes(StandardCharsets.UTF_8));

        return encode(signedBytes);
    } catch (NoSuchAlgorithmException | InvalidKeyException ex) {
        Logger.getLogger(JWebToken.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
        return null;
    }
}
//...
signature = hmacSha256(encodedHeader + "." + encode(payload));

Read how and why cryptographic hash functions are used to sign messages.

JWT Token Verification

Token verification does not require any database call.

Token-Based authentication requires a database to create and verify tokens. JWT creation may require access to the database for user details, but verification is all about checking if the server has signed the token and its still valid (looking at the expiry time).

Since 99% of the request will comprise of resource access and verification (Rest 1% may be unauthenticated resources access). This makes JWT a more efficient token authentication mechanism.

Verification requires the following steps:

  1. Validity Check
  2. Signature verification

You can find the steps to decode in java here.

Use the information in the payload

Now that we trust the token, we can use the User Id and the role mentioned in the token and provide access to the requested resource. The payload shouldn't contain sensitive information like payment information.

Generate JWT and verify Example

The below example usage shows how a Data access object is used to create the JSONObject. Once the token is generated it is passed on to the client by the auth server. Where the token is sent back again to the server, the server verifies the token.


    //Create the token from user details.
    JSONObject payload = dao.getBearerDetails(user, pass);
    String bearerToken =  JWebToken(payload).toString();

    //@TODO Send this token over the network

    //recieve the bearer token
    JWebToken incomingToken = new JWebToken(bearerToken);

    if (incomingToken.isValid()) {
       System.out.println(incomingToken.getSubject());
    }

JWT Java Source Code JWebToken.java

The program provides a constructor for both generating the token and verifying the generated token. It uses more information like issued at (iat), issuer(iat), etc.

It is hosted on github

JWT Sample

The following code can be used to generate JWT token


private static final int EXPIRY_DAYS = 90;

JSONObject jwtPayload = new JSONObject();
jwtPayload.put("status", 0);

JSONArray audArray = new JSONArray();
audArray.put("admin"); 
jwtPayload.put("sub", "John");

jwtPayload.put("aud", audArray);
LocalDateTime ldt = LocalDateTime.now().plusDays(EXPIRY_DAYS);
jwtPayload.put("exp", ldt.toEpochSecond(ZoneOffset.UTC)); //this needs to be configured

String token = new JWebToken(jwtPayload).toString();

JWT token recieved in the String format can be used to verify and extract audience and subject information as follows.

//verify and use
JWebToken incomingToken = new JWebToken(bearerToken);
if (!incomingToken.isValid()) {
    String audience = incomingToken.getAudience();
    String subject = incomingToken.getSubject();
}


Comments

Post a comment
Icon For Arrow-up