Authentication and Authorization Design Patterns - sonuprajapati15/Authentication-Authorization GitHub Wiki

Authentication and Authorization Design Patterns

1. Introduction

This document provides a solution for handling authentication and authorization, including token expiration and refresh strategies. It describes various authentication and authorization design patterns, including:

  1. Token-Based Authentication
  2. Session-Based Authentication
  3. OAuth 2.0 Authentication
  4. SAML Authentication
  5. Multi-Factor Authentication (MFA)
  6. Role-Based Access Control (RBAC)
  7. Attribute-Based Access Control (ABAC)
  8. Zero Trust Security Model

2. Authentication Design Patterns

2.1 Token-Based Authentication

  • Uses JWT (JSON Web Tokens) for stateless authentication.
  • Tokens are stored in localStorage, sessionStorage, or HttpOnly cookies.
  • Suitable for single-page applications (SPAs) and mobile apps.

2.2 Session-Based Authentication

  • Traditional approach using server-side sessions.
  • Sessions are stored in databases or in-memory caches (e.g., Redis).
  • Best suited for monolithic applications.

2.3 OAuth 2.0 Authentication

  • Industry-standard protocol for authorization.
  • Uses access tokens and refresh tokens.
  • Supports third-party authentication via providers like Google, Facebook.

2.4 SAML Authentication

  • Security Assertion Markup Language (SAML) is used for Single Sign-On (SSO).
  • Common in enterprise applications for federated authentication.

2.5 Multi-Factor Authentication (MFA)

  • Enhances security by requiring multiple verification factors.
  • Examples: SMS OTP, email codes, biometric authentication.

3. Authorization Design Patterns

3.1 Role-Based Access Control (RBAC)

  • Users are assigned predefined roles (e.g., Admin, Editor, Viewer).
  • Access permissions are granted based on roles.
  • Simple and widely used in enterprise applications.

3.2 Attribute-Based Access Control (ABAC)

  • Access is granted based on user attributes (e.g., department, location).
  • Provides dynamic and fine-grained access control.

3.3 Zero Trust Security Model

  • "Never trust, always verify" approach.
  • Requires authentication and authorization at every access request.
  • Used in highly secure environments.

4. High-Level Design (HLD)

Problem Statement

  • Secure authentication and authorization for modern applications.
  • Ensure minimal user friction while maintaining security.

Solution Overview

  • Choose the appropriate authentication mechanism based on application needs.
  • Implement secure authorization patterns to restrict access.

Architecture Diagram

graph TD;
    User -->|Login| AuthServer;
    AuthServer -->|Generate Access & Refresh Token| User;
    User -->|Send Access Token| API;
    API -->|Validate Token| AuthServer;
    AuthServer -->|Token Expired?| CheckExpiration;
    CheckExpiration -->|Yes| RefreshToken;
    RefreshToken -->|Return New Access Token| API;
    API -->|Serve Request| User;
Loading

5. Low-Level Design (LLD)

Client-Managed Token Refresh

  • The client checks token validity before making API calls.
  • Refreshes the token when necessary.

Implementation (React + Axios)

import axios from 'axios';

const api = axios.create({
  baseURL: 'https://your-api.com',
  withCredentials: true,
});

api.interceptors.request.use(async (config) => {
  let accessToken = localStorage.getItem('accessToken');

  if (isTokenExpired(accessToken)) {
    accessToken = await refreshToken();
  }

  config.headers.Authorization = `Bearer ${accessToken}`;
  return config;
}, (error) => Promise.reject(error));

async function refreshToken() {
  try {
    const response = await axios.post('https://your-api.com/refresh-token', {}, { withCredentials: true });
    localStorage.setItem('accessToken', response.data.accessToken);
    return response.data.accessToken;
  } catch (error) {
    console.error('Session expired. Please log in again.');
  }
}

Backend-Managed Token Refresh

  • The backend checks token validity before processing API requests.
  • Issues a new token if the existing one is expired.

Implementation (Node.js + Express)

app.use(async (req, res, next) => {
  let accessToken = req.headers.authorization?.split(" ")[1];
  if (!accessToken) return res.sendStatus(401);

  try {
    jwt.verify(accessToken, ACCESS_SECRET);
    return next();
  } catch (err) {
    if (err.name === 'TokenExpiredError') {
      const refreshToken = req.cookies.refreshToken;
      if (!refreshToken) return res.sendStatus(403);

      try {
        const payload = jwt.verify(refreshToken, REFRESH_SECRET);
        const newAccessToken = jwt.sign({ userId: payload.userId }, ACCESS_SECRET, { expiresIn: '15m' });
        res.setHeader('Authorization', `Bearer ${newAccessToken}`);
        req.headers.authorization = `Bearer ${newAccessToken}`;
        return next();
      } catch (refreshErr) {
        return res.sendStatus(403);
      }
    } else {
      return res.sendStatus(401);
    }
  }
});

6. Conclusion

  • Choose an authentication pattern based on security and user experience needs.
  • Implement authorization models (RBAC, ABAC) based on access control requirements.
  • Always store refresh tokens securely using HttpOnly cookies.

