Login Endpoint

Exposing the AuthenticationManager

The login endpoint checks the email and password with the same AuthenticationManager that form login uses. Spring Boot builds it from your UserDetailsService and PasswordEncoder beans (see the basic level); we only need to expose it as a bean.

@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
    return config.getAuthenticationManager();
}

@Bean
PasswordEncoder passwordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder(); // bcrypt by default
}

DTOs and Controller

package in.shopease.auth;

public record LoginRequest(@NotBlank @Email String email, @NotBlank String password) {}

public record UserSummary(String email, List<String> roles) {}

public record LoginResponse(String accessToken, String tokenType, long expiresIn, UserSummary user) {}

@RestController
@RequestMapping("/api/auth")
public class AuthController {

    private final AuthenticationManager authenticationManager;
    private final JwtService jwtService;

    public AuthController(AuthenticationManager authenticationManager, JwtService jwtService) {
        this.authenticationManager = authenticationManager;
        this.jwtService = jwtService;
    }

    @PostMapping("/login")
    public LoginResponse login(@Valid @RequestBody LoginRequest req) {
        // throws BadCredentialsException (-> 401) if the password is wrong
        Authentication auth = authenticationManager.authenticate(
                UsernamePasswordAuthenticationToken.unauthenticated(req.email(), req.password()));

        UserDetails user = (UserDetails) auth.getPrincipal();
        List<String> roles = user.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList();
        return new LoginResponse(jwtService.generateAccessToken(user), "Bearer",
                jwtService.accessTtlSeconds(), new UserSummary(user.getUsername(), roles));
    }
}

Request and Response

Example

Correct and wrong password:

curl -i -X POST localhost:8080/api/auth/login -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"asha123"}'

curl -i -X POST localhost:8080/api/auth/login -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"wrong"}'

Example output

HTTP/1.1 200 OK
Content-Type: application/json
{"accessToken":"eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5zaG9wZWFzZS5pbiIsInN1YiI6ImFzaGFAc2hvcGVhc2UuaW4iLCJyb2xlcyI6WyJST0xFX0NVU1RPTUVSIl0sImlhdCI6MTc2NzIyNTYwMCwiZXhwIjoxNzY3MjI2NTAwfQ.7mJ0q3...",
 "tokenType":"Bearer","expiresIn":900,
 "user":{"email":"[email protected]","roles":["ROLE_CUSTOMER"]}}

HTTP/1.1 401 Unauthorized
Content-Type: application/json
{"status":401,"error":"Unauthorized","message":"Invalid email or password","path":"/api/auth/login"}

Note

Note: Return the same message for "unknown email" and "wrong password". Saying "no such user" lets attackers find out which emails are registered (user enumeration). Spring's DaoAuthenticationProvider already turns UsernameNotFoundException into BadCredentialsException for this reason.