search
Tutorials star Featured

Fix: 401 Unauthorized Error - Complete Guide to Authentication Issues

Complete guide to fix 401 Unauthorized errors. Learn how to resolve authentication issues with practical solutions, token management, and best practices for secure API communication.

person By Gautam Sharma
calendar_today January 8, 2026
schedule 25 min read
HTTP Error Authentication API Security Frontend Backend Token

The ‘401 Unauthorized’ error is a common HTTP status code that indicates the client request lacks valid authentication credentials for the requested resource. This error occurs when a user tries to access a protected resource without proper authentication or with invalid credentials. Unlike a 403 Forbidden error, a 401 error suggests that the request can be fulfilled if valid authentication credentials are provided.

This comprehensive guide explains what causes this error, why it happens, and provides multiple solutions to fix it in your applications with clean code examples and directory structure.


What is the 401 Unauthorized Error?

The “401 Unauthorized” error occurs when:

  • A request is made to a protected resource without authentication
  • Invalid or expired authentication credentials are provided
  • The authentication token has expired or been revoked
  • Incorrect API keys or credentials are used
  • The authentication header is missing or malformed
  • Session cookies are invalid or expired
  • The user account has been deactivated or suspended
  • Authentication scheme is not supported

Common Error Manifestations:

  • 401 Unauthorized HTTP status code
  • WWW-Authenticate header in response
  • Authorization Required error message
  • Invalid token or Token expired messages
  • Invalid credentials error responses
  • Session expired notifications
  • Access denied error messages
  • Authentication failed responses

Understanding the Problem

This error typically occurs due to:

  • Missing or invalid authentication headers
  • Expired authentication tokens
  • Incorrect API keys or credentials
  • Session timeout or invalidation
  • Authentication middleware configuration issues
  • Token storage and retrieval problems
  • Clock synchronization issues between client and server
  • Improper authentication flow implementation

Why This Error Happens:

The HTTP 401 status code is returned by servers when they require authentication but the client has not provided valid credentials. This is part of the HTTP authentication framework that ensures only authorized users can access protected resources.


Solution 1: Proper Authentication Header Setup

The first step is to ensure proper authentication headers are sent with requests.

❌ Without Proper Headers:

// ❌ Request without authentication header
fetch('/api/protected-resource')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error)); // ❌ Will likely return 401

✅ With Proper Headers:

Setting Authentication Headers:

// ✅ Proper authentication header setup
async function makeAuthenticatedRequest(url, options = {}) {
  const token = localStorage.getItem('authToken');
  
  if (!token) {
    throw new Error('No authentication token found');
  }

  const config = {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
      ...options.headers
    },
    ...options
  };

  const response = await fetch(url, config);
  
  if (response.status === 401) {
    // ✅ Handle 401 error appropriately
    handleUnauthorizedError();
    throw new Error('Unauthorized access');
  }
  
  return response;
}

// ✅ Usage
async function fetchUserData() {
  try {
    const response = await makeAuthenticatedRequest('/api/user/profile');
    const userData = await response.json();
    return userData;
  } catch (error) {
    console.error('Failed to fetch user data:', error);
    throw error;
  }
}

Advanced Header Management:

// ✅ Advanced authentication header management
class AuthenticatedApiClient {
  constructor(baseURL) {
    this.baseURL = baseURL;
    this.defaultHeaders = {
      'Content-Type': 'application/json'
    };
  }

  // ✅ Get authentication token
  getAuthToken() {
    return localStorage.getItem('authToken');
  }

  // ✅ Set authentication header
  setAuthHeader(headers = {}) {
    const token = this.getAuthToken();
    if (token) {
      return {
        ...headers,
        'Authorization': `Bearer ${token}`
      };
    }
    return headers;
  }

  // ✅ Make authenticated request
  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;
    const headers = this.setAuthHeader({
      ...this.defaultHeaders,
      ...options.headers
    });

    const config = {
      headers,
      ...options
    };

    let response = await fetch(url, config);

    // ✅ Handle 401 errors
    if (response.status === 401) {
      // ✅ Try to refresh token
      const refreshed = await this.refreshToken();
      if (refreshed) {
        // ✅ Retry request with new token
        const newHeaders = this.setAuthHeader(this.defaultHeaders);
        const retryConfig = {
          ...config,
          headers: newHeaders
        };
        response = await fetch(url, retryConfig);
      }

      if (response.status === 401) {
        // ✅ Still unauthorized, redirect to login
        this.handleAuthFailure();
      }
    }

    return response;
  }

  // ✅ Refresh authentication token
  async refreshToken() {
    try {
      const refreshToken = localStorage.getItem('refreshToken');
      if (!refreshToken) {
        return false;
      }

      const response = await fetch('/auth/refresh', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ refreshToken })
      });

      if (response.ok) {
        const { accessToken } = await response.json();
        localStorage.setItem('authToken', accessToken);
        return true;
      }
      return false;
    } catch (error) {
      console.error('Token refresh failed:', error);
      return false;
    }
  }

  // ✅ Handle authentication failure
  handleAuthFailure() {
    localStorage.removeItem('authToken');
    localStorage.removeItem('refreshToken');
    window.location.href = '/login';
  }
}

// ✅ Initialize authenticated API client
const apiClient = new AuthenticatedApiClient('/api');

Solution 2: Token Management and Refresh

❌ Without Token Management:

// ❌ Basic token usage without refresh
const token = localStorage.getItem('authToken');

fetch('/api/protected', {
  headers: {
    'Authorization': `Bearer ${token}` // ❌ No validation or refresh
  }
});

✅ With Proper Token Management:

Token Validation and Refresh:

// ✅ Comprehensive token management system
class TokenManager {
  constructor() {
    this.tokenKey = 'authToken';
    this.refreshTokenKey = 'refreshToken';
    this.tokenExpiryBuffer = 5 * 60 * 1000; // 5 minutes buffer
  }

  // ✅ Get token from storage
  getToken() {
    return localStorage.getItem(this.tokenKey);
  }

  // ✅ Set token in storage
  setToken(token, refreshToken = null) {
    localStorage.setItem(this.tokenKey, token);
    if (refreshToken) {
      localStorage.setItem(this.refreshTokenKey, refreshToken);
    }
  }

  // ✅ Remove tokens
  removeTokens() {
    localStorage.removeItem(this.tokenKey);
    localStorage.removeItem(this.refreshTokenKey);
  }

  // ✅ Decode JWT token to get expiry time
  getTokenExpiry(token) {
    try {
      const payload = JSON.parse(atob(token.split('.')[1]));
      return payload.exp ? payload.exp * 1000 : null; // Convert to milliseconds
    } catch (error) {
      console.error('Failed to decode token:', error);
      return null;
    }
  }

  // ✅ Check if token is expired
  isTokenExpired(token) {
    if (!token) return true;

    const expiryTime = this.getTokenExpiry(token);
    if (!expiryTime) return true;

    const currentTime = Date.now();
    return currentTime >= (expiryTime - this.tokenExpiryBuffer);
  }

  // ✅ Check if token is valid (exists and not expired)
  isTokenValid() {
    const token = this.getToken();
    return token && !this.isTokenExpired(token);
  }

  // ✅ Refresh token if needed
  async ensureValidToken() {
    if (this.isTokenValid()) {
      return this.getToken();
    }

    // ✅ Try to refresh the token
    const refreshed = await this.refreshToken();
    if (refreshed) {
      return this.getToken();
    }

    // ✅ Refresh failed, remove tokens and return null
    this.removeTokens();
    return null;
  }

  // ✅ Refresh authentication token
  async refreshToken() {
    const refreshToken = localStorage.getItem(this.refreshTokenKey);
    if (!refreshToken) {
      return false;
    }

    try {
      const response = await fetch('/auth/refresh', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ refreshToken })
      });

      if (response.ok) {
        const { accessToken, refreshToken: newRefreshToken } = await response.json();
        this.setToken(accessToken, newRefreshToken);
        return true;
      }

      // ✅ Refresh failed, remove tokens
      this.removeTokens();
      return false;
    } catch (error) {
      console.error('Token refresh failed:', error);
      this.removeTokens();
      return false;
    }
  }
}

// ✅ Initialize token manager
const tokenManager = new TokenManager();

API Service with Token Management:

// ✅ API service with integrated token management
class ApiService {
  constructor(baseURL) {
    this.baseURL = baseURL;
    this.tokenManager = new TokenManager();
  }

  // ✅ Make authenticated request with automatic token management
  async request(endpoint, options = {}) {
    // ✅ Ensure we have a valid token
    const token = await this.tokenManager.ensureValidToken();
    
    if (!token) {
      // ✅ No valid token, redirect to login
      window.location.href = '/login';
      throw new Error('Authentication required');
    }

    const url = `${this.baseURL}${endpoint}`;
    const config = {
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
        ...options.headers
      },
      ...options
    };

    let response = await fetch(url, config);

    // ✅ Handle 401 errors
    if (response.status === 401) {
      // ✅ Try to refresh token and retry request
      const refreshed = await this.tokenManager.refreshToken();
      
      if (refreshed) {
        // ✅ Retry request with new token
        const newToken = this.tokenManager.getToken();
        const retryConfig = {
          ...config,
          headers: {
            ...config.headers,
            'Authorization': `Bearer ${newToken}`
          }
        };
        
        response = await fetch(url, retryConfig);
      }

      if (response.status === 401) {
        // ✅ Still unauthorized, remove tokens and redirect
        this.tokenManager.removeTokens();
        window.location.href = '/login';
        throw new Error('Authentication failed');
      }
    }

