Start Learning
Javaneer
Back to stage
Module 9·Capstone: Build BookVault

Securing the API

Add authentication and authorization: BCrypt passwords, a JWT login endpoint, and MEMBER vs LIBRARIAN roles enforced across the endpoints.

16 min readAdvanced
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 JWT

Now 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).

A passport and a boarding pass

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.

Trace an unauthorized delete

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.
Was this lesson helpful?
Edit this page on GitHub