Reactjs Jwt SpringBoot Token Authentication Example

Reactjs-SpringBoot-Jwt-Token-Authentication-Example

[no_toc]Tutorial: Reactjs Jwt SpringBoot Token Authentication Example – React.js Spring Security Login

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. And “How to build Reactjs Jwt SpringBoot Token Based Authentication Example?” is one of the most common questions for SpringBoot Java development world. So in the tutorial, I introduce how to implement an application “Reactjs JWT SpringBoot token Authentication Example” with details step by step and 100% running sourcecode.

– I give you an Epic of the application, a fullstack excutive flow from frontend to backend with overall architecture diagram.
– I give you a layer diagram of Reactjs JWT Application.
– I guide you detail-steps how to implement a security Jwt Token SpringBoot backend.
– I guide you step by step how to develop a Reactjs JWT Authentication application.
– Finally, I do an integrative testing from Reactjs JWT Authentication application to jwt SpringBoot Security RestAPIs.

Related posts:


What will we do?


Overview Reactjs Jwt SpringBoot Token Authentication Example

JWT Token Introduction

JSON Web Token (JWT) defines a compact and self-contained way for securely transmitting information between parties as a JSON object.

Scenarios where JSON Web Tokens are useful:

  • Authorization: the most common scenario for using JWT. Single Sign On is a feature that widely uses JWT
  • Information Exchange: Because JWTs can be signed, JSON Web Tokens are a good way of securely transmitting information between parties.

JSON Web Tokens consist of 3 parts:

  • Header
  • Payload
  • Signature

-> JWT looks like Header-Base64-String.Payload-Base64-String.Signature-Base64-String

Header consists of two parts:

  • token type.
  • hashing algorithm.

-> Example:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload contains the claims. Claims are statements about an entity and additional information.
There are 3 types of claims ->

  • Registered claims -> These are a set of predefined claims: iss (issuer), exp (expiration time), sub (subject)
  • Public claims
  • Private claims

Example:

{
  "sub": "thomasgkz",
  "iat": 1537603195,
  "exp": 1537689595
}

Signature -> To create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that.

Example:

HMACSHA512(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your-256-bit-secret
)

Combine all together, we get 3 Base64-URL strings separated by dots,

-> Example:

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0aG9tYXNna3oiLCJpYXQiOjE1Mzc2MDMxOTUsImV4cCI6MTUzNzY4OTU5NX0.m2YMjTYmOnfR7nnVNxqCzWbQ2FhKRe1eiizxnC2TF4eAoEzKlwo7PheVkKcxj08ST3vB-ZOIhiORvYVfSgzcog

When accessing a protected route or resource, the user agent should send the JWT, typically in the Authorization header using the Bearer schema.

Example:

Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0aG9tYXNna3oiLCJpYXQiOjE1Mzc2MDMxOTUsImV4cCI6MTUzNzY4OTU5NX0.m2YMjTYmOnfR7nnVNxqCzWbQ2FhKRe1eiizxnC2TF4eAoEzKlwo7PheVkKcxj08ST3vB-ZOIhiORvYVfSgzcog

Overall System Architecture Diagram

Reactjs JWT Authentication Overall Diagram
Reactjs JWT Authentication Overall Diagram

For the Reactjs JWT Authentication tutorial, we have 2 projects:
– Backend project (using SpringBoot or Nodejs Express) provides secured RestAPIs with JWT token.
– Reactjs project will request RestAPIs from Backend system with the JWT Token Authentication implementation.

JWT Authentication Sequence Diagram

The diagram below show how our system handles User Registration and User Login processes:

Reactjs Jwt Authentication Working Process Diagram
Reactjs Jwt Authentication Working Process Diagram

1. User Registration Phase:
– User uses a React.js register form to post user’s info (name, username, email, role, password) to Backend API /api/auth/signup.
– Backend will check the existing users in database and save user’s signup info to database. Finally, It will return a message (successfully or fail) to

2. User Login Phase:
– User posts user/password to signin to Backend RestAPI /api/auth/signin.
– Backend will check the username/password, if it is right, Backend will create and JWT string with secret then return it to Reactjs client.

After signin, user can request secured resources from backend server by adding the JWT token in Authorization Header. For each request, backend will check the JWT signature and then returns back the resources based on user’s registered authorities.

Reactjs JWT Authentication Diagram Overview

Reactjs JWT SpringBoot Authentication - Layer Diagram
Reactjs JWT SpringBoot Authentication – Layer Diagram

Reactjs JWT Authentication would be built with 5 main kind blocks:

  • Reactjs Router is a standard library for routing in React. It enables the navigation among views of various components in a React Application, allows changing the browser URL, and keeps the UI in sync with the URL.
  • Reactjs Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.
  • Reactjs Service is a bridge between Reactjs Component and Backend Server, it is used to do technical logic with Backend Server (using Ajax Engine to fetch data from Backend, or using Local Storage to save user login data) and returned a response data to React.js Components
  • Local Storage allow to save key/value pairs in a web browser. It is a place to save the login user’s info.
  • Axios – (an Ajax Engine) is a promise-based HTTP client for the browser and Node. js. Axios makes it easy to send asynchronous HTTP requests to REST endpoints and perform CRUD operations.

Jwt SpringBoot Token Security RestAPIs Diagram Overview

This is diagram for Spring Security/JWT (Springboot Token Based Authentication Example) classes that are separated into 3 layers:
– HTTP
– Spring Security
– REST API

Spring Security Jwt Token Authentication Architecture Diagram
Spring Security Jwt Token Authentication Architecture Diagram

Look at the diagram above, we can easily associate these components with Spring Security Authentication process: receive HTTP request, filter, authenticate, store Authentication data, generate token, get User details, authorize, handle exception…

At a glance:
SecurityContextHolder provides access to the SecurityContext.
SecurityContext holds the Authentication and possibly request-specific security information.
Authentication represents the principal which includes GrantedAuthority that reflects the application-wide permissions granted to a principal.
UserDetails contains necessary information to build an Authentication object from DAOs or other source of security data.
UserDetailsService helps to create a UserDetails from a String-based username and is usually used by AuthenticationProvider.
JwtAuthTokenFilter (extends OncePerRequestFilter) pre-processes HTTP request, from Token, create Authentication and populate it to SecurityContext.
JwtProvider validates, parses token String or generates token String from UserDetails.
UsernamePasswordAuthenticationToken gets username/password from login Request and combines into an instance of Authentication interface.
AuthenticationManager uses DaoAuthenticationProvider (with help of UserDetailsService & PasswordEncoder) to validate instance of UsernamePasswordAuthenticationToken, then returns a fully populated Authentication instance on successful authentication.
SecurityContext is established by calling SecurityContextHolder.getContext().setAuthentication(…​) with returned authentication object above.
AuthenticationEntryPoint handles AuthenticationException.
– Access to Restful API is protected by HTTPSecurity and authorized with Method Security Expressions.

When a HTTP request comes (from a browser, a web service client, an HttpInvoker or an AJAX application – Spring doesn’t care), it will go through a chain of filters for authentication and authorization purposes.

So, it is also true for a User Authentication request, that filter chain will be applied until relevant Authentication Filter is found.

In this architecture, we add our JwtAuthTokenFilter (that extends Spring OncePerRequestFilter abstract class) to the chain of filters.

class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ...
        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

