ReactJS, Spring Boot JWT Authentication Example

In this tutorial, we’ll create a user registration & login example using ReactJS, Spring Boot, Spring Security, and JWT authentication. You could download the source code from our Github repository, the download link is provided at the end of this tutorial.

Technologies used

Backend Technologies:

  • Java 17
  • Spring Boot 2.7.0
  • Spring Security
  • Spring Data JPA
  • JWT
  • H2 Database

Frontend Technologies:

  • React 17.0.1
  • Axios 0.27.2
  • Redux 4.0.5
  • Bootstrap 4.5.2

ReactJS - SpringBoot - JWT - Flow



Backend Project Directory:



Frontend Project Directory:



Following is the screenshot of our application -

User Registration:


User Signin:


Profile View:


Access Resource:


We will build two projects: 

1. Backend:  spring-boot-security-jwt
2. Frontend: react-redux-jwt

Project 1: spring-boot-security-jwt

Pom.xml

<?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
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.knf.dev</groupId>
<artifactId>spring-boot-security-jwt</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-security-jwt</name>
<description>Demo project for Spring Boot </description>

<properties>
<java.version>17</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</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.1</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>



User.java

@Entity
@Table(name = "auser")
public class User {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(unique = true)
private String username;
@Column(unique = true)
private String email;
private String password;

public User(String username, String email, String password) {
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 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 User() {
super();
}
}

The @Entity annotation specifies that the class is an entity and is mapped to a database table. The @Table annotation specifies the name of the database table to be used for mapping. The @Id annotation specifies the primary key of an entity and the @GeneratedValue provides for the specification of generation strategies for the values of primary keys.



UserRepository.java

import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import com.knf.dev.models.User;

public interface UserRepository
extends JpaRepository<User, String> {
Optional<User> findByUsername(String username);
Boolean existsByUsername(String username);
Boolean existsByEmail(String email);
}



LoginRequest.java

public class LoginRequest {

private String username;
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;
}
}



SignupRequest.java

public class SignupRequest {

private String username;
private String email;
private String password;

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



JwtResponse.java

public class JwtResponse {
private String token;
private String type = "Bearer";
private Long id;
private String username;
private String email;

public JwtResponse(String accessToken,
Long id, String username, String email) {

this.token = accessToken;
this.id = id;
this.username = username;
this.email = email;

}

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

public Long getId() {
return id;
}

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

public String getEmail() {
return email;
}

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

public String getUsername() {
return username;
}

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



MessageResponse.java

public class MessageResponse {

private String message;

public MessageResponse(String message) {
this.message = message;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}
}



UserDetailsImpl.java

public class UserDetailsImpl implements UserDetails {

private static final long serialVersionUID = 1L;

private Long id;

private String username;

private String email;

@JsonIgnore
private String password;

public UserDetailsImpl(Long id, String username,
String email, String password) {

this.id = id;
this.username = username;
this.email = email;
this.password = password;

}

public static UserDetailsImpl build(User user) {

return new UserDetailsImpl(user.getId(),
user.getUsername(), user.getEmail(),
user.getPassword());
}

public Long getId() {
return id;
}

public String getEmail() {
return email;
}

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

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

@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;
UserDetailsImpl user = (UserDetailsImpl) o;
return Objects.equals(id, user.id);
}

@Override
public Collection<? extends GrantedAuthority>
getAuthorities() {
return null;
}
}



UserDetailsServiceImpl.java

@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: " + username));

return UserDetailsImpl.build(user);
}
}