    return response;
  }

  // ✅ Convenience methods
  async get(endpoint, options = {}) {
    return this.request(endpoint, { ...options, method: 'GET' });
  }

  async post(endpoint, data, options = {}) {
    return this.request(endpoint, {
      ...options,
      method: 'POST',
      body: JSON.stringify(data)
    });
  }

  async put(endpoint, data, options = {}) {
    return this.request(endpoint, {
      ...options,
      method: 'PUT',
      body: JSON.stringify(data)
    });
  }

  async delete(endpoint, options = {}) {
    return this.request(endpoint, { ...options, method: 'DELETE' });
  }
}

// ✅ Initialize API service
const apiService = new ApiService('/api');

Solution 3: Frontend Implementation

❌ Without Proper Frontend Handling:

// ❌ Basic fetch without 401 handling
async function fetchUserData() {
  const response = await fetch('/api/user');
  if (response.status === 401) {
    // ❌ Just redirect without token refresh
    window.location.href = '/login';
  }
  return response.json();
}

✅ With Proper Frontend Handling:

React Hook for Authentication:

// ✅ React hook for authentication management
import { useState, useEffect, useContext, createContext } from 'react';

const AuthContext = createContext();

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

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

  useEffect(() => {
    // ✅ Check authentication status on app load
    checkAuthStatus();
  }, []);

  const checkAuthStatus = async () => {
    try {
      const isValid = tokenManager.isTokenValid();
      if (isValid) {
        // ✅ Get user info from token or API
        const token = tokenManager.getToken();
        const payload = JSON.parse(atob(token.split('.')[1]));
        setUser({
          id: payload.userId,
          email: payload.email,
          name: payload.name
        });
      }
    } catch (error) {
      console.error('Auth status check failed:', error);
    } finally {
      setLoading(false);
    }
  };

  const login = async (credentials) => {
    try {
      const response = await fetch('/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(credentials)
      });

      if (!response.ok) {
        throw new Error('Login failed');
      }

      const { accessToken, refreshToken, user } = await response.json();
      
      // ✅ Store tokens
      tokenManager.setToken(accessToken, refreshToken);
      
      // ✅ Set user
      setUser(user);

      return { success: true, user };
    } catch (error) {
      return { success: false, error: error.message };
    }
  };

  const logout = () => {
    tokenManager.removeTokens();
    setUser(null);
    window.location.href = '/login';
  };

  const value = {
    user,
    login,
    logout,
    loading,
    isAuthenticated: !!user && tokenManager.isTokenValid()
  };

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

// ✅ Custom hook for making authenticated requests
export const useApi = () => {
  const { isAuthenticated } = useAuth();
  const tokenManager = new TokenManager();

  const authenticatedFetch = async (url, options = {}) => {
    if (!isAuthenticated) {
      throw new Error('User not authenticated');
    }

    // ✅ Ensure valid token
    const token = await tokenManager.ensureValidToken();
    if (!token) {
      throw new Error('Authentication required');
    }

    const config = {
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
        ...options.headers
      },
      ...options
    };

    let response = await fetch(url, config);

    // ✅ Handle 401 errors
    if (response.status === 401) {
      const refreshed = await tokenManager.refreshToken();
      if (refreshed) {
        const newToken = tokenManager.getToken();
        const retryConfig = {
          ...config,
          headers: {
            ...config.headers,
            'Authorization': `Bearer ${newToken}`
          }
        };
        response = await fetch(url, retryConfig);
      }

      if (response.status === 401) {
        // ✅ Still unauthorized
        tokenManager.removeTokens();
        window.location.href = '/login';
        throw new Error('Authentication failed');
      }
    }

    return response;
  };

  return { authenticatedFetch };
};

Frontend API Service:

// ✅ Frontend API service with 401 handling
class FrontendApiService {
  constructor() {
    this.tokenManager = new TokenManager();
    this.baseUrl = process.env.REACT_APP_API_URL || '/api';
    this.pendingRequests = [];
  }

  // ✅ Queue requests while refreshing token
  queueRequest(request) {
    return new Promise((resolve, reject) => {
      this.pendingRequests.push({ resolve, reject, request });
    });
  }

  // ✅ Process queued requests
  async processQueue(token) {
    const requests = [...this.pendingRequests];
    this.pendingRequests = [];

    for (const { request, resolve, reject } of requests) {
      try {
        const response = await request(token);
        resolve(response);
      } catch (error) {
        reject(error);
      }
    }
  }

  // ✅ Make authenticated request with 401 handling
  async request(endpoint, options = {}) {
    // ✅ Check if there are pending requests
    if (this.pendingRequests.length > 0) {
      return this.queueRequest((token) => this.makeRequest(endpoint, options, token));
    }

    const token = await this.tokenManager.ensureValidToken();
    if (!token) {
      window.location.href = '/login';
      throw new Error('Authentication required');
    }

    return this.makeRequest(endpoint, options, token);
  }

  // ✅ Internal request method
  async makeRequest(endpoint, options, token) {
    const url = `${this.baseUrl}${endpoint}`;
    const config = {
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
        ...options.headers
      },
      ...options
    };

    let response = await fetch(url, config);

    if (response.status === 401) {
      // ✅ Handle 401 error
      const refreshed = await this.handle401Error();
      
      if (refreshed) {
        const newToken = this.tokenManager.getToken();
        const retryConfig = {
          ...config,
          headers: {
            ...config.headers,
            'Authorization': `Bearer ${newToken}`
          }
        };
        response = await fetch(url, retryConfig);
      } else {
        // ✅ Refresh failed, redirect to login
        window.location.href = '/login';
        throw new Error('Authentication failed');
      }
    }

    return response;
  }

  // ✅ Handle 401 error with token refresh
  async handle401Error() {
    const refreshToken = localStorage.getItem('refreshToken');
    if (!refreshToken) {
      return false;
    }

    try {
      const response = await fetch('/auth/refresh', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ refreshToken })
      });

      if (response.ok) {
        const { accessToken, refreshToken: newRefreshToken } = await response.json();
        this.tokenManager.setToken(accessToken, newRefreshToken);
        
        // ✅ Process queued requests
        if (this.pendingRequests.length > 0) {
          await this.processQueue(accessToken);
        }
        
        return true;
      }

      // ✅ Refresh failed
      this.tokenManager.removeTokens();
      return false;
    } catch (error) {
      console.error('Token refresh failed:', error);
      this.tokenManager.removeTokens();
      return false;
    }
  }

  // ✅ Convenience methods
  get(endpoint, options = {}) {
    return this.request(endpoint, { ...options, method: 'GET' });
  }

  post(endpoint, data, options = {}) {
    return this.request(endpoint, {
      ...options,
      method: 'POST',
      body: JSON.stringify(data)
    });
  }

  put(endpoint, data, options = {}) {
    return this.request(endpoint, {
      ...options,
      method: 'PUT',
      body: JSON.stringify(data)
    });
  }

  delete(endpoint, options = {}) {
    return this.request(endpoint, { ...options, method: 'DELETE' });
  }
}

// ✅ Initialize frontend API service
const frontendApi = new FrontendApiService();

Solution 4: Backend Implementation

❌ Without Proper Backend Handling:

// ❌ Basic authentication middleware without proper error handling
const authenticate = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  // ❌ No validation, no proper error handling
  req.user = verifyToken(token);
  next();
};

✅ With Proper Backend Handling:

Express.js Authentication Middleware:

// ✅ Comprehensive authentication middleware
const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');

// ✅ Rate limiting for auth endpoints
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // Limit each IP to 5 requests per windowMs
  message: 'Too many authentication attempts, please try again later.'
});

// ✅ JWT authentication middleware
const authenticate = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN

  if (!token) {
    return res.status(401).json({
      error: 'Unauthorized',
      message: 'Access token required',
      code: 'TOKEN_MISSING'
    });
  }

  jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
    if (err) {
      // ✅ Handle specific JWT errors
      if (err.name === 'TokenExpiredError') {
        return res.status(401).json({
          error: 'Unauthorized',
          message: 'Token has expired',
          code: 'TOKEN_EXPIRED'
        });
      } else if (err.name === 'JsonWebTokenError') {
        return res.status(401).json({
          error: 'Unauthorized',
          message: 'Invalid token format',
          code: 'TOKEN_INVALID'
        });
      } else if (err.name === 'NotBeforeError') {
        return res.status(401).json({
          error: 'Unauthorized',
          message: 'Token not active yet',
          code: 'TOKEN_NOT_ACTIVE'
        });
      }

      return res.status(401).json({
        error: 'Unauthorized',
        message: 'Token validation failed',
        code: 'VALIDATION_FAILED'
      });
    }

    // ✅ Token is valid, attach user info to request
    req.user = decoded;
    next();
  });
};

// ✅ Refresh token endpoint
app.post('/auth/refresh', authLimiter, async (req, res) => {
  const { refreshToken } = req.body;

  if (!refreshToken) {
    return res.status(401).json({
      error: 'Unauthorized',
      message: 'Refresh token required',
      code: 'REFRESH_TOKEN_MISSING'
    });
  }

  try {
    // ✅ Verify refresh token
    const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
    
    // ✅ In a real application, you would check if the refresh token is still valid
    // ✅ and hasn't been revoked (stored in database/blacklist)
    
    // ✅ Generate new access token
    const newAccessToken = jwt.sign(
      { 
        userId: decoded.userId, 
        email: decoded.email 
      },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }
    );

    // ✅ Generate new refresh token (optional - rotate refresh tokens)
    const newRefreshToken = jwt.sign(
      { 
        userId: decoded.userId, 
        email: decoded.email 
      },
      process.env.REFRESH_TOKEN_SECRET,
      { expiresIn: '7d' }
    );

    res.json({
      accessToken: newAccessToken,
      refreshToken: newRefreshToken
    });
  } catch (error) {
    res.status(403).json({
      error: 'Forbidden',
      message: 'Invalid refresh token',
      code: 'INVALID_REFRESH_TOKEN'
    });
  }
});

// ✅ Protected route using authentication middleware
app.get('/api/protected', authenticate, (req, res) => {
  res.json({
    message: 'Access granted',
    user: req.user
  });
});

Advanced Authentication with Database:

// ✅ Advanced authentication with database validation
const jwt = require('jsonwebtoken');

class AuthenticationService {
  constructor(userRepository, tokenRepository) {
    this.userRepository = userRepository;
    this.tokenRepository = tokenRepository;
  }

  // ✅ Authenticate user with token validation
  async authenticate(req, res, next) {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1];

    if (!token) {
      return this.unauthorizedResponse(res, 'Access token required');
    }

    try {
      // ✅ Verify token
      const decoded = jwt.verify(token, process.env.JWT_SECRET);

      // ✅ Check if user exists and is active
      const user = await this.userRepository.findById(decoded.userId);
      if (!user || !user.active) {
        return this.unauthorizedResponse(res, 'User account not active');
      }

      // ✅ Check if token is blacklisted
      const isBlacklisted = await this.tokenRepository.isBlacklisted(token);
      if (isBlacklisted) {
        return this.unauthorizedResponse(res, 'Token has been revoked');
      }

      // ✅ Attach user to request
      req.user = user;
      next();
    } catch (error) {
      this.handleTokenError(res, error);
    }
  }

  // ✅ Handle different token errors
  handleTokenError(res, error) {
    if (error.name === 'TokenExpiredError') {
      return this.unauthorizedResponse(res, 'Token has expired', 'TOKEN_EXPIRED');
    } else if (error.name === 'JsonWebTokenError') {
      return this.unauthorizedResponse(res, 'Invalid token', 'INVALID_TOKEN');
    } else if (error.name === 'NotBeforeError') {
      return this.unauthorizedResponse(res, 'Token not active yet', 'TOKEN_NOT_ACTIVE');
    }

    return this.unauthorizedResponse(res, 'Token validation failed', 'VALIDATION_ERROR');
  }

  // ✅ Send standardized unauthorized response
  unauthorizedResponse(res, message, code = 'UNAUTHORIZED') {
    res.status(401).json({
      error: 'Unauthorized',
      message: message,
      code: code
    });
  }

  // ✅ Generate tokens for user
  async generateTokens(user) {
    const accessToken = jwt.sign(
      { userId: user.id, email: user.email },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }
    );

    const refreshToken = jwt.sign(
      { userId: user.id, email: user.email },
      process.env.REFRESH_TOKEN_SECRET,
      { expiresIn: '7d' }
    );

    // ✅ Store refresh token in database
    await this.tokenRepository.storeRefreshToken(user.id, refreshToken);

    return { accessToken, refreshToken };
  }

  // ✅ Revoke refresh token
  async revokeRefreshToken(refreshToken) {
    try {
      const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
      await this.tokenRepository.blacklistToken(refreshToken);
      return true;
    } catch (error) {
      return false;
    }
  }
}

// ✅ Initialize authentication service
const authService = new AuthenticationService(userRepo, tokenRepo);

// ✅ Use in route
app.get('/api/protected', (req, res, next) => {
  authService.authenticate(req, res, next);
}, (req, res) => {
  res.json({ message: 'Access granted', user: req.user });
});

Solution 5: Error Handling and Recovery

❌ Without Proper Error Recovery:

// ❌ Basic error handling
fetch('/api/protected')
  .then(response => {
    if (response.status === 401) {
      window.location.href = '/login'; // ❌ Immediate redirect
    }
    return response.json();
  });

✅ With Proper Error Recovery:

Comprehensive Error Handling:

// ✅ Comprehensive 401 error handling and recovery
class ErrorHandler {
  constructor(tokenManager) {
    this.tokenManager = tokenManager;
  }

