How to authenticate using access and refresh tokens using React Js

Sumit kumar Singh
5 min readJan 20, 2024

Building a React Js login system with access and refresh tokens involves several steps, from setting up the server to handling token expiration and integrating it into a React Js application. In this comprehensive guide, I’ll walk you through the process with code examples at each step.

Server Setup for Authentication:

We’ll use Node.js with Express for the server setup. Install necessary packages:

npm init -y
npm install express body-parser jsonwebtoken bcrypt

Create an index.js file for your server:

// index.js

const express = require('express');
const bodyParser = require('body-parser');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');

const app = express();
const PORT = 3001;

app.use(bodyParser.json());

// Dummy user database
const users = [
{
id: 1,
username: 'user1',
password: '$2b$10$3MtkH6SkIBWtq6Thbu0FJOV5E/fb7ldiKYiVz0eHR9gHwUVWyxVvG', // bcrypt hash for 'password'
},
];

// Middleware for authentication
const authenticateToken = (req, res, next) => {
const token = req.headers.authorization;
if (!token) return res.sendStatus(401);

jwt.verify(token, 'secret_key', (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
};

// Login endpoint
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find((u) => u.username === username);

if (!user || !bcrypt.compareSync(password, user.password)) {
return res.status(401).json({ error: 'Invalid credentials' });
}

const accessToken = generateAccessToken({ username: user.username });
const refreshToken = jwt.sign({ username: user.username }, 'refresh_secret_key');
res.json({ accessToken, refreshToken });
});

// Token generation functions
const generateAccessToken = (user) => {
return jwt.sign(user, 'secret_key', { expiresIn: '15m' });
};

app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});

This sets up a basic Express server with a /login endpoint for user authentication.

React Js Integration:

Create a new React JS app and install dependencies:

npx create-react-app react-auth-example
cd react-auth-example
npm install axios react-router-dom

Now, implement the React JS components and context for authentication.

src/context/AuthContext.js:

// AuthContext.js

import React, { createContext, useContext, useState, useEffect } from 'react';
import axios from 'axios';

const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchUser = async () => {
try {
const response = await axios.get('/api/user', { withCredentials: true });
setUser(response.data);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};

fetchUser();
}, []);

const login = async (username, password) => {
try {
const response = await axios.post('/api/login', { username, password });
setUser(response.data);
} catch (error) {
console.error(error);
}
};

const logout = async () => {
try {
await axios.post('/api/logout', {}, { withCredentials: true });
setUser(null);
} catch (error) {
console.error(error);
}
};

const contextValue = {
user,
login,
logout,
};

return <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>;
};

export const useAuth = () => {
return useContext(AuthContext);
};

src/App.js:

// App.js

import React from 'react';
import { BrowserRouter as Router, Route, Redirect, Switch } from 'react-router-dom';
import { AuthProvider } from './context/AuthContext';
import PrivateRoute from './components/PrivateRoute';
import Home from './components/Home';
import Login from './components/Login';

function App() {
return (
<Router>
<AuthProvider>
<Switch>
<Route path="/login" component={Login} />
<PrivateRoute path="/home" component={Home} />
<Redirect from="/" to="/home" />
</Switch>
</AuthProvider>
</Router>
);
}

export default App;

src/components/PrivateRoute.js:

// PrivateRoute.js

import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';

const PrivateRoute = ({ component: Component, ...rest }) => {
const { user } = useAuth();

return (
<Route
{...rest}
render={(props) => (user ? <Component {...props} /> : <Redirect to="/login" />)}
/>
);
};

export default PrivateRoute;

src/components/Home.js:

// Home.js

import React from 'react';
import { useAuth } from '../context/AuthContext';

const Home = () => {
const { user, logout } = useAuth();

return (
<div>
<h2>Welcome, {user && user.username}!</h2>
<button onClick={logout}>Logout</button>
</div>
);
};

export default Home;

src/components/Login.js:

// Login.js

import React, { useState } from 'react';
import { useHistory } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';

