Career OS

Week 6 — Spring Security + JWT Authentication

The textbook for this week

The deep version of everything below — filter chain de-magicked, JWT anatomy, the interactive token-lifecycle visualizer, and the SplitEase build steps — is Spring Boot 07 — Security & JWT. Work that module; use this page as the weekly checklist.

Why This Week Is Critical for Fintech

No fintech company will hire you if you don’t understand authentication and authorization. “I used Spring Security” is table stakes. “I understand how JWT works, why we use short-lived access tokens with refresh tokens, and how token revocation works” — that gets you hired.

Daily Breakdown

Day 1: Authentication vs Authorization

Authentication: WHO are you? (login) Authorization: WHAT can you do? (permissions)

Authentication: "I'm Darshan" → prove it → here's your token
Authorization: "Can Darshan delete this expense?" → check role/ownership → yes/no

How JWT works (understand, don’t just use):

1. User sends email + password
2. Server verifies credentials against database
3. Server creates JWT: { userId: 1, role: "USER", exp: 1hour }
4. Server signs JWT with a SECRET KEY
5. Client stores JWT (localStorage or httpOnly cookie)
6. Every request: Authorization: Bearer <jwt>
7. Server verifies signature + checks expiration
8. No database lookup needed — token IS the proof

Why JWT for APIs (vs sessions):

  • Stateless — server doesn’t store sessions
  • Scalable — any server can verify the token
  • Cross-service — microservices can trust the same token

JWT danger: You cannot revoke a JWT. If someone steals it, it’s valid until expiration. Solution: short expiry (15 min) + refresh tokens.

Day 2: Implement Registration + Login

@Service
public class AuthService {
    private final PasswordEncoder passwordEncoder;
    private final JwtTokenProvider tokenProvider;
    private final UserRepository userRepo;
    
    public AuthResponse register(RegisterRequest request) {
        // 1. Check if email already exists
        if (userRepo.existsByEmail(request.email())) {
            throw new EmailAlreadyExistsException(request.email());
        }
        
        // 2. Hash password (NEVER store plain text)
        User user = new User();
        user.setEmail(request.email());
        user.setPassword(passwordEncoder.encode(request.password()));
        userRepo.save(user);
        
        // 3. Generate JWT
        String token = tokenProvider.generateToken(user);
        return new AuthResponse(token, user.getId());
    }
    
    public AuthResponse login(LoginRequest request) {
        User user = userRepo.findByEmail(request.email())
            .orElseThrow(() -> new InvalidCredentialsException());
        
        if (!passwordEncoder.matches(request.password(), user.getPassword())) {
            throw new InvalidCredentialsException();
            // Don't say "wrong password" — that confirms the email exists
        }
        
        String token = tokenProvider.generateToken(user);
        return new AuthResponse(token, user.getId());
    }
}

Security rule: Error messages for auth should be vague. “Invalid email or password” — never “password is wrong” (confirms email exists).

Day 3: JWT Filter + Protected Endpoints

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
            HttpServletResponse response, FilterChain chain) {
        
        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith("Bearer ")) {
            String token = header.substring(7);
            if (tokenProvider.validateToken(token)) {
                Long userId = tokenProvider.getUserIdFromToken(token);
                // Set authentication in SecurityContext
                UsernamePasswordAuthenticationToken auth = 
                    new UsernamePasswordAuthenticationToken(userId, null, authorities);
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
        }
        chain.doFilter(request, response);
    }
}

Configure which endpoints are public vs protected:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    return http
        .csrf(csrf -> csrf.disable()) // Disabled for stateless APIs
        .sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/v1/auth/**").permitAll()
            .requestMatchers("/api/v1/health").permitAll()
            .anyRequest().authenticated()
        )
        .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
        .build();
}

Day 4: Authorization — Who Can Do What?

@PreAuthorize("@expenseAuth.isOwnerOrGroupMember(#expenseId, authentication)")
@DeleteMapping("/{expenseId}")
public ResponseEntity<Void> deleteExpense(@PathVariable Long expenseId) {
    expenseService.deleteExpense(expenseId);
    return ResponseEntity.noContent().build();
}

Rules to implement:

  • Users can only see groups they belong to
  • Only the expense creator or group admin can delete an expense
  • Users can only see their own balance details
  • Nobody can modify another user’s profile

Day 5: Password Security + Refresh Tokens

Password rules:

  • BCrypt with strength 12 (default 10 is fine for now)
  • NEVER log passwords, even hashed ones
  • NEVER send passwords in query params (they end up in server logs)

Refresh token flow:

1. Login → access token (15 min) + refresh token (7 days)
2. Access token expires → client sends refresh token
3. Server verifies refresh token → issues new access + new refresh
4. Old refresh token invalidated (rotation)

This is how every real fintech app works. Implement it.

Weekend: Security Testing + DSA

  • Test that unauthenticated requests get 401
  • Test that users can’t access other users’ data (403)
  • Test expired tokens get rejected
  • Test password hashing works correctly
  • DSA: DSA 04 — Sliding Window — module + 5 problems from its list. (You will meet this pattern again in Week 8’s rate limiting — for real, not for an interview.)

Resources

WhatWhereTime
JWT explainedjwt.io (interactive decoder)15 min
Spring SecurityBaeldung “Spring Security” series30 min per article
BCryptBaeldung “Hashing with BCrypt”10 min
OWASP Auth cheatsheetcheatsheetseries.owasp.org20 min