Authentication & Security

Implement JWT, API keys, OAuth2, and custom authentication strategies

Intermediate 20 min read

Authentication Strategies

Apisto provides flexible authentication mechanisms for securing your API calls. Choose the strategy that fits your security requirements.

💡 Security First: Always use HTTPS in production, validate tokens on the server, and implement proper token expiration and refresh mechanisms.

JWT (JSON Web Tokens)

The most common authentication method for modern APIs. JWT tokens are stateless and contain encoded user information.

🔑 Basic JWT Setup


// Set JWT token (most common approach)
api.setAuthToken('your-jwt-token-here');

// All subsequent requests will include:
// Authorization: Bearer your-jwt-token-here

// Example: Login and set token
async function loginUser(credentials) {
  try {
    const response = await api.post('/auth/login', credentials);
    const { token, user } = response;
    
    // Store token in Apisto
    api.setAuthToken(token);
    
    // Optionally store in localStorage
    localStorage.setItem('authToken', token);
    localStorage.setItem('user', JSON.stringify(user));
    
    return user;
  } catch (error) {
    console.error('Login failed:', error);
    throw error;
  }
}

// Example: Logout
function logoutUser() {
  api.clearAuth();
  localStorage.removeItem('authToken');
  localStorage.removeItem('user');
  window.location.href = '/login';
}

// Auto-set token on page load
const savedToken = localStorage.getItem('authToken');
if (savedToken) {
  api.setAuthToken(savedToken);
}
            

🔄 Advanced JWT Management


// JWT with automatic token refresh
class JwtAuthManager {
  constructor(api) {
    this.api = api;
    this.isRefreshing = false;
    this.refreshSubscribers = [];
  }

  async login(credentials) {
    const response = await this.api.post('/auth/login', credentials);
    this.setTokens(response);
    return response.user;
  }

  setTokens({ accessToken, refreshToken, expiresIn }) {
    this.api.setAuthToken(accessToken);
    localStorage.setItem('accessToken', accessToken);
    localStorage.setItem('refreshToken', refreshToken);
    localStorage.setItem('tokenExpiry', Date.now() + (expiresIn * 1000));
  }

  async refreshToken() {
    if (this.isRefreshing) {
      return new Promise(resolve => {
        this.refreshSubscribers.push(resolve);
      });
    }

    this.isRefreshing = true;
    
    try {
      const refreshToken = localStorage.getItem('refreshToken');
      const response = await this.api.post('/auth/refresh', { refreshToken });
      
      this.setTokens(response);
      this.isRefreshing = false;
      
      // Resolve all pending requests
      this.refreshSubscribers.forEach(resolve => resolve());
      this.refreshSubscribers = [];
      
      return response.accessToken;
    } catch (error) {
      this.isRefreshing = false;
      this.refreshSubscribers = [];
      this.logout();
      throw error;
    }
  }

  isTokenExpired() {
    const expiry = localStorage.getItem('tokenExpiry');
    return !expiry || Date.now() >= parseInt(expiry);
  }

  logout() {
    this.api.clearAuth();
    localStorage.removeItem('accessToken');
    localStorage.removeItem('refreshToken');
    localStorage.removeItem('tokenExpiry');
  }

  // Add to request interceptor for auto-refresh
  setupAutoRefresh() {
    this.api.addRequestInterceptor(async (options, url) => {
      // Skip auth for login/refresh endpoints
      if (url.includes('/auth/')) return options;
      
      // Refresh token if expired or about to expire
      if (this.isTokenExpired()) {
        await this.refreshToken();
      }
      
      return options;
    });
  }
}

// Usage
const authManager = new JwtAuthManager(api);
authManager.setupAutoRefresh();
            

API Key Authentication

Simple authentication method where an API key is sent in headers or query parameters.

📋 Header API Key


// Set API key in header (most secure)
api.setApiKey('your-api-key-here', 'X-API-Key');

// All requests will include:
// X-API-Key: your-api-key-here

// Alternative: Use AuthStrategy directly
api.useAuth(AuthStrategy.apiKey('your-key', 'X-API-Key'));

// Multiple API keys for different services
const weatherApi = new Apisto({
  baseURL: 'https://api.weatherapi.com'
});
weatherApi.setApiKey('weather-key', 'X-API-Key');

