Step 5
Step 5 — Auth with JWT
30 min
Step 5 — Auth with JWT
REST is stateless — no server-side sessions. Each request must carry a credential. That credential is JWT.
A JWT in one line
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0In0.signature
Three parts split by .:
- Header — algorithm (HS256, RS256…)
- Payload — userId, expiry — signed, tamper-evident
- Signature — hash made with the server's secret key
The server issues a token; the client sends it on every request as Authorization: Bearer <token>.
Issue flow
1. POST /api/auth/login (email, password)
2. Service verifies password (BCrypt)
3. Issues JWT (sub=userId, exp=now+1h)
4. Returns { accessToken: "eyJ..." }
Spring Security in one config
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}
STATELESS + a JWT filter is the heart.
The verification filter
@Component
@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {
private final JwtService jwtService;
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws ServletException, IOException {
String header = req.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
Long userId = jwtService.parseUserId(token);
// populate SecurityContext …
}
chain.doFilter(req, res);
}
}
Try it
Build POST /api/auth/login, then curl -X POST http://localhost:8080/api/auth/login -d '{"email":"...","password":"..."}' to get a token. Call protected APIs with -H "Authorization: Bearer <token>".
Next
Step 6 adds tests so changes don't silently break.