Login Authentication with JWT Access, Refresh Tokens, Cookies and Axios - rs-hash/Senior GitHub Wiki

INTERCEPTORS

  • 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.

Steps to Add Axios Interceptors in a React Application

  • Install Axios
  • Create an Axios Instance
  • Add Interceptors to the Axios Instance
  • Use the Axios Instance in Your Components
  1. Install Axios
npm install axios
  1. 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;
  1. 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;
  1. 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;

Benefits of Using Interceptors

  • 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.

πŸ” 1. Login Authentication Flow: High-Level Architecture

βœ… Typical Steps:

  1. User submits credentials (username/email & password).

  2. Front end sends a POST request to the backend login endpoint (/api/auth/login).

  3. Backend validates credentials, generates an access token (JWT or session cookie), and returns it.

  4. Front end stores token/session, updates the UI state to reflect the logged-in user.

  5. On every subsequent request, the token is sent in headers or automatically via cookies.

  6. Logout clears token/cookie and resets the front-end state.


🧠 2. Authentication Strategies

A. Token-Based Authentication (JWT)

  • Front end stores a JWT and sends it via Authorization: Bearer <token> header.

  • Used in SPAs (React/Vue/Angular) and mobile apps.

B. Cookie-Based Authentication (Session Cookie)

  • Backend sets an HttpOnly, Secure cookie (server-managed session).

  • Automatically sent with every request from the browser.


πŸ”§ 3. React Front-End Implementation (JWT-based)

πŸ” A. Login Component

js
CopyEdit
async 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 localStorage is common but insecure β€” they can be stolen via XSS. Prefer HttpOnly cookies if possible."


🧠 Best Practice:

js
CopyEdit
// Prefer server-set HttpOnly cookies Set-Cookie: access_token=abc123; HttpOnly; Secure; SameSite=Strict

πŸ”„ B. Maintaining Auth State (React Context / Redux / Zustand)

js
CopyEdit
const 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> ); }

🧾 4. Making Authenticated API Calls

With JWT:

js
CopyEdit
const 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."


🧼 5. Logout Handling

js
CopyEdit
function handleLogout() { localStorage.removeItem('token'); setAuth(false); // reset UI state navigate('/login'); }

If using cookies:

js
CopyEdit
fetch('/api/logout', { method: 'POST', credentials: 'include' });

πŸ›‘οΈ 6. Security Measures (as a Principal Engineer)

βœ… Recommendations:

Concern | Front-End Practice -- | -- XSS | Never store tokens in localStorage; escape all inputs CSRF | Use SameSite=Strict cookies; send CSRF tokens in headers Session Hijack | Use HttpOnly, Secure, SameSite flags on cookies Token Expiry | Implement token refresh mechanism (silent or via refresh endpoint) Role-based Access | Use access control at both UI (hide buttons) and server (enforce) Avoid storing sensitive data in front-end | Don’t store user roles, passwords, or email addresses insecurely

πŸ”„ 7. Advanced Topics

πŸ”„ Token Refresh Flow

  • Use short-lived access token + long-lived refresh token

  • On 401 error:

    1. Send refresh token to /refresh

    2. Get new access token

    3. Retry failed request

πŸ” OAuth & Social Login

  • Use window.open() or redirects to Google/Facebook login

  • Handle the OAuth callback and extract tokens

  • Avoid leaking tokens in URL (hash fragments instead of query params)

🧾 Multi-Factor Authentication (MFA)

  • After password login, prompt for OTP code

  • Integrate with TOTP apps or SMS/email verification

⚠️ **GitHub.com Fallback** ⚠️