const paymentApi = new Apisto({
  baseURL: 'https://api.payments.com'
});
paymentApi.setApiKey('payment-key', 'X-Payment-Key');

// Environment-based keys
const apiKey = process.env.API_KEY || 
               import.meta.env.VITE_API_KEY || 
               'fallback-key';

api.setApiKey(apiKey, 'X-API-Key');
            

🔍 Query Parameter API Key


// Some APIs require keys in query parameters
class QueryParamAuth {
  constructor(api, keyName = 'api_key') {
    this.api = api;
    this.keyName = keyName;
    this.apiKey = null;
    
    // Add request interceptor to include API key
    this.api.addRequestInterceptor(async (options, url) => {
      if (this.apiKey && !options.skipAuth) {
        const urlObj = new URL(url, window.location.origin);
        urlObj.searchParams.set(this.keyName, this.apiKey);
        options.url = urlObj.pathname + urlObj.search;
      }
      return options;
    });
  }
  
  setApiKey(key) {
    this.apiKey = key;
  }
  
  clearAuth() {
    this.apiKey = null;
  }
}

// Usage
const queryAuth = new QueryParamAuth(api, 'api_key');
queryAuth.setApiKey('your-api-key');

// Now all requests will include: ?api_key=your-api-key

// For specific APIs that need both header and query auth
api.setApiKey('header-key', 'X-API-Key'); // Header
queryAuth.setApiKey('query-key'); // Query parameter
            

OAuth2 Authentication

Industry-standard protocol for authorization. Apisto supports OAuth2 flows including authorization code and client credentials.


// OAuth2 implementation with Apisto
class OAuth2Client {
  constructor(config) {
    this.config = config;
    this.api = new Apisto({ baseURL: config.tokenUrl });
  }

  // Authorization Code Flow (for web apps)
  async authorize() {
    const params = new URLSearchParams({
      client_id: this.config.clientId,
      redirect_uri: this.config.redirectUri,
      response_type: 'code',
      scope: this.config.scope,
      state: this.generateState()
    });

    // Redirect to authorization server
    window.location.href = `${this.config.authorizationUrl}?${params}`;
  }

  // Handle callback and exchange code for tokens
  async handleCallback(code) {
    try {
      const tokens = await this.api.post('', {
        grant_type: 'authorization_code',
        client_id: this.config.clientId,
        client_secret: this.config.clientSecret,
        redirect_uri: this.config.redirectUri,
        code: code
      }, {
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        skipAuth: true
      });

      this.storeTokens(tokens);
      return tokens;
    } catch (error) {
      console.error('OAuth2 callback failed:', error);
      throw error;
    }
  }

  // Client Credentials Flow (for server-to-server)
  async clientCredentials() {
    const tokens = await this.api.post('', {
      grant_type: 'client_credentials',
      client_id: this.config.clientId,
      client_secret: this.config.clientSecret,
      scope: this.config.scope
    }, {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      skipAuth: true
    });

    this.storeTokens(tokens);
    return tokens;
  }

  // Password Grant (not recommended for client-side)
  async passwordGrant(username, password) {
    const tokens = await this.api.post('', {
      grant_type: 'password',
      client_id: this.config.clientId,
      client_secret: this.config.clientSecret,
      username: username,
      password: password,
      scope: this.config.scope
    }, {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      skipAuth: true
    });

    this.storeTokens(tokens);
    return tokens;
  }

  storeTokens(tokens) {
    localStorage.setItem('oauth_access_token', tokens.access_token);
    localStorage.setItem('oauth_refresh_token', tokens.refresh_token);
    localStorage.setItem('oauth_expires_in', Date.now() + (tokens.expires_in * 1000));
    
    // Configure Apisto to use the access token
    this.api.setAuthToken(tokens.access_token);
  }

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

  // Auto-refresh token
  async refreshToken() {
    const refreshToken = localStorage.getItem('oauth_refresh_token');
    if (!refreshToken) throw new Error('No refresh token available');

    const tokens = await this.api.post('', {
      grant_type: 'refresh_token',
      client_id: this.config.clientId,
      client_secret: this.config.clientSecret,
      refresh_token: refreshToken
    }, {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      skipAuth: true
    });

    this.storeTokens(tokens);
    return tokens;
  }
}