const Login = () => {
const history = useHistory();
const { login } = useAuth();
const [formData, setFormData] = useState({ username: '', password: '' });

const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};

const handleSubmit = async (e) => {
e.preventDefault();
const { username, password } = formData;

if (username && password) {
await login(username, password);
history.push('/home');
}
};

return (
<div>
<h2>Login</h2>
<form onSubmit={handleSubmit}>
<label>
Username:
<input type="text" name="username" value={formData.username} onChange={handleChange} />
</label>
<br />
<label>
Password:
<input type="password" name="password" value={formData.password} onChange={handleChange} />
</label>
<br />
<button type="submit">Login</button>
</form>
</div>
);
};

export default Login;

Token Refresh Mechanism:

To handle token expiration, you need to set up a mechanism to refresh the access token using the refresh token.

index.js (server):

// index.js

// ... (Previous code)

// Refresh endpoint
app.post('/refresh', (req, res) => {
const refreshToken = req.body.refreshToken;
if (!refreshToken) return res.sendStatus(401);

jwt.verify(refreshToken, 'refresh_secret_key', (err, user) => {
if (err) return res.sendStatus(403);
const accessToken = generateAccessToken({ username: user.username });
res.json({ accessToken });
});
});

app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});

AuthContext.js (React JS):

Add a function to refresh the access token when needed:

// AuthContext.js

// ... (Previous code)

export const AuthProvider = ({ children }) => {
// ... (Previous code)

const refreshToken = async () => {
try {
const response = await axios.post('/api/refresh', { refreshToken: user.refreshToken });
setUser((prevUser) => ({ ...prevUser, accessToken: response.data.accessToken }));
} catch (error) {
console.error(error);
setUser(null);
}
};

useEffect(() => {
const interval = setInterval(() => {
refreshToken();
}, 5 * 60 * 1000); // Refresh every 5 minutes

return () => clearInterval(interval);
}, [user]);

// ... (Remaining code)
};

Protected Routes and Logout:

Modify the PrivateRoute.js component to handle token expiration and create a logout endpoint on the server.

PrivateRoute.js:

// PrivateRoute.js

// ... (Previous code)

const PrivateRoute = ({ component: Component, ...rest }) => {
const { user, logout, refreshToken } = useAuth();

useEffect(() => {
const refresh = async () => {
try {
await refreshToken();
} catch (error) {
console.error(error);
logout();
}
};

if (user && user.accessToken) {
const tokenExp = new Date(jwt.decode(user.accessToken).exp * 1000);
const now = new Date();

// Refresh token if it's about to expire
if (tokenExp - now < 60 * 1000) {
refresh();
}
}
}, [user, refreshToken, logout]);

return (
<Route
{...rest}
render={(props) => (user && user.accessToken ? <Component {...props} /> : <Redirect to="/login" />)}
/>
);
};

// ... (Remaining code)

index.js (Server):

Add a /logout endpoint:

// index.js

// ... (Previous code)

// Logout endpoint
app.post('/logout', (req, res) => {
// Clear the refresh token on the server
// You may want to implement additional security measures here
// such as blacklisting the token
res.sendStatus(204);
});

// ... (Remaining code)

AuthContext.js (React JS):

Add a logout function and update the context accordingly:

// AuthContext.js

// ... (Previous code)

export const AuthProvider = ({ children }) => {
// ... (Previous code)

const logout = async () => {
try {
await axios.post('/api/logout', {}, { withCredentials: true });
setUser(null);
} catch (error) {
console.error(error);
}
};

const contextValue = {
user,
login,
logout,
refreshToken,
};

return <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>;
};

Your React app should now handle token expiration, refresh tokens, and protected routes with proper logout functionality. Customize and expand upon this example to fit your specific use case and security requirements.

--

--

Sumit kumar Singh

YouTube: https://www.youtube.com/@tech..Design/ 📚 HTML,Angular, React,and JavaScript 🧑‍💻 Tips & tricks on Web Developing 👉 FULL STACK DEVELOPER