Implement JWT, API keys, OAuth2, and custom 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.
The most common authentication method for modern APIs. JWT tokens are stateless and contain encoded user information.
// 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);
}
// 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();
Simple authentication method where an API key is sent in headers or query parameters.
// 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');
// 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
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';
});
}
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);
}
});
Create custom authentication mechanisms for specialized requirements like HMAC signatures or multi-factor 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 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 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.
Learn how to use Upload and Download Managers for sophisticated file operations with progress tracking and control.
Continue to Managers →