JWT Introduction
A JSON Web Token (JWT) is a compact, URL-safe token that carries a set of claims about a user, signed so the server can trust it without keeping any server-side session. JWTs are the backbone of stateless authentication in modern Spring Boot APIs: the client sends the token on every request, and the server verifies the signature instead of looking up a session. This section explains what a JWT actually contains before the later pages build a full JWT authentication flow.
What a JWT looks like
A JWT is three Base64URL-encoded parts joined by dots: header.payload.signature.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiJhbGljZSIsInJvbGUiOiJBRE1JTiIsImV4cCI6MTcxODI4MDgwMH0
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Decoding the first two parts gives plain JSON. They are not encrypted — only signed — so never put secrets in a JWT.
Header — the signing algorithm and token type:
{ "alg": "HS256", "typ": "JWT" }
Payload — the claims:
{
"sub": "alice",
"role": "ADMIN",
"iat": 1718277200,
"exp": 1718280800
}
Signature — computed over the encoded header and payload using a secret or private key. If anyone tampers with the header or payload, the signature no longer matches and the token is rejected.
Claims
A claim is a single statement inside the payload. JWTs distinguish three kinds:
| Type | Examples | Meaning |
|---|---|---|
| Registered | iss, sub, aud, exp, iat, nbf, jti | Standard claims defined by the spec |
| Public | email, role | Common names, ideally collision-resistant |
| Private | tenantId, plan | Custom claims shared between your client and server |
The most important registered claims:
sub— the subject, usually the username or user id.iss— who issued the token (your auth server’s identifier).exp— expiration time (epoch seconds); after this the token is invalid.iat— when the token was issued.aud— the intended audience (which API should accept it).jti— a unique token id, useful for revocation lists.
Warning: Because the payload is only encoded, not encrypted, anyone holding the token can read every claim. Treat a JWT as readable; put only non-sensitive identifiers and authorization data in it.
Signing: HMAC vs RSA
The signature is what makes a JWT trustworthy. Two algorithm families dominate:
| HMAC (HS256) | RSA / EC (RS256, ES256) | |
|---|---|---|
| Key model | One shared secret | Private key signs, public key verifies |
| Who can verify | Anyone with the secret (which can also forge) | Anyone with the public key; only the holder of the private key can sign |
| Best for | A single service that both issues and verifies | An auth server issuing tokens that many independent services verify |
| Key distribution | Must keep the secret on every verifier | Publish the public key (often via a JWKS endpoint) |
For a self-contained API where the same Spring Boot app both logs users in and validates tokens, HS256 with a strong secret is simple and secure. When an external identity provider issues tokens — like Keycloak or an OAuth2 resource server — RS256 lets your service verify signatures using a published public key without ever holding the signing key.
Note: An HS256 secret must be at least 256 bits (32 bytes) of high-entropy data. A short, guessable secret undermines the entire scheme.
Stateless authentication
Traditional session auth stores a session id in a cookie and keeps the matching session in server memory or a shared store. JWTs flip this: the token itself carries the identity, so the server keeps no session state.
1. POST /auth/login (username + password)
2. Server validates credentials, signs a JWT, returns it
3. Client stores the token
4. Each request: Authorization: Bearer <token>
5. Server verifies the signature + exp, builds the SecurityContext, allows the request
The trade-off: a stateless token cannot be revoked simply by deleting a server record — it stays valid until it expires. This is exactly why short lifetimes and refresh tokens exist.
Access tokens vs refresh tokens
To balance security against usability, real systems issue two tokens:
| Access token | Refresh token | |
|---|---|---|
| Lifetime | Short (5–15 min) | Long (days/weeks) |
| Sent on | Every API request | Only to the refresh endpoint |
| Contains | User id + authorities | Minimal; often opaque |
| Revocable | No (until expiry) | Yes — stored and checked server-side |
A short-lived access token limits the damage if it leaks, while the refresh token lets the client get a new access token without re-entering the password. The Refresh Tokens page implements this end to end.
Where to store tokens on the client
This decision is a security trade-off, not a detail:
localStorage— easy, but readable by any JavaScript, so it is exposed to XSS attacks.HttpOnly,Secure,SameSitecookie — invisible to JavaScript (mitigates XSS) but requires CSRF protection.- In-memory only — safest against XSS for the access token; pair it with a refresh token in an
HttpOnlycookie.
Tip: A common production pattern: keep the short-lived access token in memory and the refresh token in an
HttpOnlySecurecookie. You get XSS resistance for the long-lived credential and a CSRF surface limited to a single refresh endpoint.
In This Section
- JWT Authentication — implement a full custom JWT login + filter with jjwt.
- Refresh Tokens — short access tokens, long refresh tokens, rotation and revocation.
- OAuth 2.0 Introduction — roles, grant types, and OpenID Connect.
- OAuth2 Social Login — log in with Google or GitHub.
- OAuth2 Resource Server — validate tokens from an external IdP.
- Keycloak Integration — a full identity provider with Spring Boot.