├── .env
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── react-redux-refresh-token-axios-jwt-flow.png
├── src
├── App.css
├── App.js
├── actions
│ ├── auth.js
│ ├── message.js
│ └── types.js
├── common
│ └── EventBus.js
├── components
│ ├── board-admin.component.js
│ ├── board-moderator.component.js
│ ├── board-user.component.js
│ ├── home.component.js
│ ├── login.component.js
│ ├── profile.component.js
│ └── register.component.js
├── helpers
│ └── history.js
├── index.css
├── index.js
├── reducers
│ ├── auth.js
│ ├── index.js
│ └── message.js
├── reportWebVitals.js
├── services
│ ├── api.js
│ ├── auth.service.js
│ ├── setupInterceptors.js
│ ├── token.service.js
│ └── user.service.js
├── setupTests.js
└── store.js
└── yarn.lock
/.env:
--------------------------------------------------------------------------------
1 | PORT=8081
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## React Redux Refresh Token with Axios and JWT example
2 |
3 | 
4 |
5 | For more detail, please visit:
6 | > [React Redux Refresh Token with Axios and JWT](https://www.bezkoder.com/redux-refresh-token-axios/)
7 |
8 | > [React Redux JWT Authentication & Authorization example](https://bezkoder.com/react-redux-jwt-auth/)
9 |
10 | > [React Hooks + Redux: JWT Authentication & Authorization example](https://bezkoder.com/react-hooks-redux-login-registration-example/)
11 |
12 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
13 |
14 | ### Set port
15 | .env
16 | ```
17 | PORT=8081
18 | ```
19 |
20 | ## Note:
21 | Open `src/services/setupInterceptors.js` and modify `config.headers` for appropriate back-end (found in the tutorial).
22 |
23 | ```js
24 | instance.interceptors.request.use(
25 | (config) => {
26 | const token = TokenService.getLocalAccessToken();
27 | if (token) {
28 | // config.headers["Authorization"] = 'Bearer ' + token; // for Spring Boot back-end
29 | config.headers["x-access-token"] = token; // for Node.js Express back-end
30 | }
31 | return config;
32 | },
33 | (error) => {
34 | return Promise.reject(error);
35 | }
36 | );
37 | ```
38 |
39 | ## Project setup
40 |
41 | In the project directory, you can run:
42 |
43 | ```
44 | npm install
45 | # or
46 | yarn install
47 | ```
48 |
49 | or
50 |
51 | ### Compiles and hot-reloads for development
52 |
53 | ```
54 | npm start
55 | # or
56 | yarn start
57 | ```
58 |
59 | Open [http://localhost:8081](http://localhost:8081) to view it in the browser.
60 |
61 | The page will reload if you make edits.
62 |
63 | ## Related Posts
64 | > [In-depth Introduction to JWT-JSON Web Token](https://bezkoder.com/jwt-json-web-token/)
65 |
66 | > [React.js CRUD example to consume Web API](https://bezkoder.com/react-crud-web-api/)
67 |
68 | > [React Redux CRUD App example with Rest API](https://bezkoder.com/react-redux-crud-example/)
69 |
70 | > [React Pagination example](https://bezkoder.com/react-pagination-material-ui/)
71 |
72 | > [React File Upload with Axios and Progress Bar to Rest API](https://bezkoder.com/react-file-upload-axios/)
73 |
74 | Fullstack (JWT Authentication & Authorization example):
75 | > [React + Spring Boot](https://bezkoder.com/spring-boot-react-jwt-auth/)
76 |
77 | > [React + Node.js Express](https://bezkoder.com/react-express-authentication-jwt/)
78 |
79 | Fullstack CRUD with Node.js Express:
80 | > [React.js + Node.js Express + MySQL](https://bezkoder.com/react-node-express-mysql/)
81 |
82 | > [React.js + Node.js Express + PostgreSQL](https://bezkoder.com/react-node-express-postgresql/)
83 |
84 | > [React.js + Node.js Express + MongoDB](https://bezkoder.com/react-node-express-mongodb-mern-stack/)
85 |
86 | Fullstack CRUD with Spring Boot:
87 | > [React.js + Spring Boot + MySQL](https://bezkoder.com/react-spring-boot-crud/)
88 |
89 | > [React.js + Spring Boot + PostgreSQL](https://bezkoder.com/spring-boot-react-postgresql/)
90 |
91 | > [React.js + Spring Boot + MongoDB](https://bezkoder.com/react-spring-boot-mongodb/)
92 |
93 | Fullstack CRUD with Django:
94 | > [React.js + Django Rest Framework](https://bezkoder.com/django-react-axios-rest-framework/)
95 |
96 | Integration (run back-end & front-end on same server/port)
97 | > [How to integrate React.js with Spring Boot](https://bezkoder.com/integrate-reactjs-spring-boot/)
98 |
99 | > [Integrate React with Node.js Express on same Server/Port](https://bezkoder.com/integrate-react-express-same-server-port/)
100 |
101 | Serverless:
102 | > [React Firebase CRUD App with Realtime Database](https://bezkoder.com/react-firebase-crud/)
103 |
104 | > [React Firestore CRUD App example | Firebase Cloud Firestore](https://bezkoder.com/react-firestore-crud/)
105 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-redux-jwt-refresh-token",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.4",
7 | "@testing-library/react": "^11.1.0",
8 | "@testing-library/user-event": "^12.1.10",
9 | "axios": "^0.21.1",
10 | "bootstrap": "^4.6.0",
11 | "react": "^17.0.2",
12 | "react-dom": "^17.0.2",
13 | "react-redux": "^7.2.4",
14 | "react-router-dom": "^5.2.0",
15 | "react-scripts": "4.0.3",
16 | "react-validation": "^3.0.7",
17 | "redux": "^4.1.0",
18 | "redux-thunk": "^2.3.0",
19 | "validator": "^13.6.0",
20 | "web-vitals": "^1.0.1"
21 | },
22 | "scripts": {
23 | "start": "react-scripts start",
24 | "build": "react-scripts build",
25 | "test": "react-scripts test",
26 | "eject": "react-scripts eject"
27 | },
28 | "eslintConfig": {
29 | "extends": [
30 | "react-app",
31 | "react-app/jest"
32 | ]
33 | },
34 | "browserslist": {
35 | "production": [
36 | ">0.2%",
37 | "not dead",
38 | "not op_mini all"
39 | ],
40 | "development": [
41 | "last 1 chrome version",
42 | "last 1 firefox version",
43 | "last 1 safari version"
44 | ]
45 | },
46 | "devDependencies": {
47 | "redux-devtools-extension": "^2.13.9"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bezkoder/redux-refresh-token-axios/0a7da8223e340891f5955f626519194a5bdbed7e/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bezkoder/redux-refresh-token-axios/0a7da8223e340891f5955f626519194a5bdbed7e/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bezkoder/redux-refresh-token-axios/0a7da8223e340891f5955f626519194a5bdbed7e/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/react-redux-refresh-token-axios-jwt-flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bezkoder/redux-refresh-token-axios/0a7da8223e340891f5955f626519194a5bdbed7e/react-redux-refresh-token-axios-jwt-flow.png
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | label {
2 | display: block;
3 | margin-top: 10px;
4 | }
5 |
6 | .card-container.card {
7 | max-width: 350px !important;
8 | padding: 40px 40px;
9 | }
10 |
11 | .card {
12 | background-color: #f7f7f7;
13 | padding: 20px 25px 30px;
14 | margin: 0 auto 25px;
15 | margin-top: 50px;
16 | -moz-border-radius: 2px;
17 | -webkit-border-radius: 2px;
18 | border-radius: 2px;
19 | -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
20 | -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
21 | box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
22 | }
23 |
24 | .profile-img-card {
25 | width: 96px;
26 | height: 96px;
27 | margin: 0 auto 10px;
28 | display: block;
29 | -moz-border-radius: 50%;
30 | -webkit-border-radius: 50%;
31 | border-radius: 50%;
32 | }
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import { Router, Switch, Route, Link } from "react-router-dom";
4 |
5 | import "bootstrap/dist/css/bootstrap.min.css";
6 | import "./App.css";
7 |
8 | import Login from "./components/login.component";
9 | import Register from "./components/register.component";
10 | import Home from "./components/home.component";
11 | import Profile from "./components/profile.component";
12 | import BoardUser from "./components/board-user.component";
13 | import BoardModerator from "./components/board-moderator.component";
14 | import BoardAdmin from "./components/board-admin.component";
15 |
16 | import { logout } from "./actions/auth";
17 | import { clearMessage } from "./actions/message";
18 |
19 | import { history } from './helpers/history';
20 |
21 | import EventBus from "./common/EventBus";
22 |
23 | class App extends Component {
24 | constructor(props) {
25 | super(props);
26 | this.logOut = this.logOut.bind(this);
27 |
28 | this.state = {
29 | showModeratorBoard: false,
30 | showAdminBoard: false,
31 | currentUser: undefined,
32 | };
33 |
34 | history.listen((location) => {
35 | props.dispatch(clearMessage()); // clear message when changing location
36 | });
37 | }
38 |
39 | componentDidMount() {
40 | const user = this.props.user;
41 |
42 | if (user) {
43 | this.setState({
44 | currentUser: user,
45 | showModeratorBoard: user.roles.includes("ROLE_MODERATOR"),
46 | showAdminBoard: user.roles.includes("ROLE_ADMIN"),
47 | });
48 | }
49 |
50 | EventBus.on("logout", () => {
51 | this.logOut();
52 | });
53 | }
54 |
55 | componentWillUnmount() {
56 | EventBus.remove("logout");
57 | }
58 |
59 | logOut() {
60 | this.props.dispatch(logout());
61 | this.setState({
62 | showModeratorBoard: false,
63 | showAdminBoard: false,
64 | currentUser: undefined,
65 | });
66 | }
67 |
68 | render() {
69 | const { currentUser, showModeratorBoard, showAdminBoard } = this.state;
70 |
71 | return (
72 |
73 |
74 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 | );
154 | }
155 | }
156 |
157 | function mapStateToProps(state) {
158 | const { user } = state.auth;
159 | return {
160 | user,
161 | };
162 | }
163 |
164 | export default connect(mapStateToProps)(App);
165 |
--------------------------------------------------------------------------------
/src/actions/auth.js:
--------------------------------------------------------------------------------
1 | import {
2 | REGISTER_SUCCESS,
3 | REGISTER_FAIL,
4 | LOGIN_SUCCESS,
5 | LOGIN_FAIL,
6 | LOGOUT,
7 | SET_MESSAGE,
8 | REFRESH_TOKEN
9 | } from "./types";
10 |
11 | import AuthService from "../services/auth.service";
12 |
13 | export const register = (username, email, password) => (dispatch) => {
14 | return AuthService.register(username, email, password).then(
15 | (response) => {
16 | dispatch({
17 | type: REGISTER_SUCCESS,
18 | });
19 |
20 | dispatch({
21 | type: SET_MESSAGE,
22 | payload: response.data.message,
23 | });
24 |
25 | return Promise.resolve();
26 | },
27 | (error) => {
28 | const message =
29 | (error.response &&
30 | error.response.data &&
31 | error.response.data.message) ||
32 | error.message ||
33 | error.toString();
34 |
35 | dispatch({
36 | type: REGISTER_FAIL,
37 | });
38 |
39 | dispatch({
40 | type: SET_MESSAGE,
41 | payload: message,
42 | });
43 |
44 | return Promise.reject();
45 | }
46 | );
47 | };
48 |
49 | export const login = (username, password) => (dispatch) => {
50 | return AuthService.login(username, password).then(
51 | (data) => {
52 | dispatch({
53 | type: LOGIN_SUCCESS,
54 | payload: { user: data },
55 | });
56 |
57 | return Promise.resolve();
58 | },
59 | (error) => {
60 | const message =
61 | (error.response &&
62 | error.response.data &&
63 | error.response.data.message) ||
64 | error.message ||
65 | error.toString();
66 |
67 | dispatch({
68 | type: LOGIN_FAIL,
69 | });
70 |
71 | dispatch({
72 | type: SET_MESSAGE,
73 | payload: message,
74 | });
75 |
76 | return Promise.reject();
77 | }
78 | );
79 | };
80 |
81 | export const logout = () => (dispatch) => {
82 | AuthService.logout();
83 |
84 | dispatch({
85 | type: LOGOUT,
86 | });
87 | };
88 |
89 | export const refreshToken = (accessToken) => (dispatch) => {
90 | dispatch({
91 | type: REFRESH_TOKEN,
92 | payload: accessToken,
93 | })
94 | }
95 |
--------------------------------------------------------------------------------
/src/actions/message.js:
--------------------------------------------------------------------------------
1 | import { SET_MESSAGE, CLEAR_MESSAGE } from "./types";
2 |
3 | export const setMessage = (message) => ({
4 | type: SET_MESSAGE,
5 | payload: message,
6 | });
7 |
8 | export const clearMessage = () => ({
9 | type: CLEAR_MESSAGE,
10 | });
11 |
--------------------------------------------------------------------------------
/src/actions/types.js:
--------------------------------------------------------------------------------
1 | export const REGISTER_SUCCESS = "REGISTER_SUCCESS";
2 | export const REGISTER_FAIL = "REGISTER_FAIL";
3 | export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
4 | export const LOGIN_FAIL = "LOGIN_FAIL";
5 | export const LOGOUT = "LOGOUT";
6 | export const REFRESH_TOKEN = "REFRESH_TOKEN";
7 |
8 | export const SET_MESSAGE = "SET_MESSAGE";
9 | export const CLEAR_MESSAGE = "CLEAR_MESSAGE";
10 |
--------------------------------------------------------------------------------
/src/common/EventBus.js:
--------------------------------------------------------------------------------
1 | const eventBus = {
2 | on(event, callback) {
3 | document.addEventListener(event, (e) => callback(e.detail));
4 | },
5 | dispatch(event, data) {
6 | document.dispatchEvent(new CustomEvent(event, { detail: data }));
7 | },
8 | remove(event, callback) {
9 | document.removeEventListener(event, callback);
10 | },
11 | };
12 |
13 | export default eventBus;
14 |
--------------------------------------------------------------------------------
/src/components/board-admin.component.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 |
3 | import UserService from "../services/user.service";
4 | import EventBus from "../common/EventBus";
5 |
6 | export default class BoardAdmin extends Component {
7 | constructor(props) {
8 | super(props);
9 |
10 | this.state = {
11 | content: ""
12 | };
13 | }
14 |
15 | componentDidMount() {
16 | UserService.getAdminBoard().then(
17 | response => {
18 | this.setState({
19 | content: response.data
20 | });
21 | },
22 | error => {
23 | this.setState({
24 | content:
25 | (error.response &&
26 | error.response.data &&
27 | error.response.data.message) ||
28 | error.message ||
29 | error.toString()
30 | });
31 |
32 | if (error.response && error.response.status === 403) {
33 | EventBus.dispatch("logout");
34 | }
35 | }
36 | );
37 | }
38 |
39 | render() {
40 | return (
41 |
42 |
43 | {this.state.content}
44 |
45 |
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/components/board-moderator.component.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 |
3 | import UserService from "../services/user.service";
4 | import EventBus from "../common/EventBus";
5 |
6 | export default class BoardModerator extends Component {
7 | constructor(props) {
8 | super(props);
9 |
10 | this.state = {
11 | content: ""
12 | };
13 | }
14 |
15 | componentDidMount() {
16 | UserService.getModeratorBoard().then(
17 | response => {
18 | this.setState({
19 | content: response.data
20 | });
21 | },
22 | error => {
23 | this.setState({
24 | content:
25 | (error.response &&
26 | error.response.data &&
27 | error.response.data.message) ||
28 | error.message ||
29 | error.toString()
30 | });
31 |
32 | if (error.response && error.response.status === 403) {
33 | EventBus.dispatch("logout");
34 | }
35 | }
36 | );
37 | }
38 |
39 | render() {
40 | return (
41 |
42 |
43 | {this.state.content}
44 |
45 |
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/components/board-user.component.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 |
3 | import UserService from "../services/user.service";
4 | import EventBus from "../common/EventBus";
5 |
6 | export default class BoardUser extends Component {
7 | constructor(props) {
8 | super(props);
9 |
10 | this.state = {
11 | content: ""
12 | };
13 | }
14 |
15 | componentDidMount() {
16 | UserService.getUserBoard().then(
17 | response => {
18 | this.setState({
19 | content: response.data
20 | });
21 | },
22 | error => {
23 | this.setState({
24 | content:
25 | (error.response &&
26 | error.response.data &&
27 | error.response.data.message) ||
28 | error.message ||
29 | error.toString()
30 | });
31 |
32 | if (error.response && error.response.status === 403) {
33 | EventBus.dispatch("logout");
34 | }
35 | }
36 | );
37 | }
38 |
39 | render() {
40 | return (
41 |
42 |
43 | {this.state.content}
44 |
45 |
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/components/home.component.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 |
3 | import UserService from "../services/user.service";
4 |
5 | export default class Home extends Component {
6 | constructor(props) {
7 | super(props);
8 |
9 | this.state = {
10 | content: ""
11 | };
12 | }
13 |
14 | componentDidMount() {
15 | UserService.getPublicContent().then(
16 | response => {
17 | this.setState({
18 | content: response.data
19 | });
20 | },
21 | error => {
22 | this.setState({
23 | content:
24 | (error.response && error.response.data) ||
25 | error.message ||
26 | error.toString()
27 | });
28 | }
29 | );
30 | }
31 |
32 | render() {
33 | return (
34 |
35 |
36 | {this.state.content}
37 |
38 |
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/login.component.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Redirect } from 'react-router-dom';
3 |
4 | import Form from "react-validation/build/form";
5 | import Input from "react-validation/build/input";
6 | import CheckButton from "react-validation/build/button";
7 |
8 | import { connect } from "react-redux";
9 | import { login } from "../actions/auth";
10 |
11 | const required = (value) => {
12 | if (!value) {
13 | return (
14 |
15 | This field is required!
16 |
17 | );
18 | }
19 | };
20 |
21 | class Login extends Component {
22 | constructor(props) {
23 | super(props);
24 | this.handleLogin = this.handleLogin.bind(this);
25 | this.onChangeUsername = this.onChangeUsername.bind(this);
26 | this.onChangePassword = this.onChangePassword.bind(this);
27 |
28 | this.state = {
29 | username: "",
30 | password: "",
31 | loading: false,
32 | };
33 | }
34 |
35 | onChangeUsername(e) {
36 | this.setState({
37 | username: e.target.value,
38 | });
39 | }
40 |
41 | onChangePassword(e) {
42 | this.setState({
43 | password: e.target.value,
44 | });
45 | }
46 |
47 | handleLogin(e) {
48 | e.preventDefault();
49 |
50 | this.setState({
51 | loading: true,
52 | });
53 |
54 | this.form.validateAll();
55 |
56 | const { dispatch, history } = this.props;
57 |
58 | if (this.checkBtn.context._errors.length === 0) {
59 | dispatch(login(this.state.username, this.state.password))
60 | .then(() => {
61 | history.push("/profile");
62 | window.location.reload();
63 | })
64 | .catch(() => {
65 | this.setState({
66 | loading: false
67 | });
68 | });
69 | } else {
70 | this.setState({
71 | loading: false,
72 | });
73 | }
74 | }
75 |
76 | render() {
77 | const { isLoggedIn, message } = this.props;
78 |
79 | if (isLoggedIn) {
80 | return ;
81 | }
82 |
83 | return (
84 |
85 |
86 |

91 |
92 |
148 |
149 |
150 | );
151 | }
152 | }
153 |
154 | function mapStateToProps(state) {
155 | const { isLoggedIn } = state.auth;
156 | const { message } = state.message;
157 | return {
158 | isLoggedIn,
159 | message
160 | };
161 | }
162 |
163 | export default connect(mapStateToProps)(Login);
164 |
--------------------------------------------------------------------------------
/src/components/profile.component.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Redirect } from 'react-router-dom';
3 | import { connect } from "react-redux";
4 |
5 | class Profile extends Component {
6 |
7 | render() {
8 | const { user: currentUser } = this.props;
9 |
10 | if (!currentUser) {
11 | return ;
12 | }
13 |
14 | return (
15 |
16 |
17 |
18 | {currentUser.username} Profile
19 |
20 |
21 |
22 | Token: {currentUser.accessToken.substring(0, 20)} ...{" "}
23 | {currentUser.accessToken.substr(currentUser.accessToken.length - 20)}
24 |
25 |
26 | Id: {currentUser.id}
27 |
28 |
29 | Email: {currentUser.email}
30 |
31 |
Authorities:
32 |
33 | {currentUser.roles &&
34 | currentUser.roles.map((role, index) => - {role}
)}
35 |
36 |
37 | );
38 | }
39 | }
40 |
41 | function mapStateToProps(state) {
42 | const { user } = state.auth;
43 | return {
44 | user,
45 | };
46 | }
47 |
48 | export default connect(mapStateToProps)(Profile);
49 |
--------------------------------------------------------------------------------
/src/components/register.component.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import Form from "react-validation/build/form";
3 | import Input from "react-validation/build/input";
4 | import CheckButton from "react-validation/build/button";
5 | import { isEmail } from "validator";
6 |
7 | import { connect } from "react-redux";
8 | import { register } from "../actions/auth";
9 |
10 | const required = (value) => {
11 | if (!value) {
12 | return (
13 |
14 | This field is required!
15 |
16 | );
17 | }
18 | };
19 |
20 | const email = (value) => {
21 | if (!isEmail(value)) {
22 | return (
23 |
24 | This is not a valid email.
25 |
26 | );
27 | }
28 | };
29 |
30 | const vusername = (value) => {
31 | if (value.length < 3 || value.length > 20) {
32 | return (
33 |
34 | The username must be between 3 and 20 characters.
35 |
36 | );
37 | }
38 | };
39 |
40 | const vpassword = (value) => {
41 | if (value.length < 6 || value.length > 40) {
42 | return (
43 |
44 | The password must be between 6 and 40 characters.
45 |
46 | );
47 | }
48 | };
49 |
50 | class Register extends Component {
51 | constructor(props) {
52 | super(props);
53 | this.handleRegister = this.handleRegister.bind(this);
54 | this.onChangeUsername = this.onChangeUsername.bind(this);
55 | this.onChangeEmail = this.onChangeEmail.bind(this);
56 | this.onChangePassword = this.onChangePassword.bind(this);
57 |
58 | this.state = {
59 | username: "",
60 | email: "",
61 | password: "",
62 | successful: false,
63 | };
64 | }
65 |
66 | onChangeUsername(e) {
67 | this.setState({
68 | username: e.target.value,
69 | });
70 | }
71 |
72 | onChangeEmail(e) {
73 | this.setState({
74 | email: e.target.value,
75 | });
76 | }
77 |
78 | onChangePassword(e) {
79 | this.setState({
80 | password: e.target.value,
81 | });
82 | }
83 |
84 | handleRegister(e) {
85 | e.preventDefault();
86 |
87 | this.setState({
88 | successful: false,
89 | });
90 |
91 | this.form.validateAll();
92 |
93 | if (this.checkBtn.context._errors.length === 0) {
94 | this.props
95 | .dispatch(
96 | register(this.state.username, this.state.email, this.state.password)
97 | )
98 | .then(() => {
99 | this.setState({
100 | successful: true,
101 | });
102 | })
103 | .catch(() => {
104 | this.setState({
105 | successful: false,
106 | });
107 | });
108 | }
109 | }
110 |
111 | render() {
112 | const { message } = this.props;
113 |
114 | return (
115 |
116 |
117 |

122 |
123 |
187 |
188 |
189 | );
190 | }
191 | }
192 |
193 | function mapStateToProps(state) {
194 | const { message } = state.message;
195 | return {
196 | message,
197 | };
198 | }
199 |
200 | export default connect(mapStateToProps)(Register);
201 |
--------------------------------------------------------------------------------
/src/helpers/history.js:
--------------------------------------------------------------------------------
1 | import { createBrowserHistory } from "history";
2 |
3 | export const history = createBrowserHistory();
4 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { Provider } from "react-redux";
4 | import store from "./store";
5 | import "./index.css";
6 | import App from "./App";
7 | import reportWebVitals from "./reportWebVitals";
8 |
9 | import setupInterceptors from "./services/setupInterceptors";
10 |
11 | ReactDOM.render(
12 |
13 |
14 | ,
15 | document.getElementById("root")
16 | );
17 |
18 | setupInterceptors(store);
19 |
20 | // If you want to start measuring performance in your app, pass a function
21 | // to log results (for example: reportWebVitals(console.log))
22 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
23 | reportWebVitals();
24 |
--------------------------------------------------------------------------------
/src/reducers/auth.js:
--------------------------------------------------------------------------------
1 | import {
2 | REGISTER_SUCCESS,
3 | REGISTER_FAIL,
4 | LOGIN_SUCCESS,
5 | LOGIN_FAIL,
6 | LOGOUT,
7 | REFRESH_TOKEN
8 | } from "../actions/types";
9 |
10 | const user = JSON.parse(localStorage.getItem("user"));
11 |
12 | const initialState = user
13 | ? { isLoggedIn: true, user }
14 | : { isLoggedIn: false, user: null };
15 |
16 | export default function (state = initialState, action) {
17 | const { type, payload } = action;
18 |
19 | switch (type) {
20 | case REGISTER_SUCCESS:
21 | return {
22 | ...state,
23 | isLoggedIn: false,
24 | };
25 | case REGISTER_FAIL:
26 | return {
27 | ...state,
28 | isLoggedIn: false,
29 | };
30 | case LOGIN_SUCCESS:
31 | return {
32 | ...state,
33 | isLoggedIn: true,
34 | user: payload.user,
35 | };
36 | case LOGIN_FAIL:
37 | return {
38 | ...state,
39 | isLoggedIn: false,
40 | user: null,
41 | };
42 | case LOGOUT:
43 | return {
44 | ...state,
45 | isLoggedIn: false,
46 | user: null,
47 | };
48 | case REFRESH_TOKEN:
49 | return {
50 | ...state,
51 | user: { ...user, accessToken: payload },
52 | };
53 | default:
54 | return state;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import auth from "./auth";
3 | import message from "./message";
4 |
5 | export default combineReducers({
6 | auth,
7 | message,
8 | });
9 |
--------------------------------------------------------------------------------
/src/reducers/message.js:
--------------------------------------------------------------------------------
1 | import { SET_MESSAGE, CLEAR_MESSAGE } from "../actions/types";
2 |
3 | const initialState = {};
4 |
5 | export default function (state = initialState, action) {
6 | const { type, payload } = action;
7 |
8 | switch (type) {
9 | case SET_MESSAGE:
10 | return { message: payload };
11 |
12 | case CLEAR_MESSAGE:
13 | return { message: "" };
14 |
15 | default:
16 | return state;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/services/api.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const instance = axios.create({
4 | baseURL: "http://localhost:8080/api",
5 | headers: {
6 | "Content-Type": "application/json",
7 | },
8 | });
9 |
10 | export default instance;
11 |
--------------------------------------------------------------------------------
/src/services/auth.service.js:
--------------------------------------------------------------------------------
1 | import api from "./api";
2 | import TokenService from "./token.service";
3 |
4 | class AuthService {
5 | login(username, password) {
6 | return api
7 | .post("/auth/signin", {
8 | username,
9 | password
10 | })
11 | .then(response => {
12 | if (response.data.accessToken) {
13 | TokenService.setUser(response.data);
14 | }
15 |
16 | return response.data;
17 | });
18 | }
19 |
20 | logout() {
21 | TokenService.removeUser();
22 | }
23 |
24 | register(username, email, password) {
25 | return api.post("/auth/signup", {
26 | username,
27 | email,
28 | password
29 | });
30 | }
31 | }
32 |
33 | export default new AuthService();
34 |
--------------------------------------------------------------------------------
/src/services/setupInterceptors.js:
--------------------------------------------------------------------------------
1 | import axiosInstance from "./api";
2 | import TokenService from "./token.service";
3 | import { refreshToken } from "../actions/auth";
4 |
5 | const setup = (store) => {
6 | axiosInstance.interceptors.request.use(
7 | (config) => {
8 | const token = TokenService.getLocalAccessToken();
9 | if (token) {
10 | // config.headers["Authorization"] = 'Bearer ' + token; // for Spring Boot back-end
11 | config.headers["x-access-token"] = token; // for Node.js Express back-end
12 | }
13 | return config;
14 | },
15 | (error) => {
16 | return Promise.reject(error);
17 | }
18 | );
19 |
20 | const { dispatch } = store;
21 | axiosInstance.interceptors.response.use(
22 | (res) => {
23 | return res;
24 | },
25 | async (err) => {
26 | const originalConfig = err.config;
27 |
28 | if (originalConfig.url !== "/auth/signin" && err.response) {
29 | // Access Token was expired
30 | if (err.response.status === 401 && !originalConfig._retry) {
31 | originalConfig._retry = true;
32 |
33 | try {
34 | const rs = await axiosInstance.post("/auth/refreshtoken", {
35 | refreshToken: TokenService.getLocalRefreshToken(),
36 | });
37 |
38 | const { accessToken } = rs.data;
39 |
40 | dispatch(refreshToken(accessToken));
41 | TokenService.updateLocalAccessToken(accessToken);
42 |
43 | return axiosInstance(originalConfig);
44 | } catch (_error) {
45 | return Promise.reject(_error);
46 | }
47 | }
48 | }
49 |
50 | return Promise.reject(err);
51 | }
52 | );
53 | };
54 |
55 | export default setup;
56 |
--------------------------------------------------------------------------------
/src/services/token.service.js:
--------------------------------------------------------------------------------
1 | class TokenService {
2 | getLocalRefreshToken() {
3 | const user = JSON.parse(localStorage.getItem("user"));
4 | return user?.refreshToken;
5 | }
6 |
7 | getLocalAccessToken() {
8 | const user = JSON.parse(localStorage.getItem("user"));
9 | return user?.accessToken;
10 | }
11 |
12 | updateLocalAccessToken(token) {
13 | let user = JSON.parse(localStorage.getItem("user"));
14 | user.accessToken = token;
15 | localStorage.setItem("user", JSON.stringify(user));
16 | }
17 |
18 | getUser() {
19 | return JSON.parse(localStorage.getItem("user"));
20 | }
21 |
22 | setUser(user) {
23 | console.log(JSON.stringify(user));
24 | localStorage.setItem("user", JSON.stringify(user));
25 | }
26 |
27 | removeUser() {
28 | localStorage.removeItem("user");
29 | }
30 | }
31 |
32 | export default new TokenService();
33 |
--------------------------------------------------------------------------------
/src/services/user.service.js:
--------------------------------------------------------------------------------
1 | import api from './api';
2 |
3 | class UserService {
4 | getPublicContent() {
5 | return api.get('/test/all');
6 | }
7 |
8 | getUserBoard() {
9 | return api.get('/test/user');
10 | }
11 |
12 | getModeratorBoard() {
13 | return api.get('/test/mod');
14 | }
15 |
16 | getAdminBoard() {
17 | return api.get('/test/admin');
18 | }
19 | }
20 |
21 | export default new UserService();
22 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from "redux";
2 | import { composeWithDevTools } from "redux-devtools-extension";
3 | import thunk from "redux-thunk";
4 | import rootReducer from "./reducers";
5 |
6 | const middleware = [thunk];
7 |
8 | const store = createStore(
9 | rootReducer,
10 | composeWithDevTools(applyMiddleware(...middleware))
11 | );
12 |
13 | export default store;
14 |
--------------------------------------------------------------------------------