JwtAuthTokenFilter validates the Token using JwtProvider:

class JwtAuthTokenFilter extends OncePerRequestFilter {
    @Autowired
    private JwtProvider tokenProvider;
 
    @Override
    protected void doFilterInternal(...) {
        String jwt = getJwt(request);
        if (jwt!=null && tokenProvider.validateJwtToken(jwt)) {
            ...
        }
        filterChain.doFilter(request, response);
    }
}

Now we have 2 cases for Springboot Token Based Authentication Example:
– Login/SignUp: RestAPI with non-protected APIs -> authenticate Login Request with AuthenticationManager, if error occurs, handle AuthenticationException with AuthenticationEntryPoint.
– With protected Resources:
+ jwt token is null/invalid -> if Authenticated Error occurs, handle AuthenticationException with AuthenticationEntryPoint.
+ jwt token is valid -> from token, get User information, then create AuthenticationToken.

More details, you can following a session of tutorial: Spring Security JWT Architecture – Springboot Token Based Authentication

Project Goal

We create a Reactjs JWT Authentication project as below:

Reactjs Jwt Authentication project structure
Reactjs Jwt Authentication project structure

It includes 8 components and 2 services and a router in app.js file.

– Home page:

Reactjs Home Page
Reactjs Home Page

– User Register page:

Reactjs JWT Authentication - Register Form Validation
Reactjs JWT Authentication – Register Form Validation

– Login Page:

reactjs jwt authentication - wrong login user - validation
reactjs jwt authentication – wrong login user – validation

– Profile Page:

Reactjs jwt authentication - sign in successfully
Reactjs jwt authentication – sign in successfully

– Use Page:

Reactjs jwt authentication - User Page Content
Reactjs jwt authentication – User Page Content

– Project Manager Page:

Reactjs JWT Authentication - PM Content
Reactjs JWT Authentication – PM Content

– Reactjs Admin page:

Reactjs jwt authentication - admin page
Reactjs jwt authentication – admin page

Jwt SpringBoot Token Authentication RestAPIs Implementation

Review SpringBoot Jwt Architecture Diagram

Spring Security Jwt Token Authentication Architecture Diagram
Spring Security Jwt Token Authentication Architecture Diagram

Details check the above session: Jwt SpringBoot Token Security RestAPIs Diagram Overview

Technology Stack

For Springboot Token Based Authentication Example, we use below stack of tech:
– Spring Boot
– jjwt – 0.9.0
– Spring Security
– Spring JPA
– MySQL

Project Structure

We create a SpringBoot project as below:

Reactjs Jwt Springboot Authentication Project Structure
Reactjs Jwt Springboot Authentication Project Structure

model package defines 2 entities User & Role that have many-to-many relationship:

Springboot Restapi Jwt Json Web Token Authentication Many To Many User Role
Springboot Restapi Jwt Json Web Token Authentication Many To Many User Role

repository package contains interfaces that use Hibernate JPA to store/retrieve data from MySQL database.
controller package defines RestAPIs for user signup/signin and testing protected resources that is secured with JWT.
message package defines payload data transferred from user agents (Browser/RestClient…) to RestAPIs and message back.
security package is the main part of the project that implements JWT security.

Jwt SpringBoot Token Authentication Goal

In the tutorial “Reactjs Jwt Springboot Token Based Authentication Example”, we expose 2 RestAPIs to signup and signin:

Sign up: /api/auth/signup

Reactjs JWT Authentication - register a customer with admin role
Reactjs JWT Authentication – register a customer with admin role

– Sign In /api/auth/signin:

JWT Authentication - Jack signin with PostMan to Backend
JWT Authentication – Jack signin with PostMan to Backend

– In the tutorial, We expose 3 RestAPIs to test protected resources:

@GetMapping("/api/test/user")
@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
public String userAccess() {
  return ">>> User Contents!";
}
 
@GetMapping("/api/test/pm")
@PreAuthorize("hasRole('PM') or hasRole('ADMIN')")
public String projectManagementAccess() {
  return ">>> Board Management Project";
}
 
@GetMapping("/api/test/admin")
@PreAuthorize("hasRole('ADMIN')")
public String adminAccess() {
  return ">>> Admin Contents";
}

– Example Access Successfully:

Jack with USER_ROLE retrieves user resources from backend restapi
Jack with USER_ROLE retrieves user resources from backend restapi

– Example Unauthorized:

Jack can NOT access PM resources
Jack can NOT access PM resources

Create Jwt Springboot Project

In the tutorial Springboot Token Based Authentication Example, We create a SpringBoot project with below dependencies:


<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-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt</artifactId>
  <version>0.9.0</version>
</dependency>

If you use MySQL database, add more the below dependency:

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <scope>runtime</scope>
</dependency>

If you use PostgreSQL database, add more the below dependency:

<dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <scope>runtime</scope>
</dependency>

Create User Defined Models for Jwt authentication

User.java model contains 5 attributes:

  • id
  • name
  • username
  • email
  • password
package com.ozenero.jwtauthentication.model;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

import org.hibernate.annotations.NaturalId;

@Entity
@Table(name = "users", uniqueConstraints = {
        @UniqueConstraint(columnNames = {
            "username"
        }),
        @UniqueConstraint(columnNames = {
            "email"
        })
})
public class User{
	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    @Size(min=3, max = 50)
    private String name;

    @NotBlank
    @Size(min=3, max = 50)
    private String username;

    @NaturalId
    @NotBlank
    @Size(max = 50)
    @Email
    private String email;

    @NotBlank
    @Size(min=6, max = 100)
    private String password;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "user_roles", 
    	joinColumns = @JoinColumn(name = "user_id"), 
    	inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set roles = new HashSet<>();

    public User() {}

    public User(String name, String username, String email, String password) {
        this.name = name;
        this.username = username;
        this.email = email;
        this.password = password;
    }

    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 getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Set getRoles() {
        return roles;
    }

    public void setRoles(Set roles) {
        this.roles = roles;
    }
}

Role.java model contains 2 attributes:

  • id
  • rolename
package com.ozenero.jwtauthentication.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.annotations.NaturalId;

@Entity
@Table(name = "roles")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Enumerated(EnumType.STRING)
    @NaturalId
    @Column(length = 60)
    private RoleName name;

    public Role() {}

    public Role(RoleName name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public RoleName getName() {
        return name;
    }

    public void setName(RoleName name) {
        this.name = name;
    }
}

RoleName.java:

package com.ozenero.jwtauthentication.model;

public enum  RoleName {
    ROLE_USER,
    ROLE_PM,
    ROLE_ADMIN
}

Implement UserRepository

UserRepository:

package com.ozenero.jwtauthentication.repository;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.ozenero.jwtauthentication.model.User;

@Repository
public interface UserRepository extends JpaRepository {
    Optional findByUsername(String username);
    Boolean existsByUsername(String username);
    Boolean existsByEmail(String email);
}

RoleRepository.java:

package com.ozenero.jwtauthentication.repository;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.ozenero.jwtauthentication.model.Role;
import com.ozenero.jwtauthentication.model.RoleName;

@Repository
public interface RoleRepository extends JpaRepository {
    Optional findByName(RoleName roleName);
}

Implement Jwt Token Authentication Spring Security

– Configure WebSecurityConfig.java:

package com.ozenero.jwtauthentication.security;