WebSecurityConfig.java

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {

@Autowired
private AuthEntryPointJwt unauthorizedHandler;

@Autowired
UserDetailsServiceImpl userDetailsService;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http)
throws Exception {

http.cors().and().csrf().disable().exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy
(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/api/auth/**")
.permitAll()
.antMatchers("/api/test/**")
.permitAll().anyRequest().authenticated();

http.addFilterBefore(authenticationJwtTokenFilter(),
UsernamePasswordAuthenticationFilter.class);

return http.build();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

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

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



AuthController.java

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

@Autowired
AuthenticationManager authenticationManager;

@Autowired
UserRepository userRepository;

@Autowired
PasswordEncoder encoder;

@Autowired
JwtUtils jwtUtils;

@PostMapping("/signin")
public ResponseEntity<?> authenticateuser
(@RequestBody LoginRequest loginRequest) {

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

SecurityContextHolder.getContext()
.setAuthentication(authentication);
String jwt = jwtUtils.generateJwtToken(authentication);

UserDetailsImpl userDetails = (UserDetailsImpl)
authentication.getPrincipal();

return ResponseEntity
.ok(new JwtResponse(jwt, userDetails.getId(),
userDetails.getUsername(),
userDetails.getEmail()));
}

@PostMapping("/signup")
public ResponseEntity<?> registerUser
(@RequestBody SignupRequest signUpRequest) {

if (userRepository.existsByUsername(signUpRequest
.getUsername())) {

return ResponseEntity.badRequest()
.body(new MessageResponse
("Error: username is already taken!"));
}

if (userRepository.existsByEmail
(signUpRequest.getEmail())) {

return ResponseEntity.badRequest()
.body(new MessageResponse
("Error: Email is already in use!"));
}

// Create new user account
User user = new User(signUpRequest.getUsername(),
signUpRequest.getEmail(),
encoder.encode(signUpRequest.getPassword()));

userRepository.save(user);

return ResponseEntity
.ok(new MessageResponse("user registered successfully!"));
}
}



UserController.java

@CrossOrigin(origins = "*", maxAge = 4800)
@RestController
@RequestMapping("/api/test")
public class UserController {

@GetMapping("/all")
public MessageResponse allAccess() {
return new MessageResponse("Server is up.....");
}

@GetMapping("/greeting")
@PreAuthorize("isAuthenticated()")
public MessageResponse userAccess() {

return new MessageResponse
("Congratulations! You are an authenticated user.");
}
}



application.yaml

knf:
app:
jwtExpirationMs: 76300000
jwtSecret: knowledgeFactory



Application.java

@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}



Project 2: Frontend: react-redux-jwt

package.json

{
"name": "react-redux-jwt",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"bootstrap": "^4.5.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-redux": "^7.2.1",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.3",
"react-validation": "^3.0.7",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"axios": "^0.27.2",
"validator": "^13.1.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"redux-devtools-extension": "^2.13.8"
}
}



./services/user.service.js

import axios from "axios";
import authHeader from "./auth-header";

const API_URL = "http://localhost:8080/api/test/";

class UserService {
getPublicContent() {
return axios.get(API_URL + "all");
}

getGreetings() {
return axios.get(API_URL + "greeting", { headers: authHeader() });
}
}

export default new UserService();



./services/auth.service.js

import axios from "axios";

const API_URL = "http://localhost:8080/api/auth/";

class AuthService {
login(username, password) {
return axios
.post(API_URL + "signin", { username, password })
.then((response) => {
if (response.data.accessToken) {
localStorage.setItem("user", JSON.stringify(response.data));
}

return response.data;
});
}

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

register(username, email, password) {
return axios.post(API_URL + "signup", {
username,
email,
password,
});
}
}

export default new AuthService();



./services/auth-header.js

export default function authHeader() {

const user = JSON.parse(localStorage.getItem("user"));

if (user && user.accessToken) {
return { Authorization: "Bearer " + user.accessToken };

} else {
return {};
}
}



./components/login.component.js

import React, { Component } from "react";
import { Redirect } from 'react-router-dom';
import Form from "react-validation/build/form";
import Input from "react-validation/build/input";
import CheckButton from "react-validation/build/button";
import { connect } from "react-redux";
import { login } from "../actions/auth";

const required = (value) => {
if (!value) {
return (
<div className="alert alert-danger" role="alert">
This field is required!
</div>
);
}
};

class Login extends Component {
constructor(props) {
super(props);
this.handleLogin = this.handleLogin.bind(this);
this.onChangeUsername = this.onChangeUsername.bind(this);
this.onChangePassword = this.onChangePassword.bind(this);

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

onChangeUsername(e) {
this.setState({
username: e.target.value,
});
}

onChangePassword(e) {
this.setState({
password: e.target.value,
});
}

handleLogin(e) {
e.preventDefault();

this.setState({
loading: true,
});

this.form.validateAll();

const { dispatch, history } = this.props;

if (this.checkBtn.context._errors.length === 0) {
dispatch(login(this.state.username, this.state.password))
.then(() => {
history.push("/profile");
window.location.reload();
})
.catch(() => {
this.setState({
loading: false
});
});
} else {
this.setState({
loading: false,
});
}
}

render() {
const { isLoggedIn, message } = this.props;

if (isLoggedIn) {
return <Redirect to="/profile" />;
}

return (
<div className="col-md-12">
<div className="card bg-light text-dark">
<h1><center>Login</center></h1>


<Form
onSubmit={this.handleLogin}
ref={(c) => {
this.form = c;
}}
>
<div className="form-group">
<label htmlFor="username">Username</label>
<Input
type="text"
className="form-control"
name="username"
value={this.state.username}
onChange={this.onChangeUsername}
validations={[required]}
/>
</div>

<div className="form-group">
<label htmlFor="password">Password</label>
<Input
type="password"
className="form-control"
name="password"
value={this.state.password}
onChange={this.onChangePassword}
validations={[required]}
/>
</div>

<div className="form-group">
<button
className="btn btn-dark btn-block"
disabled={this.state.loading}
>
{this.state.loading && (
<span className=
"spinner-border spinner-border-sm"></span>
)}
<span>Login</span>
</button>
</div>

{message && (
<div className="form-group">
<div className="alert alert-danger" role="alert">
{message}
</div>
</div>
)}
<CheckButton
style={{ display: "none" }}
ref={(c) => {
this.checkBtn = c;
}}
/>
</Form>
</div>
</div>
);
}
}

function mapStateToProps(state) {
const { isLoggedIn } = state.auth;
const { message } = state.message;
return {
isLoggedIn,
message
};
}

export default connect(mapStateToProps)(Login);



./components/register.component.js

import React, { Component } from "react";
import Form from "react-validation/build/form";
import Input from "react-validation/build/input";
import CheckButton from "react-validation/build/button";
import { isEmail } from "validator";
import { connect } from "react-redux";
import { register } from "../actions/auth";

const required = (value) => {
if (!value) {
return (
<div className="alert alert-danger" role="alert">
This field is required!
</div>
);
}
};

const email = (value) => {
if (!isEmail(value)) {
return (
<div className="alert alert-danger" role="alert">
This is not a valid email.
</div>
);
}
};

const vusername = (value) => {
if (value.length < 3 || value.length > 20) {
return (
<div className="alert alert-danger" role="alert">
The username must be between 3 and 20 characters.
</div>
);
}
};

const vpassword = (value) => {
if (value.length < 6 || value.length > 40) {
return (
<div className="alert alert-danger" role="alert">
The password must be between 6 and 40 characters.
</div>
);
}
};

class Register extends Component {
constructor(props) {
super(props);
this.handleRegister = this.handleRegister.bind(this);
this.onChangeUsername = this.onChangeUsername.bind(this);
this.onChangeEmail = this.onChangeEmail.bind(this);
this.onChangePassword = this.onChangePassword.bind(this);

this.state = {
username: "",
email: "",
password: "",
successful: false,
};
}

onChangeUsername(e) {
this.setState({
username: e.target.value,
});
}

onChangeEmail(e) {
this.setState({
email: e.target.value,
});
}

onChangePassword(e) {
this.setState({
password: e.target.value,
});
}

handleRegister(e) {
e.preventDefault();

this.setState({
successful: false,
});

this.form.validateAll();

if (this.checkBtn.context._errors.length === 0) {
this.props
.dispatch(
register(this.state.username, this.state.email,
this.state.password)
)
.then(() => {
this.setState({
successful: true,
});
})
.catch(() => {
this.setState({
successful: false,
});
});
}
}

render() {
const { message } = this.props;

return (

<div className="col-md-12">
<div className="card bg-light text-dark">

<h1><center>User Registration </center></h1>


<Form
onSubmit={this.handleRegister}
ref={(c) => {
this.form = c;
}}
>
{!this.state.successful && (
<div>
<div className="form-group">
<label htmlFor="username">Username</label>
<Input
type="text"
className="form-control"
name="username"
value={this.state.username}
onChange={this.onChangeUsername}
validations={[required, vusername]}
/>
</div>

<div className="form-group">
<label htmlFor="email">Email</label>
<Input
type="text"
className="form-control"
name="email"
value={this.state.email}
onChange={this.onChangeEmail}
validations={[required, email]}
/>
</div>

<div className="form-group">
<label htmlFor="password">Password</label>
<Input
type="password"
className="form-control"
name="password"
value={this.state.password}
onChange={this.onChangePassword}
validations={[required, vpassword]}
/>
</div>

<div className="form-group">
<button className="btn btn-dark btn-block">
Sign Up</button>
</div>
</div>
)}

{message && (
<div className="form-group">
<div className={this.state.successful ?
"alert alert-success" : "alert alert-danger"} role="alert">
{message}
</div>
</div>
)}
<CheckButton
style={{ display: "none" }}
ref={(c) => {
this.checkBtn = c;
}}
/>
</Form>
</div>
</div>
);
}
}

function mapStateToProps(state) {
const { message } = state.message;
return {
message,
};
}

export default connect(mapStateToProps)(Register);



./components/profile.component.js

import React, { Component } from "react";
import { Redirect } from 'react-router-dom';
import { connect } from "react-redux";

class Profile extends Component {

render() {

const { user: currentUser } = this.props;

if (!currentUser) {
return <Redirect to="/login" />;
}

return (
<div class="card bg-light text-dark">
<h1>{currentUser.username}</h1>
<p>
<strong>Id:</strong> {currentUser.id}
</p>
<p>
<strong>Email:</strong> {currentUser.email}
</p>
<a href="#"><i class="fa fa-dribbble"></i></a>
<a href="#"><i class="fa fa-twitter"></i></a>
<a href="#"><i class="fa fa-linkedin"></i></a>
<a href="#"><i class="fa fa-facebook"></i></a>
</div>
);
}
}

function mapStateToProps(state) {
const { user } = state.auth;
return {
user,
};
}

export default connect(mapStateToProps)(Profile);



./components/user.component.js

import React, { Component } from "react";
import UserService from "../services/user.service";
import EventBus from "../common/EventBus";

export default class User extends Component {
constructor(props) {
super(props);

this.state = {
content: ""
};
}

componentDidMount() {
UserService.getGreetings().then(
response => {
this.setState({
content: response.data.message
});
},
error => {
this.setState({
content:
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString()
});

if (error.response && error.response.status === 401) {
EventBus.dispatch("logout");
}
}
);
}

render() {
return (


<div class="card bg-light text-dark">
<h3>{this.state.content}</h3>
<a href="#"><i class="fa fa-dribbble"></i></a>
<a href="#"><i class="fa fa-twitter"></i></a>
<a href="#"><i class="fa fa-linkedin"></i></a>
<a href="#"><i class="fa fa-facebook"></i></a>
</div>
);
}
}



./helpers/history.js

import { createBrowserHistory } from "history";

export const history = createBrowserHistory();



./reducers/auth.js

import {
REGISTER_SUCCESS,
REGISTER_FAIL,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT,
} from "../actions/types";

const user = JSON.parse(localStorage.getItem("user"));

const initialState = user
? { isLoggedIn: true, user }
: { isLoggedIn: false, user: null };

export default function (state = initialState, action) {
const { type, payload } = action;

switch (type) {
case REGISTER_SUCCESS:
return {
...state,
isLoggedIn: false,
};
case REGISTER_FAIL:
return {
...state,
isLoggedIn: false,
};
case LOGIN_SUCCESS:
return {
...state,
isLoggedIn: true,
user: payload.user,
};
case LOGIN_FAIL:
return {
...state,
isLoggedIn: false,
user: null,
};
case LOGOUT:
return {
...state,
isLoggedIn: false,
user: null,
};
default:
return state;
}
}



./reducers/index.js

import { combineReducers } from "redux";
import auth from "./auth";
import message from "./message";

export default combineReducers({
auth,
message,
});



./reducers/message.js

import { SET_MESSAGE, CLEAR_MESSAGE } from "../actions/types";

const initialState = {};

export default function (state = initialState, action) {
const { type, payload } = action;

switch (type) {
case SET_MESSAGE:
return { message: payload };

case CLEAR_MESSAGE:
return { message: "" };

default:
return state;
}
}



./comman/auth-verify.js

import React, { Component } from "react";
import { history } from '../helpers/history';

const parseJwt = (token) => {
try {
return JSON.parse(atob(token.split('.')[1]));
} catch (e) {
return null;
}
};

class AuthVerify extends Component {
constructor(props) {
super(props);

history.listen(() => {
const user = JSON.parse(localStorage.getItem("user"));

if (user) {
const decodedJwt = parseJwt(user.accessToken);

if (decodedJwt.exp * 1000 < Date.now()) {
props.logOut();
}
}
});
}

render() {
return <div></div>;
}
}

export default AuthVerify;



./comman/EventBus.js

const eventBus = {
on(event, callback) {
document.addEventListener(event, (e) => callback(e.detail));
},
dispatch(event, data) {
document.dispatchEvent(new CustomEvent(event, { detail: data }));
},
remove(event, callback) {
document.removeEventListener(event, callback);
},
};

export default eventBus;



./actions/auth.js

import {
REGISTER_SUCCESS,
REGISTER_FAIL,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT,
SET_MESSAGE,
} from "./types";

import AuthService from "../services/auth.service";

export const register =
(username, email, password) => (dispatch) => {

return AuthService.register(username, email, password).then(
(response) => {
dispatch({
type: REGISTER_SUCCESS,
});

dispatch({
type: SET_MESSAGE,
payload: response.data.message,
});

return Promise.resolve();
},
(error) => {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();

dispatch({
type: REGISTER_FAIL,
});

dispatch({
type: SET_MESSAGE,
payload: message,
});

return Promise.reject();
}
);
};

export const login = (username, password) => (dispatch) => {
return AuthService.login(username, password).then(
(data) => {
dispatch({
type: LOGIN_SUCCESS,
payload: { user: data },
});

return Promise.resolve();
},
(error) => {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();

dispatch({
type: LOGIN_FAIL,
});

dispatch({
type: SET_MESSAGE,
payload: message,
});

return Promise.reject();
}
);
};

export const logout = () => (dispatch) => {
AuthService.logout();

dispatch({
type: LOGOUT,
});
};



./actions/message.js

import { SET_MESSAGE, CLEAR_MESSAGE } from "./types";

export const setMessage = (message) => ({
type: SET_MESSAGE,
payload: message,
});

export const clearMessage = () => ({
type: CLEAR_MESSAGE,
});



./actions/types.js

export const REGISTER_SUCCESS = "REGISTER_SUCCESS";
export const REGISTER_FAIL = "REGISTER_FAIL";
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN_FAIL = "LOGIN_FAIL";
export const LOGOUT = "LOGOUT";
export const SET_MESSAGE = "SET_MESSAGE";
export const CLEAR_MESSAGE = "CLEAR_MESSAGE";



store.js

import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
import rootReducer from "./reducers";

const middleware = [thunk];

const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(...middleware))
);

export default store;



App.js

import React, { Component } from "react";
import { connect } from "react-redux";
import { Router, Switch, Route, Link } from "react-router-dom";

import "bootstrap/dist/css/bootstrap.min.css";
import "./App.css";
import Login from "./components/login.component";
import Register from "./components/register.component";
import Profile from "./components/profile.component";
import User from "./components/user.component";
import { logout } from "./actions/auth";
import { clearMessage } from "./actions/message";
import { history } from './helpers/history';
import EventBus from "./common/EventBus";

class App extends Component {
constructor(props) {
super(props);
this.logOut = this.logOut.bind(this);

this.state = {
currentUser: undefined,
};

history.listen((location) => {
props.dispatch(clearMessage());
});
}

componentDidMount() {
const user = this.props.user;

if (user) {
this.setState({
currentUser: user
});
}

EventBus.on("logout", () => {
this.logOut();
});
}

componentWillUnmount() {
EventBus.remove("logout");
}

logOut() {
this.props.dispatch(logout());
this.setState({
currentUser: undefined,
});
}

render() {
const { currentUser} = this.state;

return (
<Router history={history}>
<div>
<nav className="navbar navbar-expand navbar-dark bg-dark">
<Link to={"/"} className="navbar-brand">
Knowledgefactory
</Link>
<div className="navbar-nav mr-auto">
{currentUser && (
<li className="nav-item">
<Link to={"/user"} className="nav-link">
Resource
</Link>
</li>
)}
</div>

{currentUser ? (
<div className="navbar-nav ml-auto">
<li className="nav-item">
<Link to={"/profile"} className="nav-link">
Profile
</Link>
</li>
<li className="nav-item">
<a href="/login" className="nav-link"
onClick={this.logOut}>
LogOut
</a>
</li>
</div>
) : (
<div className="navbar-nav ml-auto">
<li className="nav-item">
<Link to={"/login"} className="nav-link">
Login
</Link>
</li>

<li className="nav-item">
<Link to={"/register"} className="nav-link">
Sign Up
</Link>
</li>
</div>
)}
</nav>

<div className="container mt-3">
<Switch>
<Route exact path={["/", "/register"]} component={Register} />
<Route exact path="/login" component={Login} />
<Route exact path="/register" component={Register} />
<Route exact path="/profile" component={Profile} />
<Route exact path="/user" component={User} />
</Switch>
</div>
</div>
</Router>
);
}
}

function mapStateToProps(state) {
const { user } = state.auth;
return {
user,
};
}

export default connect(mapStateToProps)(App);



App.css

label {
display: block;
margin-top: 10px;
}

.card-container.card {
max-width: 350px !important;
padding: 40px 40px;
}

.card {
background-color: #f7f7f7;
padding: 20px 25px 30px;
margin: 0 auto 25px;
margin-top: 50px;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
border-radius: 2px;

}

.profile-img-card {
width: 96px;
height: 96px;
margin: 0 auto 10px;
display: block;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
}



index.js

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import store from "./store";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
serviceWorker.unregister();



index.css

body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont,
'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans',
'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

code {
font-family: source-code-pro, Menlo, Monaco, Consolas,
'Courier New', monospace;
}



.env

PORT=9080


Download the complete source code - click here                                        

Local Setup and Run the application

Step1: Download or clone the source code from GitHub to a local machine - Click here


Backend


Step 2: mvn clean install


Step 3: Run the Spring Boot application - mvn spring-boot:run


Frontend


Step 4: npm install or yarn install


Step 5: npm start or yarn start

From the browser call the endpoint http://localhost:9080/

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

Spring Boot + Mockito simple application with 100% code coverage

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete