Spring Boot React JWT Authentication Example

author : Sai K

In this tutorial, we will create a full-stack application using Spring Boot for the backend and React (using functional components and hooks) for the frontend. We will implement JWT-based authentication using Spring Security 6+ and React 18. The tutorial will cover setting up the project, configuring Spring Security, creating a Spring Boot REST API for user authentication with JWT, and building a React application for the same. We will also use Bootstrap for styling.


Prerequisites

Before we start, ensure you have the following:

  • Java Development Kit (JDK) installed

  • Apache Maven installed

  • Node.js and npm installed

  • An IDE (such as IntelliJ IDEA, Eclipse, or VS Code) installed

Step 1: Setting Up the Spring Boot Project

1.1 Create a Spring Boot Project

Go to Spring Initializr n your web browser.

2.Configure Project Metadata:

  • Project: Maven Project

  • Language: Java

  • Spring Boot: Select the latest version of Spring Boot

  • Group: com.example

  • Artifact: spring-boot-react-jwt

  • Name: spring-boot-react-jwt

  • Description: Full Stack Application with Spring Boot and React for JWT Authentication

  • Package Name: com.example.springbootreactjwt

  • Packaging: Jar

  • Java Version: 17 (or your preferred version)

  • Click Next.

3.Select Dependencies:

  • On the Dependencies screen, select the dependencies you need. For user authentication, you can start with:
    • Spring Web

    • Spring Security

    • Spring Data JPA

    • H2 Database

    • Spring Boot DevTools

    • JSON Web Token (JWT)

  • Click Next.
  • 4.Generate the Project:

    • Click Generate to download the project zip file.

    • Extract the zip file to your desired location.

    5.Open the Project in Your IDE:

    • Open your IDE and import the project as a Maven project.

    1.2 Maven Dependencies

    Here's a pom.xml file for the Spring Boot and JWT authentication tutorial:

    
        < ?xml version="1.0" encoding="UTF-8"?>
    < project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        < modelVersion>4.0.0< /modelVersion>
    
        < groupId>com.example< /groupId>
        < artifactId>spring-boot-react-jwt< /artifactId>
        < version>0.0.1-SNAPSHOT< /version>
        < name>spring-boot-react-jwt< /name>
        < description>Full Stack Application with Spring Boot and React for JWT Authentication
        < packaging>jar< /packaging>
    
        < parent>
            < groupId>org.springframework.boot< /groupId>
            < artifactId>spring-boot-starter-parent< /artifactId>
            < version>3.0.6< /version>
            < relativePath/> 
        < /parent>
    
        < properties>
            < java.version>17< /java.version>
            < jjwt.version>0.11.5< /jjwt.version>
        < /properties>
    
        < dependencies>
            
            < dependency>
                < groupId>org.springframework.boot< /groupId>
                < artifactId>spring-boot-starter-web< /artifactId>
            < /dependency>
            < dependency>
                < groupId>org.springframework.boot< /groupId>
                < artifactId>spring-boot-starter-data-jpa< /artifactId>
            < /dependency>
            < dependency>
                < groupId>org.springframework.boot< /groupId>
                < artifactId>spring-boot-starter-security< /artifactId>
            < /dependency>
            < dependency>
                < groupId>org.springframework.boot< /groupId>
                < artifactId>spring-boot-devtools< /artifactId>
                < scope>runtime< /scope>
                < optional>true< /optional>
            < /dependency>
    
            
            < dependency>
                < groupId>com.h2database< /groupId>
                < artifactId>h2< /artifactId>
                < scope>runtime< /scope>
            < /dependency>
    
            
            < dependency>
                < groupId>io.jsonwebtoken< /groupId>
                < artifactId>jjwt-api< /artifactId>
                < version>${jjwt.version}< /version>
            < /dependency>
            < dependency>
                < groupId>io.jsonwebtoken< /groupId>
                < artifactId>jjwt-impl< /artifactId>
                < version>${jjwt.version}< /version>
                < scope>runtime< /scope>
            < /dependency>
            < dependency>
                < groupId>io.jsonwebtoken< /groupId>
                < artifactId>jjwt-jackson< /artifactId> 
                < version>${jjwt.version}< /version>
                < scope>runtime< /scope>
            < /dependency>
        < /dependencies>
    
        < build>
            < plugins>
                < plugin>
                    < groupId>org.springframework.boot< /groupId>
                    < artifactId>spring-boot-maven-plugin< /artifactId>
                < /plugin>
            < /plugins>
        < /build>
    < /project>
    

    I suggest using the same JWT dependency versions as the ones used in this tutorial.

    1.3 Project Structure

    After importing the project, you will see the following structure in your IDE:

    
        spring-boot-react-jwt
    ├── src
    │   ├── main
    │   │   ├── java
    │   │   │   └── com
    │   │   │       └── example
    │   │   │           └── springbootreactjwt
    │   │   │               ├── SpringBootReactJwtApplication.java
    │   │   │               ├── config
    │   │   │               ├── controller
    │   │   │               ├── model
    │   │   │               ├── repository
    │   │   │               ├── security
    │   │   │               └── service
    │   ├── main
    │   │   └── resources
    │   │       ├── application.properties
    │   └── test
    │       └── java
    │           └── com
    │               └── example
    │                   └── springbootreactjwt
    │                       └── SpringBootReactJwtApplicationTests.java
    └── pom.xml
    

    Step 2: Creating the Backend

    2.1 Configure H2 Database

    Open the application.properties file located in the src/main/resources directory and add the following

    configuration:


    
     # H2 Database configuration
    spring.datasource.url=jdbc:h2:mem:testdb
    spring.datasource.driverClassName=org.h2.Driver
    spring.datasource.username=sa
    spring.datasource.password=password
    spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
    spring.h2.console.enabled=true
    
    # JPA settings
    spring.jpa.hibernate.ddl-auto=update
    spring.jpa.show-sql=true
    
    # JWT configuration
    jwt.secret=your_jwt_secret_key
    jwt.expiration=3600000
    

    2.2 Create the User Entity

    In the model package, create a new Java class named User:

    
        package com.example.springbootreactjwt.model;
    
    import jakarta.persistence.Entity;
    import jakarta.persistence.GeneratedValue;
    import jakarta.persistence.GenerationType;
    import jakarta.persistence.Id;
    
    @Entity
    public class User {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String username;
        private String password;
        private String role;
    
        // Getters and Setters
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getRole() {
            return role;
        }
    
        public void setRole(String role) {
            this.role = role;
        }
    }
    

    2.3 Create the UserRepository Interface

    In the repository package, create a new Java interface named UserRepository:

    
        package com.example.springbootreactjwt.repository;
    
    import com.example.springbootreactjwt.model.User;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public interface UserRepository extends JpaRepository {
        User findByUsername(String username);
    }
    

    2.4 Create the UserService Interface

    In the service package, create a new Java interface named UserService:

    
        package com.example.springbootreactjwt.service;
    
    import com.example.springbootreactjwt.model.User;
    
    public interface UserService {
        User findByUsername(String username);
        User saveUser(User user);
    }
    

    2.5 Implement the UserService Interface

    In the service package, create a new Java class named UserServiceImpl:

    
        package com.example.springbootreactjwt.service;
    
    import com.example.springbootreactjwt.model.User;
    import com.example.springbootreactjwt.repository.UserRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserServiceImpl implements UserService {
    
        private final UserRepository userRepository;
        private final PasswordEncoder passwordEncoder;
    
        @Autowired
        public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder) {
            this.userRepository = userRepository;
            this.passwordEncoder = passwordEncoder;
        }
    
        @Override
        public User findByUsername(String username) {
            return userRepository.findByUsername(username);
        }
    
        @Override
        public User saveUser(User user) {
            user.setPassword(passwordEncoder.encode(user.getPassword()));
            return userRepository.save(user);
        }
    }
    

    2.6 Create JWT Utility Class

    In the security package, create a new Java class named JwtTokenUtil:

    
        package com.example.springbootreactjwt.security;
    
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    import java.util.Date;
    import java.util.function.Function;
    
    @Component
    public class JwtTokenUtil {
    
        @Value("${jwt.secret}")
        private String secret;
    
        @Value("${jwt.expiration}")
        private Long expiration;
    
        public String generateToken(String username) {
            return Jwts.builder()
                    .setSubject(username)
                    .setIssuedAt(new Date())
                    .setExpiration(new Date(System.currentTimeMillis() + expiration))
                    .signWith(SignatureAlgorithm.HS512, secret)
                    .compact();
        }
    
        public Boolean validateToken(String token, String username) {
            final String tokenUsername = getUsernameFromToken(token);
            return (tokenUsername.equals(username) && !isTokenExpired(token));
        }
    
        public String getUsernameFromToken(String token) {
            return getClaimFromToken(token, Claims::getSubject);
        }
    
        public Date getExpirationDateFromToken(String token) {
            return getClaimFromToken(token, Claims::getExpiration);
        }
    
        public  T getClaimFromToken(String token, Function claimsResolver) {
            final Claims claims = getAllClaimsFromToken(token);
            return claimsResolver.apply(claims);
        }
    
        private Claims getAllClaimsFromToken(String token) {
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        }
    
        private Boolean isTokenExpired(String token) {
            final Date expiration = getExpirationDateFromToken(token);
            return expiration.before(new Date());
        }
    }
    

    2.7 Create JWT Authentication Filter

    In the security package, create a new Java class named JwtAuthenticationFilter:

    
        package com.example.springbootreactjwt.security;
    
    import jakarta.servlet.FilterChain;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    import org.springframework.stereotype.Component;
    import org.springframework.web.filter.OncePerRequestFilter;
    
    import java.io.IOException;
    
    @Component
    public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
        @Autowired
        private JwtTokenUtil jwtTokenUtil;
    
        @Autowired
        private UserDetailsService userDetailsService
    
    ;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
                throws ServletException, IOException {
            String jwt = getJwtFromRequest(request);
            if (jwt != null && jwtTokenUtil.validateToken(jwt, jwtTokenUtil.getUsernameFromToken(jwt))) {
                String username = jwtTokenUtil.getUsernameFromToken(jwt);
                var userDetails = userDetailsService.loadUserByUsername(username);
                var authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
            chain.doFilter(request, response);
        }
    
        private String getJwtFromRequest(HttpServletRequest request) {
            String bearerToken = request.getHeader("Authorization");
            if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
                return bearerToken.substring(7);
            }
            return null;
        }
    }
    

    2.8 Configure Spring Security

    Create a new Java class named SecurityConfig in the config package:

    
        package com.example.springbootreactjwt.config;
    
    import com.example.springbootreactjwt.security.JwtAuthenticationFilter;
    import com.example.springbootreactjwt.service.UserServiceImpl;
    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.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.SecurityFilterChain;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {
    
        private final UserServiceImpl userService;
        private final JwtAuthenticationFilter jwtAuthenticationFilter;
    
        public SecurityConfig(UserServiceImpl userService, JwtAuthenticationFilter jwtAuthenticationFilter) {
            this.userService = userService;
            this.jwtAuthenticationFilter = jwtAuthenticationFilter;
        }
    
        @Bean
        public UserDetailsService userDetailsService() {
            return username -> {
                var user = userService.findByUsername(username);
                if (user == null) {
                    throw new UsernameNotFoundException("User not found");
                }
                return org.springframework.security.core.userdetails.User
                        .withUsername(user.getUsername())
                        .password(user.getPassword())
                        .roles(user.getRole())
                        .build();
            };
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
            return authenticationConfiguration.getAuthenticationManager();
        }
    
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http.csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(authorizeRequests ->
                    authorizeRequests.requestMatchers("/api/login").permitAll()
                        .anyRequest().authenticated()
                )
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
            return http.build();
        }
    }
    

    2.9 Create the AuthController Class

    In the controller package, create a new Java class named AuthController:

    
        package com.example.springbootreactjwt.controller;
    
    import com.example.springbootreactjwt.model.User;
    import com.example.springbootreactjwt.security.JwtTokenUtil;
    import com.example.springbootreactjwt.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    @RequestMapping("/api")
    public class AuthController {
    
        private final AuthenticationManager authenticationManager;
        private final JwtTokenUtil jwtTokenUtil;
        private final UserService userService;
    
        @Autowired
        public AuthController(AuthenticationManager authenticationManager, JwtTokenUtil jwtTokenUtil, UserService userService) {
            this.authenticationManager = authenticationManager;
            this.jwtTokenUtil = jwtTokenUtil;
            this.userService = userService;
        }
    
        @PostMapping("/login")
        public String login(@RequestBody User user) {
            try {
                var authenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
                var authentication = authenticationManager.authenticate(authenticationToken);
                var jwt = jwtTokenUtil.generateToken(authentication.getName());
                return jwt;
            } catch (AuthenticationException e) {
                return "Invalid credentials";
            }
        }
    }
    

    Step 3: Creating the Frontend with React

    3.1 Set Up React Project

    1.Open a terminal and navigate to your workspace directory.

    2.Create a new React project using Create React App:

    npx create-react-app react-frontend

    3.Navigate to the project directory:

    cd react-frontend

    3.2 Install Axios and React Router DOM

    Install Axios to make HTTP requests and React Router DOM for routing:

    npm install axios react-router-dom@6

    3.3 Install Bootstrap

    Install Bootstrap for styling:

    npm install bootstrap

    3.4 Create Components

    Create the necessary components for the user login functionality.

    3.4.1 Create AuthService.js

    Create a new file AuthService.js in the src directory to handle API requests:

    
     import axios from 'axios';
    
    const API_BASE_URL = "http://localhost:8080/api";
    
    class AuthService {
        login(credentials) {
            return axios.post(`${API_BASE_URL}/login`, credentials);
        }
    }
    
    export default new AuthService();
    

    3.4.2 Create LoginComponent.js

    Create a new file LoginComponent.js in the src/components directory:

    3.4.3 Create DashboardComponent.js

    Create a new file DashboardComponent.js in the src/components directory:

    
        import React from 'react';
    
    const DashboardComponent = () => {
        return (
             < div className="container mt-5">
                < h2>Dashboard< /h2>
                < p>Welcome to the dashboard!< /p>
            < /div>
        );
    };
    
    export default DashboardComponent;
    
    

    3.4.4 Create App.js

    Modify the App.js file to set up routing for the application:

    
        import React from 'react';
        import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
        import LoginComponent from './components/LoginComponent';
        import DashboardComponent from './components/DashboardComponent';
        
        const App = () => {
           return(
                < Router>
                    < div className="container">
                        < Routes>
                            < Route path="/" element={< LoginComponent />} />
                            < Route path="/login" element={< LoginComponent />} />
                            < Route path="/dashboard" element={< DashboardComponent />} />
                        < /Routes>
                    < /div>
                < /Router>
           ):
        };
        
        export default App;
        

    3.4.5 Update index.js

    
            Ensure the index.js file is set up correctly:
        
    
            import React from 'react';
            import ReactDOM from 'react-dom/client';
            import './index.css';
            import App from './App';
            import 'bootstrap/dist/css/bootstrap.min.css';
            
            const root
            
             = ReactDOM.createRoot(document.getElementById('root'));
            root.render(
                < React.StrictMode>
                    < App />
                < /React.StrictMode>
            );
            

    Step 4: Running the Application

    4.1 Run the Spring Boot Application

    1.Open the SpringBootReactJwtApplication class in the src/main/java/com/example/springbootreactjwt directory.

    2.Click the green Run button in your IDE or use the terminal to run the application:

    ./mvnw spring-boot:run

    4.2 Run the React Application

    1.Open a terminal and navigate to the react-frontend directory.

    2.Start the React application:

    
                        npm start
                    

    3.Open your web browser and navigate to http://localhost:3000.

    You can now use the login functionality provided by the React frontend and Spring Boot backend.

    Conclusion

    In this tutorial, we created a full-stack application using Spring Boot for the backend and React (with functional

    components and hooks) for the frontend. We implemented JWT-based authentication using Spring Security 6+

    and created a simple login page with React. This setup provides a solid foundation for developing more complex

    full-stack applications with JWT authentication.


    Related Spring and Spring Boot Tutorials/Guides: