Chapter 9: Authentication and Security

Haiyue
9min

Chapter 9: Authentication and Security

Learning Objectives

  1. Implement Token authentication mechanism
  2. Master JWT handling and refresh
  3. Learn OAuth2.0 integration
  4. Understand HTTPS and certificate configuration
  5. Implement secure cross-origin handling

9.1 Token Authentication

Bearer Token Authentication

class TokenManager {
  constructor() {
    this.token = localStorage.getItem('access_token');
    this.refreshToken = localStorage.getItem('refresh_token');
  }

  setToken(accessToken, refreshToken = null) {
    this.token = accessToken;
    localStorage.setItem('access_token', accessToken);

    if (refreshToken) {
      this.refreshToken = refreshToken;
      localStorage.setItem('refresh_token', refreshToken);
    }
  }

  getToken() {
    return this.token;
  }

  clearToken() {
    this.token = null;
    this.refreshToken = null;
    localStorage.removeItem('access_token');
    localStorage.removeItem('refresh_token');
  }

  isTokenValid() {
    if (!this.token) return false;

    try {
      const payload = JSON.parse(atob(this.token.split('.')[1]));
      const now = Math.floor(Date.now() / 1000);
      return payload.exp > now;
    } catch (error) {
      return false;
    }
  }
}

const tokenManager = new TokenManager();

9.2 JWT Handling and Auto-Refresh

JWT Interceptor

class JWTInterceptor {
  constructor(axiosInstance, tokenManager) {
    this.axios = axiosInstance;
    this.tokenManager = tokenManager;
    this.isRefreshing = false;
    this.failedQueue = [];

    this.setupInterceptors();
  }

  setupInterceptors() {
    // Request interceptor - add token
    this.axios.interceptors.request.use((config) => {
      const token = this.tokenManager.getToken();
      if (token) {
        config.headers.Authorization = `Bearer ${token}`;
      }
      return config;
    });

    // Response interceptor - handle token expiration
    this.axios.interceptors.response.use(
      (response) => response,
      async (error) => {
        const originalRequest = error.config;

        if (error.response?.status === 401 && !originalRequest._retry) {
          if (this.isRefreshing) {
            // If token is being refreshed, add request to queue
            return new Promise((resolve, reject) => {
              this.failedQueue.push({ resolve, reject });
            }).then(token => {
              originalRequest.headers.Authorization = `Bearer ${token}`;
              return this.axios(originalRequest);
            });
          }

          originalRequest._retry = true;
          this.isRefreshing = true;

          try {
            const newToken = await this.refreshAccessToken();
            this.tokenManager.setToken(newToken);

            // Process queued requests
            this.processQueue(null, newToken);

            originalRequest.headers.Authorization = `Bearer ${newToken}`;
            return this.axios(originalRequest);
          } catch (refreshError) {
            this.processQueue(refreshError, null);
            this.tokenManager.clearToken();
            window.location.href = '/login';
            return Promise.reject(refreshError);
          } finally {
            this.isRefreshing = false;
          }
        }

        return Promise.reject(error);
      }
    );
  }

  async refreshAccessToken() {
    const refreshToken = this.tokenManager.refreshToken;
    if (!refreshToken) {
      throw new Error('No refresh token available');
    }

    const response = await axios.post('/api/auth/refresh', {
      refresh_token: refreshToken
    });

    return response.data.access_token;
  }

  processQueue(error, token = null) {
    this.failedQueue.forEach(({ resolve, reject }) => {
      if (error) {
        reject(error);
      } else {
        resolve(token);
      }
    });

    this.failedQueue = [];
  }
}

9.3 OAuth2.0 Integration

OAuth2 Flow Implementation

class OAuth2Handler {
  constructor(clientId, redirectUri, authUrl) {
    this.clientId = clientId;
    this.redirectUri = redirectUri;
    this.authUrl = authUrl;
  }

