Securing the API
Add authentication and authorization: BCrypt passwords, a JWT login endpoint, and MEMBER vs LIBRARIAN roles enforced across the endpoints.
On this page
BookVault works, but right now anyone can delete any book. This lesson adds the layer that makes it safe to expose: authentication (who are you?) and authorization (what may you do?), using JWT tokens, BCrypt-hashed passwords, and two roles - MEMBER and LIBRARIAN.
Passwords, hashed
Never store a password. Store a BCrypt hash - a one-way, salted, deliberately slow function - and verify by hashing the attempt and comparing:
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // salted, slow-by-design; never reversible
}A member's stored passwordHash is what login checks against. Even if the database leaks, the
plaintext passwords don't.
Logging in returns a JWT
BookVault is a stateless API - no server-side sessions. On successful login it issues a JWT: a signed token carrying the user's identity and roles, which the client sends on every subsequent request:
// AuthController: verify credentials, then mint a signed token
public TokenResponse login(LoginRequest request) {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.email(), request.password()));
return new TokenResponse(tokenService.issue(request.email())); // signed JWT
}The token is signed with an HMAC secret (HS256). Because the signature is verifiable, the server
trusts a token's claims without looking anything up - that's what makes it stateless and scalable. The
secret comes from the environment (JWT_SECRET), never the source.
Roles decide what you can do
The security filter chain validates the JWT on each request and then applies authorization rules - reading is open, but changing the catalog requires the LIBRARIAN role:
http
.csrf(csrf -> csrf.disable()) // safe: stateless token API
.sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll() // login is open
.requestMatchers(GET, "/api/books/**").permitAll() // anyone may browse
.requestMatchers(POST, "/api/books/**").hasRole("LIBRARIAN") // only librarians edit
.anyRequest().authenticated())
.oauth2ResourceServer(o -> o.jwt(...)); // validate the JWTNow GET /api/books is public, a MEMBER can borrow, and a DELETE /api/books/... without a LIBRARIAN
token is rejected with 401 (not authenticated) or 403 (authenticated but not allowed).
Logging in is passport control: you prove who you are once, and receive a boarding pass (the JWT) stamped with where you're allowed to go. At each gate (endpoint) staff check the pass's signature and your class (role) - no need to re-verify your passport every time. A forged or expired pass is refused at the gate. That's stateless, token-based security: authenticate once, carry a verifiable pass, and each endpoint checks it.
401 vs 403 - a precise distinction
401 Unauthorized means 'I don't know who you are' - no or invalid token; authenticate first. 403 Forbidden means 'I know who you are, but you may not do this' - a valid token without the required role. BookVault returns 401 for a missing/bad token and 403 for a MEMBER attempting a LIBRARIAN action. Getting these right makes your API honest about why a request failed.
A MEMBER (logged in, holding a valid JWT) sends DELETE /api/books/978-.... Walk through what the
security layer does, which status code comes back and why, and contrast it with the same request sent
with no token at all.
How does BookVault secure its API?
Key takeaways
- Store passwords only as BCrypt hashes (salted, slow, one-way) and verify by hashing the attempt.
- BookVault is stateless: login issues a signed JWT carrying identity and roles, sent on each request instead of a session.
- The JWT is signed with an HMAC secret from the environment, so the server trusts its claims without a lookup.
- The security filter chain validates the token then applies authorization rules - reads open, catalog edits require LIBRARIAN.
- 401 means unauthenticated (no/bad token); 403 means authenticated but lacking the required role.
- Security is a distinct layer added over a working app - authentication first, then fine-grained authorization.