java 49 lines · 9 steps

How HMAC-signed JWTs are created and verified

A compact Java class that signs JWTs with HMAC-SHA256 and verifies them safely against tampering and expiry.

Explained by highlit
1public final class HmacJwt {
2 
3 private static final ObjectMapper MAPPER = new ObjectMapper();
4 private static final Base64.Encoder ENCODER = Base64.getUrlEncoder().withoutPadding();
5 private static final Base64.Decoder DECODER = Base64.getUrlDecoder();
6 
7 private final SecretKeySpec key;
8 
9 public HmacJwt(String secret) {
10 this.key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
11 }
12 
13 public String sign(Map<String, Object> claims) throws Exception {
14 Map<String, Object> header = Map.of("alg", "HS256", "typ", "JWT");
15 String signingInput = encode(header) + "." + encode(claims);
16 String signature = ENCODER.encodeToString(hmac(signingInput));
17 return signingInput + "." + signature;
18 }
19 
20 public Map<String, Object> verify(String token) throws Exception {
21 String[] parts = token.split("\\.");
22 if (parts.length != 3) {
23 throw new SecurityException("malformed token");
24 }
25 String signingInput = parts[0] + "." + parts[1];
26 byte[] expected = hmac(signingInput);
27 byte[] provided = DECODER.decode(parts[2]);
28 if (!MessageDigest.isEqual(expected, provided)) {
29 throw new SecurityException("signature mismatch");
30 }
31 Map<String, Object> claims = MAPPER.readValue(DECODER.decode(parts[1]),
32 new TypeReference<Map<String, Object>>() {});
33 Object exp = claims.get("exp");
34 if (exp instanceof Number n && Instant.now().getEpochSecond() > n.longValue()) {
35 throw new SecurityException("token expired");
36 }
37 return claims;
38 }
39 
40 private byte[] hmac(String data) throws Exception {
41 Mac mac = Mac.getInstance("HmacSHA256");
42 mac.init(key);
43 return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
44 }
45 
46 private String encode(Object value) throws Exception {
47 return ENCODER.encodeToString(MAPPER.writeValueAsBytes(value));
48 }
49}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A JWT is just three base64url segments joined by dots, with the last being an HMAC over the first two.
  2. 2Signature comparison must use a constant-time check like MessageDigest.isEqual to avoid timing attacks.
  3. 3Verification is only trustworthy when it re-derives the signature from the received bytes and also enforces expiry.

Related explainers

Share this explainer

Here's the card — post it anywhere.

How HMAC-signed JWTs are created and verified — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code