  // ✅ Handle 401 errors with appropriate recovery strategies
  async handle401Error(originalRequest, originalOptions) {
    // ✅ First, try to refresh the token
    const refreshed = await this.tokenManager.refreshToken();
    
    if (refreshed) {
      // ✅ Retry the original request with new token
      const newToken = this.tokenManager.getToken();
      return this.retryRequest(originalRequest, originalOptions, newToken);
    }

    // ✅ Token refresh failed, clear tokens and redirect
    this.tokenManager.removeTokens();
    this.redirectToLogin();
    throw new Error('Authentication failed and could not be recovered');
  }

  // ✅ Retry request with new token
  async retryRequest(endpoint, options, token) {
    const config = {
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
        ...options.headers
      },
      ...options
    };

    const response = await fetch(endpoint, config);
    
    if (response.status === 401) {
      // ✅ Even with new token, still unauthorized
      this.tokenManager.removeTokens();
      this.redirectToLogin();
      throw new Error('Authentication failed with new token');
    }

    return response;
  }

  // ✅ Redirect to login with return URL
  redirectToLogin() {
    const returnUrl = encodeURIComponent(window.location.pathname + window.location.search);
    window.location.href = `/login?return=${returnUrl}`;
  }

  // ✅ Handle different types of authentication errors
  handleAuthError(errorCode, errorMessage) {
    switch (errorCode) {
      case 'TOKEN_EXPIRED':
        // ✅ Handle expired token
        return this.handleExpiredToken();
      case 'TOKEN_INVALID':
        // ✅ Handle invalid token
        return this.handleInvalidToken();
      case 'ACCOUNT_SUSPENDED':
        // ✅ Handle suspended account
        return this.handleSuspendedAccount();
      default:
        // ✅ Handle generic auth error
        return this.handleGenericAuthError(errorMessage);
    }
  }

  handleExpiredToken() {
    // ✅ Try to refresh token
    return this.tokenManager.refreshToken();
  }

  handleInvalidToken() {
    // ✅ Clear invalid tokens and redirect to login
    this.tokenManager.removeTokens();
    this.redirectToLogin();
  }

  handleSuspendedAccount() {
    // ✅ Show account suspended message
    alert('Your account has been suspended. Please contact support.');
    this.tokenManager.removeTokens();
    this.redirectToLogin();
  }

  handleGenericAuthError(message) {
    console.error('Authentication error:', message);
    this.tokenManager.removeTokens();
    this.redirectToLogin();
  }
}

// ✅ Initialize error handler
const errorHandler = new ErrorHandler(new TokenManager());

Working Code Examples

Complete Frontend Implementation:

// auth-manager.js
class AuthManager {
  constructor() {
    this.tokenManager = new TokenManager();
    this.errorHandler = new ErrorHandler(this.tokenManager);
    this.apiClients = new Map(); // Store different API clients
  }

  // ✅ Initialize API client with 401 handling
  createApiClient(baseURL) {
    const client = {
      request: async (endpoint, options = {}) => {
        let token = await this.tokenManager.ensureValidToken();
        
        if (!token) {
          throw new Error('Authentication required');
        }

        const url = `${baseURL}${endpoint}`;
        const config = {
          headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json',
            ...options.headers
          },
          ...options
        };

        let response = await fetch(url, config);

        if (response.status === 401) {
          // ✅ Handle 401 error with recovery
          try {
            await this.errorHandler.handle401Error(endpoint, options);
            // ✅ The error handler will redirect to login
            return null;
          } catch (error) {
            console.error('401 error recovery failed:', error);
            throw error;
          }
        }

        return response;
      },

      get: (endpoint, options = {}) => {
        return this.request(endpoint, { ...options, method: 'GET' });
      },

      post: (endpoint, data, options = {}) => {
        return this.request(endpoint, {
          ...options,
          method: 'POST',
          body: JSON.stringify(data)
        });
      }
    };

    this.apiClients.set(baseURL, client);
    return client;
  }

  // ✅ Login method
  async login(credentials) {
    try {
      const response = await fetch('/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(credentials)
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.message || 'Login failed');
      }

      const { accessToken, refreshToken, user } = await response.json();
      
      // ✅ Store tokens
      this.tokenManager.setToken(accessToken, refreshToken);
      
      return { success: true, user };
    } catch (error) {
      console.error('Login failed:', error);
      return { success: false, error: error.message };
    }
  }

  // ✅ Logout method
  logout() {
    this.tokenManager.removeTokens();
    window.location.href = '/login';
  }

  // ✅ Check if user is authenticated
  isAuthenticated() {
    return this.tokenManager.isTokenValid();
  }
}

// ✅ Initialize auth manager
const authManager = new AuthManager();

// ✅ Create API client
const apiClient = authManager.createApiClient('/api');

// ✅ Export for use in other modules
window.AuthManager = authManager;
window.ApiClient = apiClient;

Complete Backend Implementation:

// server.js
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const rateLimit = require('express-rate-limit');
const cors = require('cors');

const app = express();