import org.springframework.beans.factory.annotation.Autowired;
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.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.ozenero.jwtauthentication.security.jwt.JwtAuthEntryPoint;
import com.ozenero.jwtauthentication.security.jwt.JwtAuthTokenFilter;
import com.ozenero.jwtauthentication.security.services.UserDetailsServiceImpl;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
		prePostEnabled = true
)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserDetailsServiceImpl userDetailsService;

    @Autowired
    private JwtAuthEntryPoint unauthorizedHandler;

    @Bean
    public JwtAuthTokenFilter authenticationJwtTokenFilter() {
        return new JwtAuthTokenFilter();
    }

    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable().
                authorizeRequests()
                .antMatchers("/api/auth/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        
        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

@EnableWebSecurity is used to enable web security in a project.
@EnableGlobalMethodSecurity(prePostEnabled = true) is used to enable Spring Security global method security.

-> Example:

@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
public String userAccess() {
 
@GetMapping("/api/test/pm")
@PreAuthorize("hasRole('PM') or hasRole('ADMIN')")
 
@GetMapping("/api/test/admin")
@PreAuthorize("hasRole('ADMIN')")

PasswordEncoder uses the BCrypt strong hashing function.

Implement UserDetails Service

UserDetailsServiceImpl implements UserDetailsService that will override loadUserByUsername method. loadUserByUsername method will find a record from users database tables to build a UserDetails object for authentication.

package com.ozenero.jwtauthentication.security.services;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.ozenero.jwtauthentication.model.User;
import com.ozenero.jwtauthentication.repository.UserRepository;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    UserRepository userRepository;

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
    	
        User user = userRepository.findByUsername(username)
                	.orElseThrow(() -> 
                        new UsernameNotFoundException("User Not Found with -> username or email : " + username)
        );

        return UserPrinciple.build(user);
    }
}

-> UserPrinciple will implement UserDetails.
UserPrinciple is not used directly by Spring Security for security purposes.
It simply stores user information which is later encapsulated into Authentication objects. This allows non-security related user information (such as email addresses, telephone numbers etc) to be stored.

package com.ozenero.jwtauthentication.security.services;

import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.ozenero.jwtauthentication.model.User;

public class UserPrinciple implements UserDetails {
	private static final long serialVersionUID = 1L;

	private Long id;

    private String name;

    private String username;

    private String email;

    @JsonIgnore
    private String password;

    private Collection authorities;

    public UserPrinciple(Long id, String name, 
			    		String username, String email, String password, 
			    		Collection authorities) {
        this.id = id;
        this.name = name;
        this.username = username;
        this.email = email;
        this.password = password;
        this.authorities = authorities;
    }

    public static UserPrinciple build(User user) {
        List authorities = user.getRoles().stream().map(role ->
                new SimpleGrantedAuthority(role.getName().name())
        ).collect(Collectors.toList());

        return new UserPrinciple(
                user.getId(),
                user.getName(),
                user.getUsername(),
                user.getEmail(),
                user.getPassword(),
                authorities
        );
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public Collection getAuthorities() {
        return authorities;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        
        UserPrinciple user = (UserPrinciple) o;
        return Objects.equals(id, user.id);
    }
}

Jwt SpringBoot Authentication classes

JwtAuthTokenFilter extends OncePerRequestFilter.

org.springframework.web.filter.OncePerRequestFilter
-> Executes once per request. This is a filter base class that is used to guarantee a single execution per request dispatch. It provides a doFilterInternal method with HttpServletRequest and HttpServletResponse arguments.

In JwtAuthTokenFilter class, the doFilterInternal method will do:

  • get JWT token from header
  • validate JWT
  • parse username from validated JWT
  • load data from users table, then build an authentication object
  • set the authentication object to Security Context
package com.ozenero.jwtauthentication.security.jwt;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import com.ozenero.jwtauthentication.security.services.UserDetailsServiceImpl;


public class JwtAuthTokenFilter extends OncePerRequestFilter {

    @Autowired
    private JwtProvider tokenProvider;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    private static final Logger logger = LoggerFactory.getLogger(JwtAuthTokenFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
    								HttpServletResponse response, 
    								FilterChain filterChain) 
    										throws ServletException, IOException {
        try {
        	
            String jwt = getJwt(request);
            if (jwt!=null && tokenProvider.validateJwtToken(jwt)) {
                String username = tokenProvider.getUserNameFromJwtToken(jwt);

                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authentication 
                		= new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception e) {
            logger.error("Can NOT set user authentication -> Message: {}", e);
        }

        filterChain.doFilter(request, response);
    }

    private String getJwt(HttpServletRequest request) {
        String authHeader = request.getHeader("Authorization");
        	
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
        	return authHeader.replace("Bearer ","");
        }

        return null;
    }
}

JwtAuthEntryPoint is used to handle Error exception when having unauthorized requests.

package com.ozenero.jwtauthentication.security.jwt;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

@Component
public class JwtAuthEntryPoint implements AuthenticationEntryPoint {

    private static final Logger logger = LoggerFactory.getLogger(JwtAuthEntryPoint.class);
    
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException e) 
                        		 throws IOException, ServletException {
    	
        logger.error("Unauthorized error. Message - {}", e.getMessage());
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error -> Unauthorized");
    }
}

JwtProvider is an util class, it implements useful functions:

  • generate a JWT token
  • valiate a JWT token
  • parse username from JWT token
package com.ozenero.jwtauthentication.security.jwt;

import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import com.ozenero.jwtauthentication.security.services.UserPrinciple;

import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;

/**
 * @Copyright https://ozenero.com
 * @author ozenero.com
 *
 */

@Component
public class JwtProvider {

    private static final Logger logger = LoggerFactory.getLogger(JwtProvider.class);

    @Value("${ozenero.app.jwtSecret}")
    private String jwtSecret;

    @Value("${ozenero.app.jwtExpiration}")
    private int jwtExpiration;

    public String generateJwtToken(Authentication authentication) {

        UserPrinciple userPrincipal = (UserPrinciple) authentication.getPrincipal();

        return Jwts.builder()
		                .setSubject((userPrincipal.getUsername()))
		                .setIssuedAt(new Date())
		                .setExpiration(new Date((new Date()).getTime() + jwtExpiration*1000))
		                .signWith(SignatureAlgorithm.HS512, jwtSecret)
		                .compact();
    }
    
    public boolean validateJwtToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
            logger.error("Invalid JWT signature -> Message: {} ", e);
        } catch (MalformedJwtException e) {
            logger.error("Invalid JWT token -> Message: {}", e);
        } catch (ExpiredJwtException e) {
            logger.error("Expired JWT token -> Message: {}", e);
        } catch (UnsupportedJwtException e) {
            logger.error("Unsupported JWT token -> Message: {}", e);
        } catch (IllegalArgumentException e) {
            logger.error("JWT claims string is empty -> Message: {}", e);
        }
        
        return false;
    }
    
    public String getUserNameFromJwtToken(String token) {
        return Jwts.parser()
			                .setSigningKey(jwtSecret)
			                .parseClaimsJws(token)
			                .getBody().getSubject();
    }
}

Implement Security SpringBoot Rest-Controllers

- Create Payload Message

LoginForm.java contains username & password ->

package com.ozenero.jwtauthentication.message.request;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

public class LoginForm {
    @NotBlank
    @Size(min=3, max = 60)
    private String username;

