Reading:
Generate JWT Token and Verify Java Example

Generate JWT Token and Verify Java Example

Metamug
Generate JWT Token and Verify Java Example

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 {

        //MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hash = secret.getBytes(StandardCharsets.UTF_8);//digest.digest(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

The token received in the request must contain 3 parts we mentioned above. Let us split the parts using String split method.

JWT Header Payload and Signature

//split into 3 parts with . delimiter
String[] parts = token.split("\\.");

All three parts are Base64 url encoded. So use the equivalent decoder.

private static String decode(String encodedString) {
    return new String(Base64.getUrlDecoder().decode(encodedString));
}

We converted the decoded JSON string to JSONObject

JSONObject payload = new JSONObject(decode(parts[1]));

Check if the expiry timestamp is greater than the current timestamp

payload.getLong("exp") > (System.currentTimeMillis() / 1000)

Regenerate the signature as explained in an earlier step using the same algorithm. Check this regenerated token is matching the signature mentioned in the token.

signature.equals(hmacSha256(parts[0] + "." + parts[1], secret))

If these above two conditions pass, then the token is valid.

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

The below 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. Check metamug auth example for more details.


import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
 *
 * @author user
 */
public class JWebToken {

    private static final String SECRET_KEY = "FREE_MASON"; //@TODO Add Signature here
    private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
    private static final String ISSUER = "mason.metamug.net";
    private static final String JWT_HEADER = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
    private JSONObject payload = new JSONObject();
    private String signature;
    private String encodedHeader;

    private JWebToken() {
        encodedHeader = encode(new JSONObject(JWT_HEADER));
    }

    public JWebToken(JSONObject payload) {
        this(payload.getString("sub"), payload.getJSONArray("aud"), payload.getLong("exp"));
    }

    public JWebToken(String sub, JSONArray aud, long expires) {
        this();
        payload.put("sub", sub);
        payload.put("aud", aud);
        payload.put("exp", expires);
        payload.put("iat", LocalDateTime.now().toEpochSecond(ZoneOffset.UTC));
        payload.put("iss", ISSUER);
        payload.put("jti", UUID.randomUUID().toString()); //how do we use this?
        signature = hmacSha256(encodedHeader + "." + encode(payload), SECRET_KEY);
    }

    /**
     * For verification
     *
     * @param token
     * @throws java.security.NoSuchAlgorithmException
     */
    public JWebToken(String token) throws NoSuchAlgorithmException {
        this();
        String[] parts = token.split("\\.");
        if (parts.length != 3) {
            throw new IllegalArgumentException("Invalid Token format");
        }
        if (encodedHeader.equals(parts[0])) {
            encodedHeader = parts[0];
        } else {
            throw new NoSuchAlgorithmException("JWT Header is Incorrect: " + parts[0]);
        }

        payload = new JSONObject(decode(parts[1]));
        if (payload.isEmpty()) {
            throw new JSONException("Payload is Empty: ");
        }
        if (!payload.has("exp")) {
            throw new JSONException("Payload doesn't contain expiry " + payload);
        }
        signature = parts[2];
    }

    @Override
    public String toString() {
        return encodedHeader + "." + encode(payload) + "." + signature;
    }

    public boolean isValid() {
        return payload.getLong("exp") > (LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)) //token not expired
                && signature.equals(hmacSha256(encodedHeader + "." + encode(payload), SECRET_KEY)); //signature matched
    }

    public String getSubject() {
        return payload.getString("sub");
    }

    public List<String> getAudience() {
        JSONArray arr = payload.getJSONArray("aud");
        List<String> list = new ArrayList<>();
        for (int i = 0; i < arr.length(); i++) {
            list.add(arr.getString(i));
        }
        return list;
    }

    private static String encode(JSONObject obj) {
        return encode(obj.toString().getBytes(StandardCharsets.UTF_8));
    }

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

    private static String decode(String encodedString) {
        return new String(Base64.getUrlDecoder().decode(encodedString));
    }

    /**
     * Sign with HMAC SHA256 (HS256)
     *
     * @param data
     * @return
     * @throws Exception
     */
    private String hmacSha256(String data, String secret) {
        try {

            //MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = secret.getBytes(StandardCharsets.UTF_8);//digest.digest(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;
        }
    }

}
Icon For Arrow-up