Documentation Index
Fetch the complete documentation index at: https://aiauth.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Error Handling Guide
Learn how to properly handle errors and implement robust error recovery in your Auth-Agent implementation.
Error Types
AuthError
All SDK methods throw AuthError objects with detailed information:
interface AuthError extends Error {
code: string; // Error code
description?: string; // Human-readable description
statusCode?: number; // HTTP status code
}
Common Error Codes
Access token has expired. Refresh the token or re-authenticate.
Refresh token is invalid or expired. User must re-authenticate.
OAuth state parameter mismatch. Possible CSRF attack.
Code verifier not found. Must call getAuthorizationUrl() first.
Insufficient permissions for the requested operation.
No refresh token provided for refresh operation.
Token required but not provided.
Token introspection failed.
Basic Error Handling
Try-Catch Pattern
import type { AuthError } from "ai-auth";
try {
const tokens = await sdk.exchangeCode(code, state, redirectUri);
console.log("Success!", tokens);
} catch (error) {
const authError = error as AuthError;
console.error("Error:", authError.code);
console.error("Description:", authError.description);
console.error("Status:", authError.statusCode);
}
Specific Error Handling
try {
const tokens = await sdk.exchangeCode(code, state, redirectUri);
} catch (error) {
const authError = error as AuthError;
switch (authError.code) {
case "state_mismatch":
console.error("CSRF attack detected!");
redirectToError("Security violation detected");
break;
case "pkce_missing":
console.error("Invalid OAuth flow");
redirectToLogin();
break;
default:
console.error("Unknown error:", authError.description);
showErrorMessage(authError.description);
break;
}
}
Handling Specific Scenarios
Token Expiration
async function makeAuthenticatedRequest() {
try {
const accessToken = tokenManager.getAccessToken();
return await sdk.getUserInfo(accessToken);
} catch (error) {
const authError = error as AuthError;
if (authError.code === "token_expired" && tokenManager.refreshToken) {
// Attempt to refresh
try {
await sdk.refreshAccessToken(tokenManager.refreshToken);
// Retry the request
const newToken = tokenManager.getAccessToken();
return await sdk.getUserInfo(newToken);
} catch (refreshError) {
console.error("Refresh failed, redirecting to login");
redirectToLogin();
}
}
throw error;
}
}
Network Errors
import { sleep } from "ai-auth";
async function retryableRequest<T>(
fn: () => Promise<T>,
maxRetries = 3
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error: any) {
const authError = error as AuthError;
// Don't retry on auth errors
if (authError.statusCode === 401 || authError.statusCode === 403) {
throw error;
}
// Don't retry on client errors
if (authError.statusCode && authError.statusCode < 500) {
throw error;
}
// Last attempt
if (i === maxRetries - 1) {
throw error;
}
// Network error or server error - retry with backoff
const delay = Math.pow(2, i) * 1000;
console.log(`Request failed, retrying in ${delay}ms...`);
await sleep(delay);
}
}
throw new Error("Max retries exceeded");
}
// Usage
const userInfo = await retryableRequest(() => sdk.getUserInfo(accessToken));
CSRF Protection
async function handleOAuthCallback(code: string, state: string) {
// Retrieve stored state
const storedState = await storage.getState();
try {
if (state !== storedState) {
throw new Error("State mismatch - possible CSRF attack");
}
const tokens = await sdk.exchangeCode(code, state, redirectUri);
return tokens;
} catch (error) {
const authError = error as AuthError;
if (authError.code === "state_mismatch" || error.message.includes("CSRF")) {
// Log security incident
await logSecurityIncident({
type: "csrf_attempt",
details: { code, receivedState: state, expectedState: storedState },
});
// Clear potentially compromised session
await storage.clear();
// Redirect to safe page
redirectToError("Security violation detected. Please try again.");
return null;
}
throw error;
} finally {
// Always clear stored state after use
await storage.clearState();
}
}
Error Recovery Strategies
Graceful Degradation
class APIClient {
async getUserProfile(): Promise<UserProfile | null> {
try {
const token = await this.getValidToken();
const profile = await sdk.getAgentProfile(token);
return profile;
} catch (error) {
const authError = error as AuthError;
if (authError.code === "token_expired") {
console.warn("Session expired, user needs to re-login");
this.notifySessionExpired();
return null; // Graceful degradation
}
if (authError.statusCode && authError.statusCode >= 500) {
console.error("Server error, using cached profile");
return this.getCachedProfile(); // Fallback to cache
}
throw error; // Re-throw unexpected errors
}
}
}
Automatic Recovery
class RobustAPIClient {
private retryCount = 0;
private maxRetries = 3;
async makeRequest<T>(requestFn: (token: string) => Promise<T>): Promise<T> {
while (this.retryCount < this.maxRetries) {
try {
const token = await this.getValidToken();
const result = await requestFn(token);
this.retryCount = 0; // Reset on success
return result;
} catch (error) {
const authError = error as AuthError;
if (authError.code === "token_expired") {
// Try to refresh
try {
await this.refreshToken();
this.retryCount++;
continue; // Retry with new token
} catch (refreshError) {
throw new Error("Session expired, please login again");
}
}
if (authError.statusCode && authError.statusCode >= 500) {
// Server error - retry with backoff
this.retryCount++;
if (this.retryCount < this.maxRetries) {
await sleep(Math.pow(2, this.retryCount) * 1000);
continue;
}
}
throw error; // Non-recoverable error
}
}
throw new Error("Max retries exceeded");
}
}
User Notification
interface ErrorNotification {
title: string;
message: string;
action?: () => void;
}
function handleError(error: AuthError): ErrorNotification {
switch (error.code) {
case "token_expired":
case "refresh_failed":
return {
title: "Session Expired",
message: "Your session has expired. Please log in again.",
action: () => redirectToLogin(),
};
case "forbidden":
return {
title: "Access Denied",
message: "You don't have permission to perform this action.",
action: () => redirectToHome(),
};
case "state_mismatch":
return {
title: "Security Error",
message: "A security issue was detected. Please try again.",
action: () => redirectToLogin(),
};
default:
if (error.statusCode && error.statusCode >= 500) {
return {
title: "Server Error",
message:
"Our servers are experiencing issues. Please try again later.",
action: () => retryLastAction(),
};
}
return {
title: "Error",
message: error.description || "An unexpected error occurred.",
action: undefined,
};
}
}
// Usage
try {
await sdk.getUserInfo(accessToken);
} catch (error) {
const notification = handleError(error as AuthError);
showNotification(notification);
}
Logging and Monitoring
Error Logging
class ErrorLogger {
static log(error: AuthError, context: any = {}) {
const errorData = {
timestamp: new Date().toISOString(),
code: error.code,
description: error.description,
statusCode: error.statusCode,
stack: error.stack,
context,
};
// Log to console
console.error("[Auth Error]", errorData);
// Send to monitoring service
if (typeof window !== "undefined") {
// Client-side monitoring (e.g., Sentry)
window.errorTracker?.captureException(error, {
extra: errorData,
});
} else {
// Server-side monitoring
logger.error("Auth error occurred", errorData);
}
}
}
// Usage
try {
await sdk.exchangeCode(code, state, redirectUri);
} catch (error) {
ErrorLogger.log(error as AuthError, {
operation: "oauth_code_exchange",
userId: currentUser?.id,
});
throw error;
}
Metrics Collection
class ErrorMetrics {
private static errors: Map<string, number> = new Map();
static track(error: AuthError) {
const key = error.code;
const count = this.errors.get(key) || 0;
this.errors.set(key, count + 1);
}
static getReport() {
return Array.from(this.errors.entries()).map(([code, count]) => ({
code,
count,
}));
}
static clear() {
this.errors.clear();
}
}
// Track errors
try {
await sdk.getUserInfo(accessToken);
} catch (error) {
ErrorMetrics.track(error as AuthError);
throw error;
}
// Get report
console.log("Error Report:", ErrorMetrics.getReport());
Complete Error Handling Example
import { AgentSDK, TokenManager, sleep } from "ai-auth";
import type { AuthError } from "ai-auth";
class RobustAuthService {
private sdk: AgentSDK;
private tokenManager: TokenManager;
private refreshInProgress = false;
constructor() {
this.tokenManager = new TokenManager();
this.sdk = new AgentSDK({
agentId: process.env.AGENT_ID!,
onTokensReceived: (tokens) => this.tokenManager.setTokens(tokens),
onTokensRefreshed: (tokens) => this.tokenManager.setTokens(tokens),
onTokensRevoked: () => this.tokenManager.clear(),
});
}
async makeRequest<T>(
requestFn: (token: string) => Promise<T>,
options = { retries: 3, backoff: true }
): Promise<T> {
let attempts = 0;
while (attempts < options.retries) {
try {
// Ensure token is valid
await this.ensureValidToken();
// Make request
const token = this.tokenManager.getAccessToken();
return await requestFn(token);
} catch (error) {
const authError = error as AuthError;
attempts++;
// Handle specific errors
if (this.shouldRetry(authError, attempts, options.retries)) {
if (options.backoff) {
await sleep(Math.pow(2, attempts - 1) * 1000);
}
continue;
}
// Log and re-throw
this.logError(authError);
throw this.createUserFriendlyError(authError);
}
}
throw new Error("Max retry attempts exceeded");
}
private async ensureValidToken() {
if (this.tokenManager.isExpired()) {
// Prevent multiple simultaneous refresh attempts
if (this.refreshInProgress) {
// Wait for refresh to complete
while (this.refreshInProgress) {
await sleep(100);
}
return;
}
if (!this.tokenManager.refreshToken) {
throw this.createError("session_expired", "Please log in again");
}
try {
this.refreshInProgress = true;
await this.sdk.refreshAccessToken(this.tokenManager.refreshToken);
} catch (error) {
this.tokenManager.clear();
throw this.createError("session_expired", "Your session has expired");
} finally {
this.refreshInProgress = false;
}
}
}
private shouldRetry(
error: AuthError,
attempts: number,
maxRetries: number
): boolean {
// Don't retry if max attempts reached
if (attempts >= maxRetries) {
return false;
}
// Don't retry auth errors
if (error.code === "forbidden" || error.code === "state_mismatch") {
return false;
}
// Don't retry client errors (except token_expired)
if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500) {
return error.code === "token_expired";
}
// Retry server errors and network errors
return !error.statusCode || error.statusCode >= 500;
}
private logError(error: AuthError) {
console.error("[Auth Service Error]", {
code: error.code,
description: error.description,
statusCode: error.statusCode,
timestamp: new Date().toISOString(),
});
}
private createError(code: string, message: string): AuthError {
const error = new Error(message) as AuthError;
error.code = code;
error.description = message;
return error;
}
private createUserFriendlyError(error: AuthError): Error {
const userMessages: Record<string, string> = {
token_expired: "Your session has expired. Please log in again.",
refresh_failed: "Your session has expired. Please log in again.",
forbidden: "You don't have permission to perform this action.",
state_mismatch: "Security verification failed. Please try again.",
session_expired: "Your session has expired. Please log in again.",
};
const message =
userMessages[error.code] || error.description || "An error occurred";
return new Error(message);
}
}
export default new RobustAuthService();
Best Practices
Always catch and handle errors
Never let authentication errors crash your application. Always use try-catch
blocks.
Provide user-friendly error messages
Translate technical error codes into messages users can understand and act
on.
Log all authentication errors with context to help diagnose issues in
production.
Implement retry logic for transient errors
Network errors and server errors should be retried with exponential backoff.
Handle token expiration gracefully
Attempt to refresh expired tokens automatically before prompting users to
re-login.
Track error frequencies to identify systemic issues early.
Next Steps
Token Refresh
Learn about token refresh strategies
AgentSDK Methods
Explore all SDK methods