7. Future Enhancements

  • Implement OAuth 2.0 with PKCE for enhanced security.
  • Use adaptive authentication based on user behavior.
  • Apply fine-grained access control using ABAC models.

There are two main strategies for handling token refresh, each with its own pros and cons:

1️⃣ Client-Managed Token Refresh (Best for Frontend-Heavy Apps)

The client checks token validity and refreshes it before making API calls.

How it Works

  • Before every API call, the client checks if the access token is expired.
  • If expired, it calls the refresh token API to get a new access token.
  • Stores the new access token and proceeds with the original API request.

Implementation Steps

  1. Store Tokens Securely:

    • Store the access token in memory (or localStorage if necessary).
    • Store the refresh token in HttpOnly Secure Cookies (so it's not accessible via JavaScript).
  2. Intercept Requests (Axios Example):

    • Use an Axios interceptor (or similar for Fetch API) to check token validity before each request.

Code Example (React + Axios)

import axios from 'axios';

// Create Axios instance const api = axios.create({ baseURL: 'https://your-api.com', withCredentials: true, // Important for sending cookies });

// Request interceptor for automatic refresh api.interceptors.request.use( async (config) => { let accessToken = localStorage.getItem('accessToken');

// Check if token is expired
if (isTokenExpired(accessToken)) {
  accessToken = await refreshToken();
}

// Attach new access token to request
config.headers.Authorization = `Bearer ${accessToken}`;
return config;

}, (error) => Promise.reject(error) );

// Function to refresh token async function refreshToken() { try { const response = await axios.post('https://your-api.com/refresh-token', {}, { withCredentials: true }); const { accessToken } = response.data; localStorage.setItem('accessToken', accessToken); return accessToken; } catch (error) { console.error('Session expired. Please log in again.'); // Redirect to login } }

// Function to check if token is expired function isTokenExpired(token) { if (!token) return true; const { exp } = JSON.parse(atob(token.split('.')[1])); return exp < Date.now() / 1000; }

export default api;


βœ… Pros of Client-Managed Approach

  • Lower backend load (tokens are refreshed only when needed).
  • Faster responses as API calls don’t wait for the server to refresh tokens.
  • Works well for frontend-heavy applications.

❌ Cons

  • Relies on client storage, which is less secure than backend-managed solutions.
  • More logic on the client side.

2️⃣ Backend-Managed Token Refresh (Best for Secure APIs)

The backend checks the token's validity and refreshes before every API response.

How it Works

  1. The client sends the access token in the request.
  2. The backend:
    • Validates the access token.
    • If expired, uses the refresh token (sent via cookies) to get a new access token.
    • Sends the new access + refresh token in the API response.
  3. The client updates the stored tokens.

Implementation Steps

  1. Client sends access token (or gets auto-refreshed)
  2. Backend verifies token & refreshes if expired
  3. Backend responds with new access token (if refreshed)

Backend Code Example (Node.js + Express)

app.use(async (req, res, next) => {
  let accessToken = req.headers.authorization?.split(" ")[1];

if (!accessToken) return res.sendStatus(401);

try { // Verify access token jwt.verify(accessToken, ACCESS_SECRET); return next(); // Token valid, proceed with API request } catch (err) { if (err.name === 'TokenExpiredError') { // Try to refresh token const refreshToken = req.cookies.refreshToken; if (!refreshToken) return res.sendStatus(403); // No refresh token, force login

  try {
    const payload = jwt.verify(refreshToken, REFRESH_SECRET);
    const newAccessToken = jwt.sign({ userId: payload.userId }, ACCESS_SECRET, { expiresIn: '15m' });

    // Send new token in response headers
    res.setHeader('Authorization', `Bearer ${newAccessToken}`);
    req.headers.authorization = `Bearer ${newAccessToken}`; // Set for next middleware
    return next();
  } catch (refreshErr) {
    return res.sendStatus(403); // Refresh token invalid, force login
  }
} else {
  return res.sendStatus(401);
}

} });


βœ… Pros of Backend-Managed Approach

  • More secure (refresh logic is handled on the server).
  • Client doesn’t have to manage token refresh logic.
  • Works well for backend-heavy applications or APIs.

❌ Cons

  • Every API call might involve token refresh (adds latency).
  • Increases backend load due to frequent refresh token checks.

πŸš€ Which One to Choose?

Feature Client-Managed Refresh Backend-Managed Refresh
Security πŸ”Έ Moderate (tokens stored on client) βœ… High (tokens refreshed on backend)
Performance βœ… Faster (refresh only when needed) πŸ”Έ Slightly slower (refresh checked in backend)
Backend Load βœ… Lower load πŸ”Έ Higher load
Works for Web Apps βœ… Yes βœ… Yes
Works for APIs πŸ”Έ Requires client handling βœ… Fully managed
Best for... Frontend-heavy apps Secure API systems

My Recommendation

  1. If you want better security, go for backend-managed.
  2. If you need better performance, use client-managed with refresh before API calls.
⚠️ **GitHub.com Fallback** ⚠️