JWT authentication for a stateless API
What is inside a JWT, what its signature does and does not protect, how a Spring resource server validates one, and why revoking one is hard.
After this lesson you can
- Decode a JWT's three parts and say what is and is not secret about each
- Validate a token in the right order — algorithm, signature, then time claims
- Configure Spring Boot as a resource server and map scopes to authorities
A JSON Web Token is three base64url-encoded parts joined by dots:
header.payload.signature. The header names the signing algorithm
({"alg":"HS256","typ":"JWT"}); the payload carries claims such as
sub (subject), exp (expiry, seconds since the epoch), nbf (not
before), iss (issuer), aud (audience) and scope; the signature is
computed over the first two parts.
The payload is encoded, not encrypted. Anyone holding the token can read every claim, so a JWT must never carry a password or other secret. What the signature gives you is integrity: change one character of the header or payload and the signature no longer matches.
import javax.crypto.Mac;import javax.crypto.spec.SecretKeySpec;import java.nio.charset.StandardCharsets;import java.security.MessageDigest;import java.util.*; public class Solution { static final Base64.Encoder ENC = Base64.getUrlEncoder().withoutPadding(); static final byte[] SECRET = "demo-secret-for-lessons-only-32b!".getBytes(StandardCharsets.UTF_8); static String b64(String json) { return ENC.encodeToString(json.getBytes(StandardCharsets.UTF_8)); } static String hmac(String data) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(SECRET, "HmacSHA256")); return ENC.encodeToString(mac.doFinal(data.getBytes(StandardCharsets.UTF_8))); } static boolean verify(String token) throws Exception { String[] p = token.split("\\.", -1); String expected = hmac(p[0] + "." + p[1]); return MessageDigest.isEqual(expected.getBytes(StandardCharsets.UTF_8), p[2].getBytes(StandardCharsets.UTF_8)); } public static List<String> run() throws Exception { String header = b64("{\"alg\":\"HS256\",\"typ\":\"JWT\"}"); String payload = b64("{\"sub\":\"ana\",\"role\":\"user\"}"); String token = header + "." + payload + "." + hmac(header + "." + payload); String decoded = new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), StandardCharsets.UTF_8); String forgedPayload = b64("{\"sub\":\"ana\",\"role\":\"admin\"}"); String forged = header + "." + forgedPayload + "." + token.split("\\.")[2]; return List.of( "token: " + token, "anyone can read: " + decoded, "original verifies: " + verify(token), "role changed to admin verifies: " + verify(forged)); }}HS256 versus RS256
HS256 is an HMAC with a shared secret: whoever can verify a token can
also mint one, so the secret must stay inside services you fully trust.
RS256 and ES256 sign with a private key held only by the
authorization server; resource servers verify with the public key,
usually fetched from the issuer's JWK Set endpoint. For anything with
more than one service, asymmetric keys are the norm.
Validation order
A token is checked in an order that never trusts unverified data:
- It has exactly three parts that decode.
- Its algorithm is one the server expects. A server that lets the
token choose — including
"alg":"none", which means "unsigned" — accepts forgeries. Pin the algorithm. - The signature is valid, compared in constant time.
- Only now are the claims trusted:
expandnbfagainst the clock (with a small allowance for clock skew), thenissandaud.
Checking exp before the signature is not a security hole by itself,
but it means reasoning about claims an attacker wrote.
Spring Boot as a resource server
With spring-boot-starter-oauth2-resource-server and
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://auth.example.com
Boot configures a JwtDecoder that discovers the issuer's keys,
verifies signatures, and validates / (with 60 seconds of
clock skew by default) and the issuer. A
reads , and the (or ) claim
becomes authorities prefixed with , so a token with
passes .
Exercise · Pro
Validate an HS256 token
Exercises are free in the first 3 lessons of every course. From here on they are part of Pro and Sprint; the lesson and its runnable examples stay open.
Upgrade