Login Authentication with JWT Access, Refresh Tokens, Cookies and Axios - rs-hash/Senior GitHub Wiki
- In a React application, interceptors are commonly used with axios for handling HTTP requests and responses globally. Axios interceptors allow you to run your custom code or modify the request/response before it is handled by then or catch.
- Install Axios
- Create an Axios Instance
- Add Interceptors to the Axios Instance
- Use the Axios Instance in Your Components
- Install Axios
npm install axios- Create an Axios Instance Create an Axios instance where you can add your interceptors.
// axiosInstance.js
import axios from 'axios';
const axiosInstance = axios.create({
baseURL: 'https://jsonplaceholder.typicode.com',
// You can set headers or other default configurations here
});
export default axiosInstance;- Add Interceptors to the Axios Instance You can add request and response interceptors to the Axios instance to handle specific logic for each request or response.
// axiosInstance.js
import axios from 'axios';
const axiosInstance = axios.create({
baseURL: 'https://jsonplaceholder.typicode.com',
});
// Request interceptor
axiosInstance.interceptors.request.use(
(config) => {
// Add logic before the request is sent, such as adding authentication headers
console.log('Request Interceptor:', config);
// Example: Adding an Authorization header
config.headers['Authorization'] = 'Bearer your_token';
return config;
},
(error) => {
// Do something with request error
return Promise.reject(error);
}
);
// Response interceptor
axiosInstance.interceptors.response.use(
(response) => {
// Any status code that lie within the range of 2xx cause this function to trigger
console.log('Response Interceptor:', response);
return response;
},
(error) => {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Handle response error
console.error('Response Interceptor Error:', error);
return Promise.reject(error);
}
);
export default axiosInstance;- Use the Axios Instance in Your Components Import the Axios instance in your components and use it to make API calls.
// App.js
import React, { useEffect, useState } from 'react';
import axiosInstance from './axiosInstance';
const App = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
const fetchPosts = async () => {
try {
const response = await axiosInstance.get('/posts');
setPosts(response.data);
} catch (error) {
console.error('Error fetching posts:', error);
}
};
fetchPosts();
}, []);
return (
<div>
<h1>Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
};
export default App;-
Centralized Error Handling: Handle all errors in one place instead of catching them in every request. -
Authentication: Automatically add authentication headers or tokens to every request. -
Logging: Log every request and response for debugging purposes. -
Data Transformation: Transform request data before sending it or transform response data before passing it to the application.
-
User submits credentials (username/email & password).
-
Front end sends a POST request to the backend login endpoint (
/api/auth/login). -
Backend validates credentials, generates an access token (JWT or session cookie), and returns it.
-
Front end stores token/session, updates the UI state to reflect the logged-in user.
-
On every subsequent request, the token is sent in headers or automatically via cookies.
-
Logout clears token/cookie and resets the front-end state.
-
Front end stores a JWT and sends it via
Authorization: Bearer <token>header. -
Used in SPAs (React/Vue/Angular) and mobile apps.
-
Backend sets an HttpOnly, Secure cookie (server-managed session).
-
Automatically sent with every request from the browser.
jsCopyEditasync function handleLogin(e) { e.preventDefault(); const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); const data = await response.json(); if (response.ok) { localStorage.setItem('token', data.token); setAuth(true); // update global auth state } else { setError(data.message); } }
"Storing tokens in
localStorageis common but insecure β they can be stolen via XSS. PreferHttpOnly cookiesif possible."
jsCopyEdit// Prefer server-set HttpOnly cookies Set-Cookie: access_token=abc123; HttpOnly; Secure; SameSite=Strict
jsCopyEditconst AuthContext = createContext(); function AuthProvider({ children }) { const [isAuthenticated, setAuth] = useState(false); useEffect(() => { const token = localStorage.getItem('token'); setAuth(!!token); }, []); return ( <AuthContext.Provider value={{ isAuthenticated, setAuth }}> {children} </AuthContext.Provider> ); }
jsCopyEditconst token = localStorage.getItem('token'); fetch('/api/user', { headers: { Authorization:Bearer <span class="hljs-subst">${token}</span></span></span><span>} });
"Always check token expiration before sending requests. Consider automatic token refresh."
jsCopyEditfunction handleLogout() { localStorage.removeItem('token'); setAuth(false); // reset UI state navigate('/login'); }
If using cookies:
jsCopyEditfetch('/api/logout', { method: 'POST', credentials: 'include' });
-
Use short-lived access token + long-lived refresh token
-
On 401 error:
-
Send refresh token to
/refresh -
Get new access token
-
Retry failed request
-
-
Use
window.open()or redirects to Google/Facebook login -
Handle the OAuth callback and extract tokens
-
Avoid leaking tokens in URL (
hashfragments instead of query params)
-
After password login, prompt for OTP code
-
Integrate with TOTP apps or SMS/email verification