// ✅ Middleware
app.use(cors());
app.use(express.json());

// ✅ Rate limiting
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5,
  message: 'Too many authentication attempts, please try again later.'
});

// ✅ Mock user database (use real database in production)
const users = [
  {
    id: 1,
    email: 'user@example.com',
    password: '$2b$10$...', // hashed password
    active: true
  }
];

// ✅ Authentication middleware
const authenticate = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.status(401).json({
      error: 'Unauthorized',
      message: 'Access token required',
      code: 'TOKEN_MISSING'
    });
  }

  jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key', (err, decoded) => {
    if (err) {
      if (err.name === 'TokenExpiredError') {
        return res.status(401).json({
          error: 'Unauthorized',
          message: 'Token has expired',
          code: 'TOKEN_EXPIRED'
        });
      }
      return res.status(401).json({
        error: 'Unauthorized',
        message: 'Invalid token',
        code: 'TOKEN_INVALID'
      });
    }

    req.user = decoded;
    next();
  });
};

// ✅ Login endpoint
app.post('/auth/login', authLimiter, async (req, res) => {
  const { email, password } = req.body;

  // ✅ Find user
  const user = users.find(u => u.email === email);
  if (!user || !user.active) {
    return res.status(401).json({
      error: 'Unauthorized',
      message: 'Invalid credentials',
      code: 'INVALID_CREDENTIALS'
    });
  }

  // ✅ Verify password
  const isValidPassword = await bcrypt.compare(password, user.password);
  if (!isValidPassword) {
    return res.status(401).json({
      error: 'Unauthorized',
      message: 'Invalid credentials',
      code: 'INVALID_CREDENTIALS'
    });
  }

  // ✅ Generate tokens
  const accessToken = jwt.sign(
    { userId: user.id, email: user.email },
    process.env.JWT_SECRET || 'your-secret-key',
    { expiresIn: '15m' }
  );

  const refreshToken = jwt.sign(
    { userId: user.id, email: user.email },
    process.env.REFRESH_TOKEN_SECRET || 'your-refresh-secret',
    { expiresIn: '7d' }
  );

  res.json({
    accessToken,
    refreshToken,
    user: { id: user.id, email: user.email }
  });
});

// ✅ Refresh token endpoint
app.post('/auth/refresh', (req, res) => {
  const { refreshToken } = req.body;

  if (!refreshToken) {
    return res.status(401).json({
      error: 'Unauthorized',
      message: 'Refresh token required',
      code: 'REFRESH_TOKEN_MISSING'
    });
  }

  jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET || 'your-refresh-secret', (err, decoded) => {
    if (err) {
      return res.status(403).json({
        error: 'Forbidden',
        message: 'Invalid refresh token',
        code: 'INVALID_REFRESH_TOKEN'
      });
    }

    // ✅ Find user
    const user = users.find(u => u.id === decoded.userId);
    if (!user || !user.active) {
      return res.status(403).json({
        error: 'Forbidden',
        message: 'User account not active',
        code: 'USER_INACTIVE'
      });
    }

    // ✅ Generate new access token
    const newAccessToken = jwt.sign(
      { userId: user.id, email: user.email },
      process.env.JWT_SECRET || 'your-secret-key',
      { expiresIn: '15m' }
    );

    res.json({ accessToken: newAccessToken });
  });
});

// ✅ Protected endpoint
app.get('/api/profile', authenticate, (req, res) => {
  res.json({ message: 'Profile data', user: req.user });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Best Practices for Authentication

1. Secure Token Storage

// ✅ Use HttpOnly cookies for refresh tokens when possible
// ✅ Store access tokens in memory instead of localStorage for sensitive apps
// ✅ Implement proper XSS protection

2. Short-Lived Access Tokens

// ✅ Use short-lived access tokens (15-30 minutes)
const token = jwt.sign(payload, secret, { expiresIn: '15m' });

3. Proper Error Messages

// ✅ Don't expose sensitive information in error messages
res.status(401).json({ error: 'Unauthorized' }); // ✅ Generic message
// Don't do: res.status(401).json({ error: 'Wrong password' }); // ❌ Too specific

4. Rate Limiting

// ✅ Implement rate limiting for authentication endpoints
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5 // Limit each IP to 5 requests per windowMs
});

5. Secure Token Transmission

// ✅ Always use HTTPS for token transmission
// ✅ Never transmit tokens over HTTP

Debugging Steps

Step 1: Check Authentication Header

# ✅ Verify the Authorization header is present and correctly formatted
# Should be: "Authorization: Bearer <token>"

Step 2: Validate Token

// ✅ Decode and inspect the token
const token = localStorage.getItem('authToken');
const payload = JSON.parse(atob(token.split('.')[1]));
console.log('Token payload:', payload);

Step 3: Check Token Expiration