    @NotBlank
    @Size(min = 6, max = 40)
    private String password;

    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;
    }
}

SignUpForm.java contains:

  • name
  • username
  • email
  • role
  • password
package com.ozenero.jwtauthentication.message.request;

import java.util.Set;

import javax.validation.constraints.*;

public class SignUpForm {
    @NotBlank
    @Size(min = 3, max = 50)
    private String name;

    @NotBlank
    @Size(min = 3, max = 50)
    private String username;

    @NotBlank
    @Size(max = 60)
    @Email
    private String email;
    
    private Set role;
    
    @NotBlank
    @Size(min = 6, max = 40)
    private String password;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
    
    public Set getRole() {
    	return this.role;
    }
    
    public void setRole(Set role) {
    	this.role = role;
    }
}

JwtResponse.java is returned by SpringBoot server after successful authentication, it contains 2 parts:

  • JWT Token
  • Schema Type of Token
package com.ozenero.jwtauthentication.message.response;

public class JwtResponse {
    private String token;
    private String type = "Bearer";

    public JwtResponse(String accessToken) {
        this.token = accessToken;
    }

    public String getAccessToken() {
        return token;
    }

    public void setAccessToken(String accessToken) {
        this.token = accessToken;
    }

    public String getTokenType() {
        return type;
    }

    public void setTokenType(String tokenType) {
        this.type = tokenType;
    }
}

AuthRestAPIs.java defines 2 APIs:

  • /api/auth/signup: sign up
    -> check username/email is already in use.
    -> create User object
    -> store to database
  • /api/auth/signin: sign in
    -> attempt to authenticate with AuthenticationManager bean.
    -> add authentication object to SecurityContextHolder
    -> Generate JWT token, then return JWT to client
package com.ozenero.jwtauthentication.controller;

import java.util.HashSet;
import java.util.Set;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.CrossOrigin;
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;

import com.ozenero.jwtauthentication.message.request.LoginForm;
import com.ozenero.jwtauthentication.message.request.SignUpForm;
import com.ozenero.jwtauthentication.message.response.JwtResponse;
import com.ozenero.jwtauthentication.model.Role;
import com.ozenero.jwtauthentication.model.RoleName;
import com.ozenero.jwtauthentication.model.User;
import com.ozenero.jwtauthentication.repository.RoleRepository;
import com.ozenero.jwtauthentication.repository.UserRepository;
import com.ozenero.jwtauthentication.security.jwt.JwtProvider;

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/auth")
public class AuthRestAPIs {

    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    UserRepository userRepository;

    @Autowired
    RoleRepository roleRepository;

    @Autowired
    PasswordEncoder encoder;

    @Autowired
    JwtProvider jwtProvider;

    @PostMapping("/signin")
    public ResponseEntity authenticateUser(@Valid @RequestBody LoginForm loginRequest) {

        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        loginRequest.getUsername(),
                        loginRequest.getPassword()
                )
        );

        SecurityContextHolder.getContext().setAuthentication(authentication);

        String jwt = jwtProvider.generateJwtToken(authentication);
        return ResponseEntity.ok(new JwtResponse(jwt));
    }

    @PostMapping("/signup")
    public ResponseEntity registerUser(@Valid @RequestBody SignUpForm signUpRequest) {
        if(userRepository.existsByUsername(signUpRequest.getUsername())) {
            return new ResponseEntity("Fail -> Username is already taken!",
                    HttpStatus.BAD_REQUEST);
        }

        if(userRepository.existsByEmail(signUpRequest.getEmail())) {
            return new ResponseEntity("Fail -> Email is already in use!",
                    HttpStatus.BAD_REQUEST);
        }

        // Creating user's account
        User user = new User(signUpRequest.getName(), signUpRequest.getUsername(),
                signUpRequest.getEmail(), encoder.encode(signUpRequest.getPassword()));

        Set strRoles = signUpRequest.getRole();
        Set roles = new HashSet<>();

        strRoles.forEach(role -> {
        	switch(role) {
	    		case "admin":
	    			Role adminRole = roleRepository.findByName(RoleName.ROLE_ADMIN)
	                .orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find."));
	    			roles.add(adminRole);
	    			
	    			break;
	    		case "pm":
	            	Role pmRole = roleRepository.findByName(RoleName.ROLE_PM)
	                .orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find."));
	            	roles.add(pmRole);
	            	
	    			break;
	    		default:
	        		Role userRole = roleRepository.findByName(RoleName.ROLE_USER)
	                .orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find."));
	        		roles.add(userRole);        			
        	}
        });
        
        user.setRoles(roles);
        userRepository.save(user);

        return ResponseEntity.ok().body("User registered successfully!");
    }
}package com.ozenero.jwtauthentication.controller;

import java.util.HashSet;
import java.util.Set;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.CrossOrigin;
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;

import com.ozenero.jwtauthentication.message.request.LoginForm;
import com.ozenero.jwtauthentication.message.request.SignUpForm;
import com.ozenero.jwtauthentication.message.response.JwtResponse;
import com.ozenero.jwtauthentication.model.Role;
import com.ozenero.jwtauthentication.model.RoleName;
import com.ozenero.jwtauthentication.model.User;
import com.ozenero.jwtauthentication.repository.RoleRepository;
import com.ozenero.jwtauthentication.repository.UserRepository;
import com.ozenero.jwtauthentication.security.jwt.JwtProvider;

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/auth")
public class AuthRestAPIs {

    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    UserRepository userRepository;

    @Autowired
    RoleRepository roleRepository;

    @Autowired
    PasswordEncoder encoder;

    @Autowired
    JwtProvider jwtProvider;

    @PostMapping("/signin")
    public ResponseEntity authenticateUser(@Valid @RequestBody LoginForm loginRequest) {

        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        loginRequest.getUsername(),
                        loginRequest.getPassword()
                )
        );

        SecurityContextHolder.getContext().setAuthentication(authentication);

        String jwt = jwtProvider.generateJwtToken(authentication);
        return ResponseEntity.ok(new JwtResponse(jwt));
    }

    @PostMapping("/signup")
    public ResponseEntity registerUser(@Valid @RequestBody SignUpForm signUpRequest) {
        if(userRepository.existsByUsername(signUpRequest.getUsername())) {
            return new ResponseEntity("Fail -> Username is already taken!",
                    HttpStatus.BAD_REQUEST);
        }

        if(userRepository.existsByEmail(signUpRequest.getEmail())) {
            return new ResponseEntity("Fail -> Email is already in use!",
                    HttpStatus.BAD_REQUEST);
        }

        // Creating user's account
        User user = new User(signUpRequest.getName(), signUpRequest.getUsername(),
                signUpRequest.getEmail(), encoder.encode(signUpRequest.getPassword()));

        Set strRoles = signUpRequest.getRole();
        Set roles = new HashSet<>();

        strRoles.forEach(role -> {
        	switch(role) {
	    		case "admin":
	    			Role adminRole = roleRepository.findByName(RoleName.ROLE_ADMIN)
	                .orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find."));
	    			roles.add(adminRole);
	    			
	    			break;
	    		case "pm":
	            	Role pmRole = roleRepository.findByName(RoleName.ROLE_PM)
	                .orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find."));
	            	roles.add(pmRole);
	            	
	    			break;
	    		default:
	        		Role userRole = roleRepository.findByName(RoleName.ROLE_USER)
	                .orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find."));
	        		roles.add(userRole);        			
        	}
        });
        
        user.setRoles(roles);
        userRepository.save(user);

        return ResponseEntity.ok().body("User registered successfully!");
    }
}

