Featured resource
Tech Upskilling Playbook 2025
Tech Upskilling Playbook

Build future-ready tech teams and hit key business milestones with seven proven plays from industry leaders.

Learn more
  • Labs icon Lab
  • Core Tech
Labs

Guided: Secure User Authentication with JWT in Full-stack Java with Spring Boot 3 and React

Build real world security skills by developing a full stack Java application with robust user authentication from the ground up. In this guided Code Lab, you’ll implement secure login and registration using JSON Web Tokens (JWT), Spring Boot 3, and React. Going step-by-step, you will learn how to protect APIs, manage user sessions without compromising security, and build a modern authentication flow trusted by technology teams. New to authentication? Looking to develop your secure coding skills? This hands-on lab will give you practical experience that’s immediately applicable to enterprise level applications. At the end, you'll have a working application, a solid grasp of JWT based security, and confidence in building secure full-stack solutions. This is perfect for aspiring full stack developers, security conscious engineers, and anyone looking to understand modern web authentication in action.

Labs

Path Info

Level
Clock icon Beginner
Duration
Clock icon 1h 2m
Last updated
Clock icon Aug 07, 2025

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. Challenge

    Backend: Security Configuration

    A chain in web security refers to a sequence of Filter instances that process an incoming HTTP request before it reaches the main application logic. These filters are executed one after another in a defined order.

    Each Filter performs a specific task, such as authentication, authorization, logging, input validation, or modifying request data. This allows security and request processing logic to be handled separately from business logic.

    When a request arrives, it passes through the filter chain. Each filter can inspect or change the request, block it, or pass it on to the next filter. Only if the request passes all checks will it reach the intended endpoint.

    This approach helps create clean, modular, and secure web applications by centralizing cross-cutting concerns like security and monitoring.

    info > If you get stuck, there are files with completed code for each task located in the solution folder in your filetree. The method generateToken(UserDetails userDetails) is a shortcut that creates a JWT (JSON Web Token) for a user without any extra claims. It internally calls another method that accepts both custom claims and user details, passing in an empty map to indicate no additional claims. This simplifies token generation when only basic user information is needed. Allowed methods refer to the specific HTTP actions such as GET, POST, PUT, and DELETE that a server explicitly permits for a given endpoint or resource. Each of these methods serves a different purpose: for example, GET is used to retrieve data, POST to create new data, PUT to update existing data, and DELETE to remove data.

    When a client (like a web browser or mobile app) makes a request to the server, it must use one of the methods that the server allows for that particular endpoint. If the client tries to use a method that is not supported for example, sending a DELETE request to an endpoint that only allows GET and POST the server will reject the request and respond with a 405 Method Not Allowed error.

    This helps enforce proper use of the API and protects the application from unintended or harmful actions.

  2. Challenge

    Backend: User Management

    To improve security and data integrity, it's important to apply validation and uniqueness constraints to user input, especially for critical fields like usernames. In this task, you'll be updating the User entity to ensure the username field cannot be left blank, is limited to a maximum of 50 characters, and must be unique across all users.

    These constraints help prevent invalid data from being stored and ensure consistency when processing user registrations or logins. When a user registers, it's important to construct their account securely and cleanly. Using the builder pattern to create a new User object improves code readability and maintainability.

    Additionally, encoding the password before saving it helps prevent sensitive information from being stored in plain text, which is a critical security practice. In this task, you’ll implement user registration logic that validates and securely creates a new user with default settings, such as assigning the ROLE_USER role. To protect user credentials, passwords should never be stored in plain text. Instead, they should be securely hashed using a strong algorithm like BCrypt. BCrypt is a widely accepted and secure hashing function designed specifically for password storage. By adding a BCrypt password encoder to your application configuration, you ensure that all passwords are encoded before being saved. This setup is a standard security best practice in any modern authentication system.

  3. Challenge

    Backend: Authentication System

    To allow users to authenticate securely, you need to create a login endpoint in your application. This endpoint will accept a POST request with user credentials, validate them, and then pass the information to the authenticationService. If the credentials are valid, the service will generate and return an authentication response, typically including a token. The endpoint will respond with an HTTP 200 OK status to indicate a successful login. This setup is essential for enabling secure, stateless authentication in modern web applications. In a JWT-based authentication system, users don't send their credentials with every request. Instead, they include a JWT token in the request header. To process this securely, you need a filter that validates the token on each incoming request. This filter checks if the token is valid and, if so, sets up an authenticated security context for the request.

    This allows Spring Security to recognize the user and authorize access to protected resources without requiring them to log in again. This step is essential to enabling stateless authentication using JWT. To protect sensitive parts of your application, such as admin features, you can use role-based access control. In Spring Boot, this is done by securing controller endpoints with annotations like @PreAuthorize. By restricting certain endpoints to users with specific roles (e.g., ADMIN), you ensure that only authorized personnel can access critical functionality. In this task, you'll secure two endpoints under the /api/v1/admin path so that only users with the ADMIN role can access them. This is a core part of enforcing proper access control within your application.

  4. Challenge

    Frontend: Authentication UI

    The login form is a crucial component that allows users to enter their credentials and access the application securely. To do this properly, the form must include input fields for both the username and password, where the password input is specifically marked as type="password" to mask the input. In this task, you’ll update the Login.js file to include these fields using Material UI’s <TextField> component, which provides a consistent and user-friendly input design. To maintain a user’s authenticated session across page refreshes or browser restarts, authentication data must be securely stored in the browser. This includes the JWT access token (used for API authentication), a refresh token (used to get a new access token when the current one expires), and the user's basic information (e.g. username and role).

    Storing these items in localStorage allows the frontend to persist the login state and manage session continuity. In this task, you’ll update the AuthContext.js file to store this data after both login and registration events. In a frontend application, protected routes ensure that only authenticated users, or users with specific roles, can access certain pages. This is essential for implementing role-based access control (RBAC). In React, this can be achieved by creating a custom PrivateRoute component that checks the user's authentication and role. If a user lacks the required role, they are redirected to a safe location (e.g., the dashboard), preventing unauthorized access to restricted pages.

    In this task, you'll secure access to the /dashboards route so that only authenticated users with the proper role can view it.

  5. Challenge

    Frontend: User Experience

    When building a frontend application with authentication, it’s important to customize the user interface based on the user’s role and login status. This improves both security and user experience.

    In this task, you'll update the navigation bar (Navbar.js) to conditionally display a link to the Admin Dashboard only when the user is both authenticated and has the role ROLE_ADMIN. This ensures that only authorized users see and can access admin-related pages, using React Router's Link component and Material UI's Button for styling. An admin dashboard typically displays sensitive or privileged data, so it must be protected and loaded only by authenticated administrators.

    In this task, you'll update the AdminDashboard.js component to fetch data securely from the backend using Axios. You'll create an asynchronous function called fetchDashboardData, which will request the admin data from a protected API endpoint. If successful, the data will be saved to state for rendering. If it fails, the component will display an error message and log the error to the console for debugging. The user dashboard is a personalized area where users can view content specific to their account. A common and user-friendly feature in multi-user applications is to greet users by name and display their role. This helps confirm their identity and shows that they’re accessing the correct view.

    In this task, you'll update the Dashboard.js file to show a dynamic welcome message using Material UI’s Typography component. It will insert the user's username and role into the message for a simple, effective personalization experience.

  6. Challenge

    Frontend: Security Features

    A secure logout function is critical to protecting user data and preventing unauthorized access after a session ends. When a user logs out, all authentication related data such as tokens and user details, must be cleared from the browser. Additionally, any default headers used for API calls, like the Authorization header in Axios, must be removed to stop sending the token in future requests.

    In this task, you'll update the AuthContext.js file to include a logout function that handles all of these steps, ensuring the session is properly and securely terminated. In a React application, using a context provider is a clean and efficient way to share global state and functions, such as authentication logic, across multiple components. The AuthContext allows components anywhere in the app to access the current user, authentication status, and relevant functions like login, register, and logout.

    In this task, you'll define an object called value that bundles all of this authentication-related state and behavior. This object will be passed to the context provider so other components can easily consume it. Great, with all the tasks complete, Start the application.

    1. Start the back end. Using the first Terminal tab, start spring-boot, by typing mvn spring-boot:run --offline. Note: this will take a moment to start spring-boot running.
    2. Start the front end. Using the second Terminal tab, navigate to to the front end folder by typing cd frontend. Then start the front end by typing npm start
    3. Once the front end loads, use the Browser tab to navigate to http://localhost:3000/, this will load the application in the browser.

    Congratulations on coming this far and completing the Code Lab Secure User Authentication with JWT in Full-stack Java with Spring Boot 3 and React!

Tim Coakley is a Senior Security Solutions Architect for a large multi-national organisation and an author at PluralSight. Tim started a long and successful full-time career in Digital Forensics supporting the criminal justice system and law enforcement on a long list of criminal cases.

What's a lab?

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Provided environment for hands-on practice

We will provide the credentials and environment necessary for you to practice right within your browser.

Guided walkthrough

Follow along with the author’s guided walkthrough and build something new in your provided environment!

Did you know?

On average, you retain 75% more of your learning if you get time for practice.