// ✅ Verify token hasn't expired
const isExpired = Date.now() >= payload.exp * 1000;
console.log('Token expired:', isExpired);

Step 4: Test API Endpoint Directly

# ✅ Use curl or Postman to test the endpoint directly
curl -H "Authorization: Bearer YOUR_TOKEN_HERE" http://localhost:3000/api/protected

Common Mistakes to Avoid

1. Hardcoding Credentials

// ❌ Don't hardcode tokens or credentials
const token = 'hardcoded-token'; // ❌ Very bad

// ✅ Use environment variables or secure storage
const token = process.env.AUTH_TOKEN; // ✅ Better

2. Not Handling Token Expiration

// ❌ Don't ignore token expiration
const token = localStorage.getItem('authToken'); // ❌ Might be expired
fetch('/api/data', { headers: { 'Authorization': `Bearer ${token}` } });

3. Poor Error Handling

// ❌ Don't ignore 401 errors
fetch('/api/protected').catch(err => console.error(err)); // ❌ Silent failure

4. Storing Tokens Insecurely

// ❌ Don't store sensitive tokens in plain text
sessionStorage.setItem('token', token); // ❌ Vulnerable to XSS

Performance Considerations

1. Efficient Token Validation

// ✅ Cache token validation results when appropriate
// ✅ Use synchronous validation for better performance

2. Minimize Token Requests

// ✅ Refresh tokens proactively before they expire
// ✅ Batch API requests when possible

3. Optimize Network Requests

// ✅ Use efficient HTTP methods
// ✅ Implement proper caching strategies

Security Considerations

1. Protect Against CSRF

// ✅ Implement CSRF protection for state-changing requests
// ✅ Use anti-CSRF tokens

2. Secure Token Storage

// ✅ Use HttpOnly cookies for refresh tokens
// ✅ Implement proper XSS protection

3. Validate Token Audience

// ✅ Always validate token audience and issuer
jwt.verify(token, secret, {
  audience: 'your-app',
  issuer: 'your-auth-server'
});

Testing Authentication

1. Unit Tests for Token Validation

// ✅ Test token validation with various scenarios
test('should reject expired token', () => {
  const expiredToken = jwt.sign({ exp: Math.floor(Date.now() / 1000) - 1 }, secret);
  expect(() => jwt.verify(expiredToken, secret)).toThrow();
});

2. Integration Tests for 401 Handling

// ✅ Test 401 error handling
test('should handle 401 error correctly', async () => {
  // Mock a 401 response and verify handling
});

3. Security Tests

// ✅ Test security aspects of authentication
// ✅ Test token tampering protection
// ✅ Test brute force protection

Alternative Solutions

1. Session-Based Authentication

// ✅ Consider session-based auth for traditional web apps
// ✅ Better for apps with frequent server communication

2. OAuth 2.0 Integration

// ✅ Consider OAuth 2.0 for third-party authentication
// ✅ Better for applications integrating with external services

3. API Key Authentication

// ✅ Consider API keys for machine-to-machine communication
// ✅ Better for service-to-service communication

Migration Checklist

  • Implement proper authentication header management
  • Add token refresh mechanisms
  • Secure token storage and transmission
  • Test 401 error handling and recovery
  • Implement proper error handling and user feedback
  • Add security measures against common attacks
  • Update documentation for team members
  • Test with various authentication states

Conclusion

The ‘401 Unauthorized’ error is a common HTTP status code that indicates missing or invalid authentication credentials. By following the solutions provided in this guide—whether through proper authentication headers, token management, secure implementation, or comprehensive error handling—you can create robust and secure authentication systems.

The key is to implement proper authentication flows, handle token expiration gracefully, provide clear error messages, and maintain security best practices. With proper implementation of these patterns, your applications will provide a seamless authentication experience while maintaining security.

Remember to always use secure token storage, implement token refresh mechanisms, handle errors gracefully, and test thoroughly to create secure and user-friendly applications that properly handle authentication challenges.

Gautam Sharma

About Gautam Sharma

Full-stack developer and tech blogger sharing coding tutorials and best practices

Related Articles

Tutorials

Fix: JWT token invalid or expired error - Complete Guide

Complete guide to fix JWT token invalid or expired errors. Learn how to handle JWT authentication issues with practical solutions, token refresh strategies, and best practices for secure applications.

January 8, 2026
Tutorials

How to Fix: 403 Forbidden Error - Complete Tutorial

Complete guide to fix 403 Forbidden errors. Learn how to resolve permission issues with practical solutions, authorization management, and best practices for secure API communication.

January 8, 2026
Tutorials

How to Fix: API Key Not Working error - Full Tutorial

Complete guide to fix API key not working errors. Learn how to resolve authentication issues with practical solutions, key management, and best practices for secure API communication.

January 8, 2026