// Usage example
const oauthClient = new OAuth2Client({
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  authorizationUrl: 'https://provider.com/oauth/authorize',
  tokenUrl: 'https://provider.com/oauth/token',
  redirectUri: 'https://yourapp.com/callback',
  scope: 'read write'
});

// Start OAuth2 flow
document.getElementById('login-btn').addEventListener('click', () => {
  oauthClient.authorize();
});

// Handle callback (on callback page)
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');

if (code) {
  oauthClient.handleCallback(code)
    .then(() => {
      window.location.href = '/dashboard';
    })
    .catch(error => {
      console.error('OAuth2 failed:', error);
      window.location.href = '/login?error=auth_failed';
    });
}
          

Session & Cookie Authentication

Traditional session-based authentication where the server manages session state and uses cookies.


// Session-based authentication with Apisto
class SessionAuth {
  constructor(api) {
    this.api = api;
    this.setupSessionAuth();
  }

  setupSessionAuth() {
    // Use session strategy (browser handles cookies automatically)
    this.api.useAuth(AuthStrategy.session());

    // Add CSRF token handling
    this.api.addRequestInterceptor(async (options, url) => {
      // Skip for GET requests or external APIs
      if (options.method === 'GET' || !url.startsWith(this.api.baseURL)) {
        return options;
      }

      // Get CSRF token from meta tag or cookie
      const csrfToken = this.getCsrfToken();
      if (csrfToken) {
        options.headers['X-CSRF-Token'] = csrfToken;
        options.headers['X-Requested-With'] = 'XMLHttpRequest';
      }

      return options;
    });
  }

  getCsrfToken() {
    // Try to get from meta tag first
    const metaTag = document.querySelector('meta[name="csrf-token"]');
    if (metaTag) {
      return metaTag.getAttribute('content');
    }

    // Fallback to cookie
    return this.getCookie('csrf_token');
  }

  getCookie(name) {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop().split(';').shift();
  }

  // Login with session
  async login(credentials) {
    const response = await this.api.post('/auth/login', credentials, {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    });

    // Session cookie is automatically set by server
    // Browser will include it in subsequent requests

    return response;
  }

  // Logout
  async logout() {
    await this.api.post('/auth/logout');
    // Server should clear the session cookie
    window.location.href = '/login';
  }

  // Check if user is authenticated
  async checkAuth() {
    try {
      const user = await this.api.get('/auth/user');
      return user;
    } catch (error) {
      if (error.status === 401) {
        return null;
      }
      throw error;
    }
  }
}

// Usage with Rails/Django/Laravel apps
const sessionAuth = new SessionAuth(api);

// Auto-check authentication on page load
sessionAuth.checkAuth()
  .then(user => {
    if (user) {
      console.log('User is authenticated:', user);
      showAuthenticatedUI(user);
    } else {
      console.log('User not authenticated');
      showLoginUI();
    }
  })
  .catch(error => {
    console.error('Auth check failed:', error);
    showLoginUI();
  });

// Form-based login
document.getElementById('login-form').addEventListener('submit', async (e) => {
  e.preventDefault();
  
  const formData = new FormData(e.target);
  const credentials = Object.fromEntries(formData);
  
  try {
    await sessionAuth.login(credentials);
    window.location.href = '/dashboard';
  } catch (error) {
    showError('Login failed: ' + error.message);
  }
});
          

Custom Authentication Strategies

Create custom authentication mechanisms for specialized requirements like HMAC signatures or multi-factor auth.

🔐 HMAC Signature Auth


// HMAC authentication for secure API calls
class HmacAuth {
  constructor(api, secretKey) {
    this.api = api;
    this.secretKey = secretKey;
    this.setupHmacAuth();
  }

  setupHmacAuth() {
    this.api.addRequestInterceptor(async (options, url) => {
      if (options.skipAuth) return options;

      const timestamp = Date.now().toString();
      const method = options.method?.toUpperCase() || 'GET';
      const path = new URL(url).pathname;
      const body = options.body ? await this.hashBody(options.body) : '';
      
      const message = `${method}\n${path}\n${timestamp}\n${body}`;
      const signature = await this.generateSignature(message);

      options.headers['X-Auth-Timestamp'] = timestamp;
      options.headers['X-Auth-Signature'] = signature;
      options.headers['X-Auth-Version'] = '1.0';

      return options;
    });
  }

  async generateSignature(message) {
    const encoder = new TextEncoder();
    const key = await crypto.subtle.importKey(
      'raw',
      encoder.encode(this.secretKey),
      { name: 'HMAC', hash: 'SHA-256' },
      false,
      ['sign']
    );

    const signature = await crypto.subtle.sign(
      'HMAC',
      key,
      encoder.encode(message)
    );

    return Array.from(new Uint8Array(signature))
      .map(b => b.toString(16).padStart(2, '0'))
      .join('');
  }

  async hashBody(body) {
    if (!body) return '';
    
    const encoder = new TextEncoder();
    const data = encoder.encode(typeof body === 'string' ? body : JSON.stringify(body));
    const hash = await crypto.subtle.digest('SHA-256', data);
    
    return Array.from(new Uint8Array(hash))
      .map(b => b.toString(16).padStart(2, '0'))
      .join('');
  }
}

// Usage (similar to AWS signature v4)
const hmacAuth = new HmacAuth(api, 'your-secret-key');

// All requests will now include HMAC signatures
// Server can verify by recreating the signature with the same algorithm
            

🔒 Multi-Factor Auth


// Multi-factor authentication flow
class MFAAuth {
  constructor(api) {
    this.api = api;
    this.mfaRequired = false;
  }

  async login(credentials) {
    try {
      const response = await this.api.post('/auth/login', credentials);
      
      if (response.mfa_required) {
        this.mfaRequired = true;
        this.showMFAForm();
        return { mfaRequired: true };
      }
      
      // Regular login successful
      this.api.setAuthToken(response.token);
      return response;
    } catch (error) {
      throw error;
    }
  }

  async verifyMFA(code, method = 'totp') {
    try {
      const response = await this.api.post('/auth/mfa/verify', {
        code,
        method
      });

      this.api.setAuthToken(response.token);
      this.mfaRequired = false;
      return response;
    } catch (error) {
      throw error;
    }
  }

  async setupMFA() {
    const response = await this.api.post('/auth/mfa/setup');
    
    // Show QR code for TOTP setup
    this.showQRCode(response.qr_code);
    
    return response;
  }

  async confirmMFASetup(code) {
    const response = await this.api.post('/auth/mfa/confirm', { code });
    return response;
  }

  showMFAForm() {
    // Show MFA input form in your UI
    document.getElementById('mfa-section').classList.remove('hidden');
    document.getElementById('mfa-code').focus();
  }

  showQRCode(qrCodeData) {
    // Display QR code for TOTP app setup
    const qrCodeElement = document.getElementById('qr-code');
    qrCodeElement.innerHTML = `<img src="${qrCodeData}" alt="MFA QR Code">`;
    document.getElementById('mfa-setup').classList.remove('hidden');
  }
}

// Usage in login flow
const mfaAuth = new MFAAuth(api);

async function handleLogin(credentials) {
  try {
    const result = await mfaAuth.login(credentials);
    
    if (result.mfaRequired) {
      // Wait for MFA code input
      return;
    }
    
    // Login successful without MFA
    redirectToDashboard();
  } catch (error) {
    showError('Login failed: ' + error.message);
  }
}

// Handle MFA verification
document.getElementById('mfa-form').addEventListener('submit', async (e) => {
  e.preventDefault();
  const code = document.getElementById('mfa-code').value;
  
  try {
    await mfaAuth.verifyMFA(code);
    redirectToDashboard();
  } catch (error) {
    showError('MFA verification failed: ' + error.message);
  }
});
            

Complete Authentication Service


// Complete authentication service combining multiple strategies
class AuthService {
  constructor(api) {
    this.api = api;
    this.currentUser = null;
    this.setupAuthInterceptors();
  }

  setupAuthInterceptors() {
    // Auto-add auth token to requests
    this.api.addRequestInterceptor(async (options, url) => {
      if (options.skipAuth) return options;

      const token = this.getStoredToken();
      if (token) {
        options.headers['Authorization'] = `Bearer ${token}`;
      }

      return options;
    });

    // Handle auth errors globally
    this.api.addErrorInterceptor(async (error, options) => {
      if (error.status === 401) {
        // Token expired or invalid
        await this.handleUnauthorized();
      } else if (error.status === 403) {
        // Insufficient permissions
        this.handleForbidden();
      }
      return error;
    });
  }