TestRestAPIs define 3 RestAPIs:

  • /api/test/user -> access by users has USER_ROLE or ADMIN_ROLE
  • /api/test/pm -> access by users has USER_PM or ADMIN_ROLE
  • /api/test/admin -> access by users has ADMIN_ROLE
package com.ozenero.jwtauthentication.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestRestAPIs {
	
	@GetMapping("/api/test/user")
	@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
	public String userAccess() {
		return ">>> User Contents!";
	}

	@GetMapping("/api/test/pm")
	@PreAuthorize("hasRole('PM') or hasRole('ADMIN')")
	public String projectManagementAccess() {
		return ">>> Board Management Project";
	}
	
	@GetMapping("/api/test/admin")
	@PreAuthorize("hasRole('ADMIN')")
	public String adminAccess() {
		return ">>> Admin Contents";
	}
}

Configure Jwt SpringBoot Application Properties

- Configure for MySQL database:

spring.datasource.url=jdbc:mysql://localhost:3306/ozenerodb
spring.datasource.username=root
spring.datasource.password=12345
spring.jpa.generate-ddl=true

# App Properties
ozenero.app.jwtSecret=jwtLoizenai.comSecretKey
ozenero.app.jwtExpiration=86400

- Configure for PostgreSQL database:

## PostgreSQL
spring.datasource.url=jdbc:postgresql://localhost:5432/ozenerodb
spring.datasource.username=postgres
spring.datasource.password=123
#drop & create table again, good for testing, comment this in production
spring.jpa.hibernate.ddl-auto=create

Testing Jwt SpringBoot RestAPIs

– Start Springboot server by commandline mvn spring-boot:run.
– Insert data to roles table :

INSERT INTO roles(name) VALUES('ROLE_USER');
INSERT INTO roles(name) VALUES('ROLE_PM');
INSERT INTO roles(name) VALUES('ROLE_ADMIN');

Sign-Up 3 users:

- Jack has ROLE_USER role
- Adam has ROLE_PM & ROLE_USER roles
- Thomas has ROLE_ADMIN role

Reactjs JWT Authentication - register a customer with admin role
Reactjs JWT Authentication - register a customer with admin role

– Check database’s tables:

Check MySQL database - add Thomas - admin user
Check MySQL database - add Thomas - admin user

SignIn with normal user

– Jack can access api/test/user url, can NOT access others.

-> Sign In:

jwt springboot register user successfully
jwt springboot register user successfully

Access Protected Resources:

jwt springboot authentication - access user content successfully
jwt springboot authentication - access user content successfully
jwt springboot token authentication - can not access pm content with user role
jwt springboot token authentication - can not access pm content with user role

SignIn with PM user

– Adam can access api/test/user and api/test/pm url.
Can NOT access /api/test/admin url.

-> Sign In:

Adam SignIn successfully
Adam SignIn successfully

-> Access Protected Resources:

Adam can retrieve USER resources
Adam can retrieve USER resources
Adam can access PM resources
Adam can access PM resources
Adam can NOT access Admin resources
Adam can NOT access Admin resources

SignIn with Admin user

– Thomas can access all URLs.

-> Sign In:

Thomas Signin
Thomas Signin

-> Access Protected Resource:

Thomas access projected resource
Thomas access projected resource

Reactjs Login JWT Token Authentication Implementation

Review Reactjs Login jwt token architecture Diagram

Reactjs JWT Authentication - Layer Diagram
Reactjs JWT Authentication - Layer Diagram

More details you can see more at above session: Reactjs JWT Authentication Diagram Overview

Step by step to build Reactjs JWT Authentication Application

Reactjs Jwt Authentication project structure
Reactjs Jwt Authentication project structure

For building the Reactjs JWT Authentication, we do below steps:

  • We create Reactjs Jwt Authentication application
  • We implement React.js Authentication Service. It provides RestAPIs to signin/signup and retrieve user's login info from Local Storage
  • We implement React.js Backend Service. It provides RestAPIs to retrieve data from Security Backend
  • We implement Reactjs components to signIn/signUp and show data from backend server
  • We define a Reactjs router to navigate between UI components

Setup Reactjs Project

Create React App is a command line utility that generates React projects for you. It’s a convenient tool because it also offers commands that will build and optimize your project for production.

The create-react-app will set up everything you need to run a React application.

– Create a new project in the app directory with Yarn.

yarn create react-app Reactjs-Jwt-Authentication

Project Structure:

Start Setup React project structure
Start Setup React project structure

More details you can see at: Create Reactjs Project

After the app creation process completes, navigate into the app directory and install Bootstrap, React Router, and Reactstrap.

Reactstrap: This library contains React Bootstrap 4 components that favor composition and control. The library does not depend on jQuery or Bootstrap javascript.
– React Router: Components are the heart of React’s powerful, declarative programming model. React Router is a collection of navigational components that compose declaratively with your application.

cd Reactjs-Jwt-Authentication
yarn add bootstrap react-cookie react-router-dom reactstrap

Implement Reactjs JWT Authentication Service

The service AuthenticationService implements 4 main functions to signup/signin/signout and get current login user's info:

To do http requests with Backend server, the AuthenticationService use Axios (an HttpClient) to interact with Server. AuthenticationService uses localStorage to store user login's info.

import axios from "axios";

/**
 * @Copyright by https://ozenero.com
 *        youtube ozenero
 */

class AuthenticationService {
  signin = (username, password) => {
      return axios.post("/api/auth/signin", {username, password})
        .then(response => {
          if (response.data.accessToken) {
            localStorage.setItem("user", JSON.stringify(response.data));
          }
          return response.data;
        })
        .catch(err => {
          console.log(err);
          throw err;
        });
  }

  signOut() {
    localStorage.removeItem("user");
  }

  register = async(firstname, lastname, username, email, password) => {
    return axios.post("/api/auth/signup", {
      firstname,
      lastname,
      username,
      email,
      password
    });
  }

  getCurrentUser() {
    return JSON.parse(localStorage.getItem('user'));;
  }
}

export default new AuthenticationService();

Implement Reactjs JWT Backend Service

BackendService is used to get resources from server:
- getUserBoard()
- getPmBoard()
- getAdminBoard()

With each request from BackendService to server, we use axios interceptors to attach the Authorization header with JWT token.

import axios from 'axios';

// Add a request interceptor
axios.interceptors.request.use( config => {
  const user = JSON.parse(localStorage.getItem('user'));

  if(user && user.accessToken){
    const token = 'Bearer ' + user.accessToken;
    config.headers.Authorization =  token;
  }

  return config;
});

class BackendService {
  async getUserBoard() {
    return await axios.get("/api/test/user");
  }

  async getPmBoard() {
    return await axios.get("/api/test/pm");
  }

  async getAdminBoard() {
    return await axios.get("/api/test/admin");
  }
}

export default new BackendService();

Implement Reactjs JWT AppNavbar component

Reactjs jwt authentication - navigation bar
Reactjs jwt authentication - navigation bar
import React, { Component } from 'react';
import { Collapse, Nav, Navbar, NavbarBrand, NavbarToggler, NavbarText, NavItem, NavLink } from 'reactstrap';
import { Link } from 'react-router-dom';