  // Authorization code flow
  initiateAuthorizationCode(scope = 'read write') {
    const state = this.generateState();
    sessionStorage.setItem('oauth_state', state);

    const params = new URLSearchParams({
      response_type: 'code',
      client_id: this.clientId,
      redirect_uri: this.redirectUri,
      scope: scope,
      state: state
    });

    window.location.href = `${this.authUrl}?${params.toString()}`;
  }

  // Handle authorization callback
  async handleAuthorizationCallback(code, state) {
    // Validate state
    const storedState = sessionStorage.getItem('oauth_state');
    if (state !== storedState) {
      throw new Error('Invalid state parameter');
    }

    // Exchange for access token
    const response = await axios.post('/api/oauth/token', {
      grant_type: 'authorization_code',
      client_id: this.clientId,
      code: code,
      redirect_uri: this.redirectUri
    });

    return response.data;
  }

  generateState() {
    return Math.random().toString(36).substring(2, 15) +
           Math.random().toString(36).substring(2, 15);
  }
}

9.4 HTTPS and Security Configuration

Secure Request Configuration

// HTTPS and security configuration
const secureAxios = axios.create({
  // Force HTTPS
  baseURL: 'https://api.example.com',

  // Security headers
  headers: {
    'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
    'X-Content-Type-Options': 'nosniff',
    'X-Frame-Options': 'DENY',
    'X-XSS-Protection': '1; mode=block'
  },

  // Request validation
  validateStatus: (status) => {
    return status >= 200 && status < 300;
  }
});

// Certificate configuration in Node.js environment
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'production') {
  const https = require('https');
  const fs = require('fs');

  const httpsAgent = new https.Agent({
    cert: fs.readFileSync('path/to/client-cert.pem'),
    key: fs.readFileSync('path/to/client-key.pem'),
    ca: fs.readFileSync('path/to/ca-cert.pem'),
    rejectUnauthorized: true
  });

  secureAxios.defaults.httpsAgent = httpsAgent;
}

9.5 CSRF Protection

CSRF Token Handling

class CSRFHandler {
  constructor() {
    this.token = null;
    this.init();
  }

  async init() {
    try {
      // Get CSRF token
      const response = await axios.get('/api/csrf-token');
      this.token = response.data.token;

      // Set default header
      axios.defaults.headers.common['X-CSRF-TOKEN'] = this.token;
    } catch (error) {
      console.error('Failed to get CSRF token:', error);
    }
  }

  async refreshToken() {
    await this.init();
  }

  getToken() {
    return this.token;
  }
}

const csrfHandler = new CSRFHandler();

9.6 API Key Management

Secure API Key Handling

class APIKeyManager {
  constructor() {
    this.apiKey = process.env.API_KEY || '';
    this.keyRotationInterval = 24 * 60 * 60 * 1000; // 24 hours
    this.setupKeyRotation();
  }

  setupKeyRotation() {
    setInterval(() => {
      this.rotateApiKey();
    }, this.keyRotationInterval);
  }

  async rotateApiKey() {
    try {
      const response = await axios.post('/api/rotate-key', {
        current_key: this.apiKey
      });

      this.apiKey = response.data.new_key;
      process.env.API_KEY = this.apiKey;

      console.log('API key rotated successfully');
    } catch (error) {
      console.error('Failed to rotate API key:', error);
    }
  }

  getApiKey() {
    return this.apiKey;
  }

  setApiKeyHeader(config) {
    config.headers = config.headers || {};
    config.headers['X-API-Key'] = this.getApiKey();
    return config;
  }
}

Chapter Summary

  • Token authentication is the standard authentication method for modern web applications
  • JWT auto-refresh ensures continuous user experience
  • OAuth2.0 provides secure third-party authorization
  • HTTPS and security configuration protect data transmission
  • CSRF protection prevents cross-site request forgery attacks

Key Points

  • Never store sensitive information on the client side
  • Use HTTPS to protect all authentication-related communications
  • Implement token auto-refresh to avoid repeated user logins
  • Proper security header configuration enhances application security