  async login(credentials) {
    try {
      const response = await this.api.post('/auth/login', credentials);
      this.setUser(response.user, response.token);
      return response;
    } catch (error) {
      this.clearAuth();
      throw error;
    }
  }

  async logout() {
    try {
      await this.api.post('/auth/logout');
    } catch (error) {
      // Logout even if API call fails
      console.warn('Logout API call failed:', error);
    } finally {
      this.clearAuth();
      window.location.href = '/login';
    }
  }

  setUser(user, token) {
    this.currentUser = user;
    this.storeToken(token);
    this.api.setAuthToken(token);
    
    // Emit auth change event
    this.emitAuthChange('login', user);
  }

  clearAuth() {
    this.currentUser = null;
    this.clearStoredToken();
    this.api.clearAuth();
    
    // Emit auth change event
    this.emitAuthChange('logout', null);
  }

  async checkAuth() {
    try {
      const user = await this.api.get('/auth/user');
      this.currentUser = user;
      return user;
    } catch (error) {
      this.clearAuth();
      throw error;
    }
  }

  async refreshToken() {
    try {
      const response = await this.api.post('/auth/refresh');
      this.setUser(response.user, response.token);
      return response;
    } catch (error) {
      this.clearAuth();
      throw error;
    }
  }

  async handleUnauthorized() {
    // Try to refresh token first
    try {
      await this.refreshToken();
      // Token refreshed successfully, retry the original request
      return true;
    } catch (error) {
      // Refresh failed, redirect to login
      this.clearAuth();
      window.location.href = '/login?session_expired=true';
      return false;
    }
  }

  handleForbidden() {
    // Show access denied message
    showError('You do not have permission to access this resource');
    
    // Optionally redirect
    // window.location.href = '/access-denied';
  }

  // Token storage
  storeToken(token) {
    localStorage.setItem('auth_token', token);
    localStorage.setItem('auth_token_stored_at', Date.now().toString());
  }

  getStoredToken() {
    return localStorage.getItem('auth_token');
  }

  clearStoredToken() {
    localStorage.removeItem('auth_token');
    localStorage.removeItem('auth_token_stored_at');
  }

  // Event system for auth changes
  emitAuthChange(type, user) {
    const event = new CustomEvent('authChange', {
      detail: { type, user }
    });
    window.dispatchEvent(event);
  }

  // Check if user has specific role/permission
  hasRole(role) {
    return this.currentUser?.roles?.includes(role);
  }

  hasPermission(permission) {
    return this.currentUser?.permissions?.includes(permission);
  }

  // Get user info
  getUser() {
    return this.currentUser;
  }

  isAuthenticated() {
    return !!this.currentUser && !!this.getStoredToken();
  }
}

// Usage throughout your application
const authService = new AuthService(api);

// Initialize auth on app start
authService.checkAuth()
  .then(user => {
    console.log('User authenticated:', user);
    initializeApp(user);
  })
  .catch(() => {
    console.log('User not authenticated');
    showLoginScreen();
  });

// Listen for auth changes
window.addEventListener('authChange', (event) => {
  const { type, user } = event.detail;
  
  if (type === 'login') {
    showUserMenu(user);
    updateUIForAuthenticatedUser();
  } else if (type === 'logout') {
    hideUserMenu();
    updateUIForUnauthenticatedUser();
  }
});

// Protected API calls
async function fetchUserData() {
  if (!authService.isAuthenticated()) {
    throw new Error('User must be authenticated');
  }

  return await api.get('/user/data');
}

// Role-based access
function accessAdminPanel() {
  if (!authService.hasRole('admin')) {
    showError('Admin access required');
    return;
  }
  
  // Show admin panel
  showAdminPanel();
}
        

🎉 Excellent! You've mastered authentication with Apisto. You can now implement secure, production-ready authentication systems with JWT, API keys, OAuth2, and custom strategies.

Ready for advanced file management?

Learn how to use Upload and Download Managers for sophisticated file operations with progress tracking and control.

Continue to Managers →