import { withRouter } from 'react-router-dom';

import AuthenticationService from '../services/AuthenticationService';

class AppNavbar extends Component {
  constructor(props) {
    super(props);
    this.state = {isOpen: false};
    this.toggle = this.toggle.bind(this);

    this.state = {
      showUser: false,
      showPM: false,
      showAdmin: false,
      username: undefined,
      login: false
    };
  }

  componentDidMount() {
    const user = AuthenticationService.getCurrentUser();

    if (user) {
      const roles = [];

      user.authorities.forEach(authority => {
        roles.push(authority.authority)
      });
  
      this.setState({
        showUser: true,
        showPM: roles.includes("ROLE_PM") || roles.includes("ROLE_ADMIN"),
        showAdmin: roles.includes("ROLE_ADMIN"),
        login: true,
        username: user.username
      });
    }
  }

  signOut = () => {
    AuthenticationService.signOut();
    this.props.history.push('/home');
    window.location.reload();
  }

  toggle() {
    this.setState({
      isOpen: !this.state.isOpen
    });
  }

  render() {
    return <Navbar color="dark" dark expand="md">
      <NavbarBrand tag={Link} to="/home">Loizenai.com</NavbarBrand>
      <Nav className="mr-auto">
        <NavLink href="/home">Home</NavLink>
        {this.state.showUser && <NavLink href="/user">User</NavLink>}
        {this.state.showPM && <NavLink href="/pm">PM</NavLink>}
        {this.state.showAdmin && <NavLink href="/admin">Admin</NavLink>}
      </Nav>
      <NavbarToggler onClick={this.toggle}/>
      <Collapse isOpen={this.state.isOpen} navbar>
        {
          this.state.login ? (
            <Nav className="ml-auto" navbar>
              <NavItem>
                  <NavbarText>
                    Signed in as: <a href="/profile">{this.state.username}</a>
                  </NavbarText>
              </NavItem>
              <NavItem>
                <NavLink href="#" onClick={this.signOut}>SignOut</NavLink>
              </NavItem>
            </Nav>                 
          ) : (
            <Nav className="ml-auto" navbar>
              <NavItem>
                <NavLink href="/signin">Login</NavLink>
              </NavItem>
              <NavItem>
                <NavLink href="/signup">SignUp</NavLink>
              </NavItem>
            </Nav>
          )
        }
      </Collapse>
    </Navbar>;
  }
}

export default withRouter(AppNavbar);

- In AppNavbar component, We use componentDidMount() to load user info, and then extracts data from user login info to determine what links would be showed on React.js's UI.

Implement Reactjs JWT Home component

Reactjs Home Page
Reactjs Home Page
import React, { Component } from 'react';
import AppNavbar from './AppNavbar';
import { Link } from 'react-router-dom';
import { Button, Container } from 'reactstrap';
import { Alert } from "react-bootstrap"

class Home extends Component {

  constructor(props) {
    super(props);
  }

  componentDidMount() {
  }

  render() {
    return (
      <div>
        <AppNavbar/>
        <Container fluid>
          <div style={{marginTop:"20px"}}>
            <Alert variant="primary">
              <h2>Reactjs JWT Authentication Application</h2>
              <Button color="success"><Link to="/signin"><span style={{color:"white"}}>Login</span></Link></Button>
            </Alert>
          </div>
        </Container>
      </div>
    );
  }
}

export default Home;

Implement Reactjs JWT SignUp component

Reactjs Jwt Authentication - Register Form
Reactjs Jwt Authentication - Register Form

- For posting data from register-form to backend server, SignUp component uses the register(...) function of AuthenticationService service.

- SignUp component implements a changeHandler() function to save the values from input fields of Register form to the state object of SignUp component.

- The function changeHandler() also includes segment code to validate input data from register-form:

Reactjs JWT Authentication - Register Form Validation
Reactjs JWT Authentication - Register Form Validation

- Detail Coding:

import React, { Component } from 'react';
import AppNavbar from './AppNavbar';
import { Container } from 'reactstrap';
import { Button, Form, FormGroup, Input, Label, Row, Col } from "reactstrap";
import { Alert } from "react-bootstrap"

import Authentication from '../services/AuthenticationService'

const validEmailRegex = RegExp(/^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i);

const validateForm = (errors) => {
  let valid = true;
  Object.values(errors).forEach(
    (val) => val.length > 0 && (valid = false)
  );
  return valid;
}

class SignUp extends Component {
  
  constructor(props) {
    super(props);
    this.state = {
      firstname: "",
      lastname: "",
      username: "",
      email: "",
      password: "",
      message: "",
      successful: false,
      validForm: true,
      errors: {
        firstname: '',
        lastname: '',
        username: '',
        email: '',
        password: ''
      }
    };
  }

  changeHandler = (event) => {
    const { name, value } = event.target;
  
    let errors = this.state.errors;

    switch (name) {
      case 'firstname':
        errors.firstname = 
          value.length < 3
            ? 'FirstName must be 3 characters long!'
            : '';
        break;
      case 'lastname':
        errors.lastname = 
          value.length < 3
            ? 'LastName must be 3 characters long!'
            : '';
        break;
      case 'username':
        errors.username = 
          value.length < 5
            ? 'Username must be 5 characters long!'
            : '';
        break;
      case 'email': 
        errors.email = 
          validEmailRegex.test(value)
            ? ''
            : 'Email is not valid!';
        break;
      case 'password': 
        errors.password = 
          value.length < 8
            ? 'Password must be 8 characters long!'
            : '';
        break;
      default:
        break;
    }
  
    this.setState({errors, [name]: value}, ()=> {
        console.log(errors)
    })  
  }

  signUp = (e) => {
    e.preventDefault();
    const valid = validateForm(this.state.errors);
    this.setState({validForm: valid});
    if(valid){
      Authentication.register(
        this.state.firstname,
        this.state.lastname,
        this.state.username,
        this.state.email,
        this.state.password
      ).then(
        response => {
          this.setState({
            message: response.data.message,
            successful: true
          });
        },
        error => {
          console.log("Fail! Error = " + error.toString());
          
          this.setState({
            successful: false,
            message: error.toString()
          });
        }
      );  
    }
  }

