Spring Boot HandBook

    Signup and Login using JWT

    Introduction:#

    JSON Web Token (JWT) is a widely adopted standard for securely transmitting information between parties as a JSON object. This compact and URL-safe format simplifies the encoding of claims—assertions about an entity, typically a user, along with additional data. JWTs are especially valuable in web applications, where they provide robust user authentication and authorization mechanisms.

    In a typical flow, when a user logs in, the server generates a JWT that contains user-specific information, such as a unique identifier and roles. This token is then sent to the client, which stores it for future requests. By including the JWT in the Authorization header of these requests, clients can authenticate themselves and access protected resources without repeatedly transmitting sensitive credentials.

    Implementing JWT for signup and login processes not only enhances security but also improves the user experience in web applications, enabling seamless authentication across various services.

    Using JWT For Authentication#

    JWT (JSON Web Token) is used in web applications for user authentication. Upon login, the server generates a JWT with the user's information and sends it to the client, which includes the token in future requests to verify their identity.

    Explanation of JWT authentication:#

    1. User Signup:

      1. The signup process allows users to register by providing their details like email, name, and password.
      2. To implement this, you need to create a DTO (Data Transfer Object) class for capturing the signup information, a controller to handle the signup request, and a service to manage the signup logic.

    DTO Class for Signup:

    The SignupDto class will capture the user's registration details and enforce validation constraints.

    import lombok.Data; @Data public class SignupDto { //you can add validation here private String email; private String password; private String name; }

    Controller for Signup Request:

    • The client sends a POST request to /auth/signUp with the user's email, name, and password.
    • A DTO that represents the user data returned after successful signup.
    import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/auth") @RequiredArgsConstructor public class AuthController { private final AuthService authService; @PostMapping("/signUp") public ResponseEntity<UserDto> signUp(@RequestBody SignupDto signupDto){ return new ResponseEntity<>( authService.signUp(signupDto), HttpStatus.CREATED); } } // we need userDto import lombok.Data; @Data public class UserDto { private Long id; private String email; private String name; }

    Service for Signup Logic:

    The AuthService checks if a user already exists with the provided email. If not, it creates a new user, encodes the password, and saves the user to the database.

    import lombok.RequiredArgsConstructor; import org.modelmapper.ModelMapper; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import java.util.Optional; @Service @RequiredArgsConstructor public class AuthService { private final UserRepo userRepo; private final ModelMapper modelMapper; private final PasswordEncoder passwordEncoder; public UserDto signUp(SignupDto signupDto) { // Check if a user already exists with the provided email Optional<UserEntity> user = userRepo .findByEmail(signupDto.getEmail()); if(user.isPresent()) throw new BadCredentialsException("Cannot signup, User already exists with email "+signupDto.getEmail()); // Map SignupDto to UserEntity and encode the password UserEntity mappedUser = modelMapper.map(signupDto,UserEntity.class); mappedUser.setPassword(passwordEncoder.encode(mappedUser.getPassword())); // Save the user entity to the database UserEntity savedUser = userRepo.save(mappedUser); // Map the saved UserEntity to UserDto and return it return modelMapper.map(savedUser,UserDto.class); } }

    Output:

    Upon successful signup, the controller returns the user details (excluding sensitive information) as a UserDto, confirming the registration.

    After Sign up Postman view

    DataBase:

    1. User Login:

    The login process allows users to authenticate themselves by sending their credentials.

    If the credentials are valid, a JWT is generated and returned to the client.

    DTO Class for Login:

    import lombok.Data; @Data public class LoginDto { // you can add validation here private String email; private String password; }

    Controller for Login Request:

    • The client sends a POST request to /auth/logIn with the user's email and password.
    • The controller handles the login request and returns the generated JWT if authentication is successful.
    import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/auth") @RequiredArgsConstructor public class AuthController { private final AuthService authService; @PostMapping("/logIn") public ResponseEntity<String> logIn( @RequestBody LoginDto loginDto){ String token = authService.logIn(loginDto); return ResponseEntity.ok(token); } }

    Service for Login Logic:

    The AuthService verifies the user's credentials. If valid, it generates a JWT.

    After finding all the information then it will return an Authenticate authentication to the service. For that, we need to use AuthenticationManager and if we have no bean then we have to create a bean in any of our configuration classes. For this, we will use the default authentication manager that is already there(your ide).

    import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class AuthService { private final AuthenticationManager authenticationManager; private final JWTService jwtService; //from your custom class public String logIn(LoginDto loginDto) { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginDto.getEmail(),loginDto.getPassword()) ); UserEntity user = (UserEntity) authentication.getPrincipal(); //userentity used from you custom entity class String token = jwtService.createToken(user); return token; } } // create a authenticationmanager bean import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity //by adding this annotation we are telling springboot that we want to configure this SpringSecurityFilterChain public class WebSecurityConfig { @Bean AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); }

    Output:

    Upon successful login, the controller returns a JWT, allowing the client to authenticate subsequent requests.

    Successfully login with jwt token

    Test Token:

    Use **jwt.io** to decode and verify the structure and claims of your JWT. Just copy your token and paste it into the website to view the details.

    Test JWT Token

    Login Workflow with JWT

    Login Workflow with JWT
    1. Token Structure:

    The JWT typically contains three parts: a header, a payload, and a signature. The payload includes claims such as the user's ID and roles.

    1. Token Storage:

    The client stores the JWT, usually in local storage or cookies, to include in the Authorization header of future requests.

    Here we store this in our cookies. Just made a few changes to our login code.

    Controller for Login Request:

    • We have to pass the HttpServletRequest as a request with LoginDto.
    • Create a cookies.
    import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/auth") @RequiredArgsConstructor public class AuthController { private final AuthService authService; @PostMapping("/logIn") public ResponseEntity<String> logIn( @RequestBody LoginDto loginDto, HttpServletRequest request, HttpServletResponse response){ String token = authService.logIn(loginDto); Cookie cookie = new Cookie("token", token); cookie.setHttpOnly(true); // it makes sure that this cookie cannot be accessed by any other it can only be fund with the help of your Http methods // no other attacker can be access our website // Prevents JavaScript access to the cookie response.addCookie(cookie); // Http only cookies can be passed from backend to frontend only return ResponseEntity.ok(token); } }

    Output:

    Now You will see the output here that after getting a token we will also get a cookie here.

    JWT token inside cookies
    1. Authenticated Requests:

    The client includes the JWT in the Authorization header when making requests to access protected resources. The server validates the token to ensure the request is coming from an authenticated user.

    1. Token Expiry:

    JWTs have an expiration time, after which they are no longer valid. The client must re-authenticate to obtain a new token when the current one expires.

    1. Security:

    JWTs should be stored securely on the client side to prevent unauthorized access. Using HTTPS and secure storage mechanisms is essential to protect the token.

    Internal Flow of Spring-Security#

    Internal Flow of Spring-Security

    Conclusion#

    This article demonstrates how to implement secure user signup and login using JWT in a Spring Boot application. It covers the generation, storage, and validation of JWTs for authentication. By utilizing best practices for token management and security, the approach ensures seamless access to protected resources.

    Last updated on Jan 14, 2025