A JWT Token looks as follows.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0IiwiYXVkIjpbImFkbWluIl0sImlzcyI6Im1hc29uLm1ldGFtdWcubmV0IiwiZXhwIjoxNTc0NTEyNzY1LCJpYXQiOjE1NjY3MzY3NjUsImp0aSI6ImY3YmZlMzNmLTdiZjctNGViNC04ZTU5LTk5MTc5OWI1ZWI4YSJ9.EVcCaSqrSNVs3cWdLt-qkoqUk7rPHEOsDHS8yejwxMw
It has following three parts.
JWT may not contain the signature section, but that would not help the server verify the token. If we do not want to store the token in the database, we should keep the signature section in the token.
The token received in the request must contain 3 parts we mentioned above.
Let us split the parts using String
split method.
//split into 3 parts with . delimiter
String[] parts = token.split("\\.");
All three parts are Base64 url encoded, use the Base64
class to decode.
private static String decode(String encodedString) {
return new String(Base64.getUrlDecoder().decode(encodedString));
}
Covert the payload and header strings into JSONObject objects. We need to use signature as it is for verification.
JSONObject header = new JSONObject(decode(parts[0]));
JSONObject payload = new JSONObject(decode(parts[1]));
String signature = decode(parts[2]);
Check if the expiry timestamp is greater than the current timestamp. The payload contains
payload.getLong("exp") > (System.currentTimeMillis() / 1000)
If the token is expired, we can avoid the verification step and discard the token. Token expiry safegaurds the server from malicious tokens or any other token alterations.
Regenerate the signature using the same algorithm found the header.
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;
}
}
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 safer.
String headerAndPayloadHashed = hmacSha256(parts[0] + "." + parts[1]
If you notice, we have not Base64 decoded the parts before creating the hash.
Check this regenerated token is matching the signature mentioned in the token as follows.
signature.equals(headerAndPayloadHashed, secret))
If token isn't expired and signature is valid, then the token is valid.
As seen above, payload contains the information required, in this example we are not considering encryption, as we are not encouraging storing Personal information into the payload.
JSONObject payload = new JSONObject(decode(parts[1]));
long userId = payload.getLong("sub");
We can keep identifiers, we can used to indentify, operations, IDs etc whatever is necessary to identify the user for the request.
I have created a simple java file JWebToken.java to decode easily.
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();
}
You can modify this class to improve the decoder as you like.
Try decoding the JWT token online