  render() {
    const title = <h2>Register User</h2>;
    const errors = this.state.errors;

    let alert = "";

    if(this.state.message){
      if(this.state.successful){
        alert = (
                  <Alert variant="success">
                    {this.state.message}
                  </Alert>
                );
      }else{
        alert = (
                  <Alert variant="danger">
                    {this.state.message}
                  </Alert>
                );
      }
    }

    return ( 
      <div>
        <AppNavbar/>
        <Container fluid>
          <Row>
          <Col sm="12" md={{ size: 4, offset: 4 }}>
          {title}
            <Form onSubmit={this.signUp}>
              <FormGroup controlId="forFirstname">
                <Label for="firstname">First Name</Label>
                <Input
                  type="text" 
                  placeholder="Enter First Name"
                  name="firstname" id="firstname"
                  value={this.state.firstname}
                  autoComplete="firstname"
                  onChange={this.changeHandler}
                />
                {
                  errors.firstname && ( 
                      <Alert variant="danger">
                        {errors.firstname}
                      </Alert>
                    )
                }
              </FormGroup>

              <FormGroup controlId="forLastname">
                <Label for="lastname">Last Name</Label>
                <Input
                  type="text" 
                  placeholder="Enter Last Name"
                  name="lastname" id="lastname"
                  value={this.state.lastname}
                  autoComplete="lastname"
                  onChange={this.changeHandler}
                />
                {
                  errors.lastname && ( 
                      <Alert variant="danger">
                        {errors.firstname}
                      </Alert>
                    )
                }
              </FormGroup>

              <FormGroup controlId="forUsername">
                <Label for="username">Username</Label>
                <Input
                  type="text" 
                  placeholder="Enter UserName"
                  name="username" id="username"
                  value={this.state.username}
                  autoComplete="username"
                  onChange={this.changeHandler}
                />
                {
                  errors.username && ( 
                      <Alert variant="danger">
                        {errors.username}
                      </Alert>
                    )
                }
              </FormGroup>

              <FormGroup controlId="formEmail">
                <Label for="email">Email</Label>
                <Input required
                  type="text" 
                  placeholder="Enter Email"
                  name="email" id="email"
                  value={this.state.email}
                  autoComplete="email"
                  onChange={this.changeHandler}
                />
                {
                  errors.email && ( 
                      <Alert variant="danger">
                        {errors.email}
                      </Alert>
                    )
                }
              </FormGroup>

              <FormGroup controlId="formPassword">
                <Label for="password">Password</Label>
                <Input required 
                  type="password" 
                  placeholder="Enter Password"
                  name="password" id="password"
                  value={this.state.password}
                  autoComplete="password"
                  onChange={this.changeHandler}
                />
                {
                  errors.password && ( 
                      <Alert key="errorspassword" variant="danger">
                        {errors.password}
                      </Alert>
                    )
                }
              </FormGroup>

              <Button variant="primary" type="submit">
                Create
              </Button>
              {
                !this.state.validForm && (
                  <Alert key="validForm" variant="danger">
                    Please check the inputs again!
                  </Alert>
                )
              }

              {alert}
            </Form>
            </Col>
          </Row>
        </Container>
      </div>);
  }
}

export default SignUp;

Implement Reactjs JWT Login component

reactjs jwt authentication - wrong login user - validation
reactjs jwt authentication - wrong login user - validation

- Login component implement a function doLogin that uses a function signin() of AuthenticationService service to signin to secured Backend server with Jwt token.

import React, { Component } from 'react';
import AppNavbar from './AppNavbar';
import { Container } from 'reactstrap';
import { Form, Alert, FormGroup, Input, Label, Row, Col } from "reactstrap";
import {Button} from 'react-bootstrap';
import AuthenticationService from "../services/AuthenticationService";
import avatar from '../../avatar.png';

import '../../App.css';

class Login extends Component {

  constructor(props) {
    super(props);

    this.state = {
      username: "",
      password: "",
      error: ""
    };
  }

  changeHandler = (event) => {
    let nam = event.target.name;
    let val = event.target.value;
    this.setState({[nam]: val});
  }

  doLogin = async (event) => {
    event.preventDefault();

    AuthenticationService
        .signin(this.state.username, 
                  this.state.password)
      .then(
        () => {
          this.props.history.push('/profile');
        },
        error => {
          console.log("Login fail: error = { " + error.toString() + " }");
          this.setState({error: "Can not signin successfully ! Please check username/password again"});
        }
    );
  }

  render() {
    return ( 
      <div>
        <AppNavbar/>
        <Container fluid>
          <Row style={{marginTop:"20px"}}>
          <Col sm="12" md={{ size: 3, offset: 4 }}>
            <div style={{marginBottom: "10px"}}>
              <img src={avatar} alt="Avatar" className="avatar center" 
                style={{width: "50%", height: "auto"}}/>
            </div>
            <Form  onSubmit={this.doLogin}>
              <FormGroup>
                <Label for="username"><strong>Username</strong></Label>
                <Input autoFocus 
                  type="text"
                  name="username" id="username"
                  value={this.state.username}
                  placeholder="Enter Username"
                  autoComplete="username"
                  onChange={this.changeHandler}
                />
              </FormGroup>

              <FormGroup>
                <Label for="password"><strong>Password</strong></Label>
                <Input type="password" 
                  name="password" id="password"
                  value={this.state.password}
                  placeholder="Enter Password"
                  autoComplete="password"
                  onChange={this.changeHandler}
                />
              </FormGroup>

              <Button type="submit" variant="primary" size="lg" block>
                Sign In
              </Button>
              {
                this.state.error && (
                  <Alert color="danger">
                    {this.state.error}
                  </Alert>
                )
              }
            </Form>
            </Col>
          </Row>
        </Container>
      </div>);
  }
}

export default Login;

Implement Reactjs JWT Profile component

Profile component just uses a function componentDidMount() that calls a function getCurrentUser() of AuthenticationService service to get the current user login's info and shows them on UI.

Reactjs jwt authentication - sign in with a role admi
Reactjs jwt authentication - sign in with a role admi
import React, { Component } from 'react';
import AppNavbar from './AppNavbar';
import { Link } from 'react-router-dom';
import { Button, Container } from 'reactstrap';
import { Alert } from "react-bootstrap"

import AuthenticationService from '../services/AuthenticationService';

class Profile extends Component {

  constructor(props) {
    super(props);
    this.state = {user: undefined};
  }

  componentDidMount() {
    const user = AuthenticationService.getCurrentUser();
    this.setState({user: user});
  }

  render() {
    let userInfo = "";
    const user = this.state.user;

    // login
    if (user && user.accessToken) {

      let roles = "";

      user.authorities.forEach(authority => {
        roles = roles + " " + authority.authority
      });

      userInfo = (
                <div style={{marginTop:"20px"}}>
                  <Alert variant="info">
                    <h2>User Info</h2>
                    <ul>
                      <li>Username: {user.username}</li>
                      <li>Access Token: {user.accessToken}</li>
                      <li>Authorities: {roles}</li>
                    </ul>
                  </Alert>
                </div>
              );
    } else { // not login
      userInfo = <div style={{marginTop:"20px"}}>
                    <Alert variant="primary">
                      <h2>Profile Component</h2>
                      <Button color="success"><Link to="/signin"><span style={{color:"white"}}>Login</span></Link></Button>
                    </Alert>
                  </div>
    }

    return (
      <div>
        <AppNavbar/>
        <Container fluid>
        {userInfo}
        </Container>
      </div>
    );
  }
}

export default Profile;

Implement Reactjs jwt token User Page

Reactjs jwt authentication - User Page Content
Reactjs jwt authentication - User Page Content

In the componentDidMount() function, the component UserPage uses the service BackendService to load user-content from secured backend-server and show it on UI.

import AppNavbar from './AppNavbar';
import React, { Component } from 'react';
import { Container } from 'reactstrap';
import BackendService from '../services/BackendService';
import { Alert } from "react-bootstrap"

class UserPage extends Component {
  constructor(props) {
    super(props);
    this.state={
      content: "",
      error: ""
    }
  }

  componentDidMount() {
    BackendService.getUserBoard()
      .then( response => {
        this.setState({content: response.data})
      } , error => {
        console.log(error);
        this.setState({
          error: error.toString()
        }); 
      });
  }

