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
Frontend Project Directory:
Following is the screenshot of our application -
User Registration:
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
@Servicepublic 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
@SpringBootApplicationpublic 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