  render() {
    return (
      <div>
        <AppNavbar/>
        <Container fluid>
            {
              this.state.content ? (
                <div style={{marginTop: "20px"}}>
                  <Alert variant="info">
                    <h2>{this.state.content}</h2>
                  </Alert>
                </div>
              ) : (
                <div style={{marginTop: "20px"}}>
                  <Alert variant="danger">
                    {this.state.error}
                  </Alert>
                </div>
              )
            }
        </Container>
      </div>
    );
  }
}

export default UserPage;

Implement Reactjs jwt token PM Page

Reactjs JWT Authentication - PM Content
Reactjs JWT Authentication - PM Content

In the componentDidMount() function, the component ProjectManagerPage uses the service BackendService to load pm-content from secured backend-server and show it on UI.

import AppNavbar from './AppNavbar';
import React, { Component } from 'react';
import { Container } from 'reactstrap';
import { Alert } from "reactstrap";
import BackendService from '../services/BackendService';

class ProjectManagerPage extends Component {
  constructor(props) {
    super(props);
    this.state={
      content: "",
      error: ""
    }
  }

  componentDidMount() {
    BackendService.getPmBoard()
    .then( response => {
      this.setState({
        content: response.data
      })
    } , error => {
      console.log(error);
      this.setState({
        error: error.toString()
      }); 
    });
  }

  render() {
    return (
      <div>
        <AppNavbar/>
        <Container fluid>
          {
            this.state.content ? (
              <div style={{marginTop: "20px"}}>
                <Alert color="info">
                  <h2>{this.state.content}</h2>
                </Alert>
              </div>
            ) : (
              <div style={{marginTop: "20px"}}>
                <Alert color="danger">
                  {this.state.error}
                </Alert>
              </div>
            )
          }
        </Container>
      </div>
    );
  }
}

export default ProjectManagerPage;

Implement Reactjs jwt token Admin Page

Reactjs jwt authentication - admin page
Reactjs jwt authentication - admin page

In the componentDidMount() function, the component AdminPage uses the service BackendService to load admin-content from secured backend-server and show it on UI.

import AppNavbar from './AppNavbar';
import React, { Component } from 'react';
import { Container } from 'reactstrap';
import { Alert } from "reactstrap";
import BackendService from '../services/BackendService';

class AdminPage extends Component {
  constructor(props) {
    super(props);
    this.state={
      content: "",
      error: ""
    }
  }

  componentDidMount() {
    BackendService.getAdminBoard()
      .then( response => {
        this.setState({
          content: response.data
        })
      } , error => {
        console.log(error);
        this.setState({
          error: error.toString()
        }); 
      });    
  }

  render() {
    return (
      <div>
        <AppNavbar/>
        <Container fluid>
          {
            this.state.content ? (
              <div style={{marginTop: "20px"}}>
                <Alert variant="info">
                  <h2>{this.state.content}</h2>
                </Alert>
              </div>
            ) : (
              <div style={{marginTop: "20px"}}>
                <Alert variant="danger">
                  {this.state.error}
                </Alert>
              </div>
            )
          }
        </Container>
      </div>
    );
  }
}

export default AdminPage;

Define Reactjs Router

App.js uses React Router to navigate between components:

  • path “/” and "/home" is mapped with Home component
  • path “/profile” is mapped with Profile component
  • path “/user” is mapped with UserPage component
  • path “/pm” is mapped with ProjectManagerPage component
  • path “/admin” is mapped with Admin component
  • path “/signin” is mapped with Login component
  • path “/signup” is mapped with SignUp component
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import './App.css';
import Home from './app/components/Home';
import Profile from './app/components/Profile';
import UserPage from './app/components/UserPage';
import ProjectManagerPage from './app/components/ProjectManagerPage';
import SignUp from './app/components/SignUp';
import AdminPage from './app/components/AdminPage';
import Login from './app/components/Login';

class App extends Component {
  render() {
    return (
      <Router>
        <Switch>
          <Route path='/' exact={true} component={Home}/>
          <Route path='/home' exact={true} component={Home}/>
          <Route path='/profile' exact={true} component={Profile}/>
          <Route path='/user' exact={true} component={UserPage}/>
          <Route path='/pm' exact={true} component={ProjectManagerPage}/>
          <Route path='/admin' exact={true} component={AdminPage}/>
          <Route path='/signin' exact={true} component={Login}/>
          <Route path='/signup' exact={true} component={SignUp}/>  
        </Switch>
      </Router>
    )
  }
}

export default App;

Configure URL proxy request

To proxy from /api to http://localhost:8080/api, add a proxy setting to app/package.json.

"scripts": {...},
"proxy": "http://localhost:8080",

Integrative Testing – Reactjs JWT Authentication with Backend Security

- Start Backend Server at port 8080.
- Start Reactjs application with cmd: yarn start

Testcase 1 – Reactjs JWT SignUp

Reactjs jwt authentication - Jack is create successfully
Reactjs jwt authentication - Jack is create successfully

- Check MySQL database:

Check MySQL database - jack user is registered successfully
Check MySQL database - jack user is registered successfully

Testcase 2 – Reactjs JWT Login with user role

reactjs jwt authentication - wrong login user - validation
reactjs jwt authentication - wrong login user - validation

Then re-login with right username/password: jack-ozenero.com/123456789.

-> after signin successfully, Reactjs application will be redirected to Profile component:

Reactjs jwt authentication - sign in successfully
Reactjs jwt authentication - sign in successfully

- Get User content:

Reactjs jwt authentication - User Page Content
Reactjs jwt authentication - User Page Content

Testcase 3 – Reactjs JWT SignOut

To signout, just click to the signout link at the most right side of Navigation-Bar, after signout successfully, Reactjs application will be redirected to Home page.

Reactjs Home Page
Reactjs Home Page

Testcase 4 – Reactjs JWT SignIn with other roles

1. Register a Adam user, with PM_ROLE and USER_ROLE:

Reactjs jwt authentication - register a user successfully with 2 roles user and pm
Reactjs jwt authentication - register a user successfully with 2 roles user and pm

- Check MySQL:

Check MySQL database - add Adam user with PM roles
Check MySQL database - add Adam user with PM roles

- Adam logins successfully with below user login's info:

Reatjs jwt authentication - login with a user with PM roles successfully
Reatjs jwt authentication - login with a user with PM roles successfully

2. Register Thomas user, with ADMIN_ROLE:

Reactjs JWT Authentication - register a customer with admin role
Reactjs JWT Authentication - register a customer with admin role

- Check MySQL:

Check MySQL database - add Thomas - admin user
Check MySQL database - add Thomas - admin user

- Access the admin-content:

Reactjs jwt authentication - sign in with a role admi
Reactjs jwt authentication - sign in with a role admi
Reactjs jwt authentication - admin page
Reactjs jwt authentication - admin page

Further Reading

Related posts:


Sourcecode

Full sourcecode: "Reactjs Jwt SpringBoot Token Authentication Example - React.js Spring Security Login"

1. Reactjs Frontend:

reactjs-jwt-token-authentication

Github sourceode:

reactjs-jwt-authentication-github

2. Jwt SpringBoot Token Authentication Backend:

jwt-springboot-reactjs-token-authentication-example

Github sourcode:

jwt-springboot-reactjs-jwt-authentication

0 0 votes
Article Rating
Subscribe
Notify of
guest
19 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments