Error Handling

Guide to handling errors in the AuthOS SDK with examples for common error scenarios

Updated Apr 12, 2026 Edit this page

Error Handling

The AuthOS SDK provides structured error handling through the SsoApiError class. All API errors include an error code, status code, message, and timestamp for consistent error handling across your application.

The SsoApiError Class

All API errors thrown by the SDK are instances of SsoApiError, which extends the standard JavaScript Error class with additional properties:

class SsoApiError extends Error {
  statusCode: number;      // HTTP status code (e.g., 401, 403, 404)
  errorCode: string;       // Specific error code (e.g., "TOKEN_EXPIRED")
  timestamp: string;       // ISO 8601 timestamp when error occurred
}

Basic Error Handling

import { SsoClient, SsoApiError } from '@drmhse/sso-sdk';

const sso = new SsoClient({ baseURL: 'https://sso.example.com' });

try {
  const profile = await sso.user.getProfile();
  console.log(profile);
} catch (error) {
  if (error instanceof SsoApiError) {
    console.error(`Error: ${error.message}`);
    console.error(`Status: ${error.statusCode}`);
    console.error(`Code: ${error.errorCode}`);
    console.error(`Timestamp: ${error.timestamp}`);
  } else {
    // Network error or other non-API error
    console.error('Unexpected error:', error);
  }
}

Common Error Codes

The following error codes are returned by the API and are available in the AuthErrorCodes enum:

Authentication & Authorization Errors

Error Code Status Description
TOKEN_EXPIRED 401 JWT access token has expired
UNAUTHORIZED 401 Invalid or missing authentication credentials
JWT_ERROR 401 Malformed or invalid JWT token
FORBIDDEN 403 User lacks required permissions
ORGANIZATION_NOT_ACTIVE 403 Organization is pending approval or suspended
FEATURE_NOT_AVAILABLE_IN_TIER 403 Feature not available in organization’s current tier

Request & Validation Errors

Error Code Status Description
BAD_REQUEST 400 Invalid request parameters or malformed JSON
DUPLICATE_CONSTRAINT 400 A resource with this information already exists
NOT_FOUND 404 Requested resource does not exist
INVITATION_EXPIRED 400 Invitation link has expired
TOO_MANY_REQUESTS 429 Rate limit exceeded

Device Flow Errors

Error Code Status Description
DEVICE_CODE_PENDING 400 Authorization is still pending (keep polling)
DEVICE_CODE_EXPIRED 400 Device code has expired

Resource Limit Errors

Error Code Status Description
SERVICE_LIMIT_EXCEEDED 400 Service creation limit reached for organization tier
TEAM_LIMIT_EXCEEDED 400 Team member limit reached for organization tier

Server Errors

Error Code Status Description
INTERNAL_SERVER_ERROR 500 Unexpected server error
DATABASE_ERROR 500 General database operation failed
OAUTH_ERROR 500 OAuth provider communication failed
STRIPE_ERROR 500 Billing system error
GENERIC_ERROR 500 General system error

Error Handling Patterns

Token Expiration Handling

Automatically refresh expired tokens before retrying the request:

async function makeAuthenticatedRequest<T>(
  requestFn: () => Promise<T>
): Promise<T> {
  try {
    return await requestFn();
  } catch (error) {
    if (error instanceof SsoApiError && error.errorCode === 'TOKEN_EXPIRED') {
      // Token expired - refresh it
      const refreshToken = localStorage.getItem('sso_refresh_token');
      if (refreshToken) {
        // sso.auth.refreshToken() automatically updates the session
        await sso.auth.refreshToken(refreshToken);

        // Retry the original request
        return await requestFn();
      }
      // No refresh token - redirect to login
      window.location.href = '/login';
      throw error;
    }
    throw error;
  }
}

// Usage
const profile = await makeAuthenticatedRequest(() => sso.user.getProfile());

Device Flow Error Handling

Handle device flow polling with proper error handling:

const pollForToken = async (deviceCode: string, clientId: string) => {
  const maxAttempts = 120; // 10 minutes with 5-second intervals
  let attempts = 0;

  const poll = async (): Promise<TokenResponse> => {
    try {
      const tokens = await sso.auth.deviceCode.exchangeToken({
        grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
        device_code: deviceCode,
        client_id: clientId
      });
      return tokens;
    } catch (error) {
      if (error instanceof SsoApiError) {
        if (error.errorCode === 'DEVICE_CODE_PENDING') {
          // User hasn't authorized yet - continue polling
          attempts++;
          if (attempts >= maxAttempts) {
            throw new Error('Device authorization timeout - please try again');
          }
          await new Promise(resolve => setTimeout(resolve, 5000));
          return poll();
        } else if (error.errorCode === 'DEVICE_CODE_EXPIRED') {
          throw new Error('Device code expired - please restart the login process');
        }
      }
      throw error;
    }
  };

  return poll();
};

Permission Error Handling

Display user-friendly messages for permission errors:

async function performAdminAction() {
  try {
    await sso.organizations.delete('acme-corp');
  } catch (error) {
    if (error instanceof SsoApiError) {
      if (error.errorCode === 'FORBIDDEN') {
        alert('You do not have permission to delete this organization. Contact your organization admin.');
        return;
      } else if (error.errorCode === 'ORGANIZATION_NOT_ACTIVE') {
        alert('This organization is not active. Please contact platform support.');
        return;
      }
    }
    // Generic error handling
    alert('An error occurred. Please try again later.');
  }
}

Resource Not Found Handling

Gracefully handle missing resources:

async function loadOrganization(slug: string) {
  try {
    const org = await sso.organizations.get(slug);
    return org;
  } catch (error) {
    if (error instanceof SsoApiError && error.errorCode === 'NOT_FOUND') {
      // Organization doesn't exist - redirect to organization list
      console.warn(`Organization '${slug}' not found`);
      window.location.href = '/organizations';
      return null;
    }
    throw error;
  }
}

Validation Error Handling

Handle validation errors with specific feedback:

async function createService(orgSlug: string, serviceData: any) {
  try {
    const service = await sso.services.create(orgSlug, serviceData);
    return service;
  } catch (error) {
    if (error instanceof SsoApiError) {
      if (error.errorCode === 'SERVICE_LIMIT_EXCEEDED') {
        alert('Service limit reached for your organization tier. Please upgrade to create more services.');
        return null;
      } else if (error.errorCode === 'BAD_REQUEST') {
        alert(`Invalid service data: ${error.message}`);
        return null;
      }
    }
    throw error;
  }
}

Helper Methods

The SsoApiError class provides convenience methods for common checks:

try {
  await sso.user.getProfile();
} catch (error) {
  if (error instanceof SsoApiError) {
    if (error.isAuthError()) {
      // Handle authentication errors (401, TOKEN_EXPIRED, UNAUTHORIZED)
      redirectToLogin();
    } else if (error.isForbidden()) {
      // Handle permission errors (403, FORBIDDEN)
      showPermissionDeniedMessage();
    } else if (error.isNotFound()) {
      // Handle not found errors (404, NOT_FOUND)
      showResourceNotFoundMessage();
    }
  }
}

Available Helper Methods

  • error.is(errorCode: string): Check for specific error code
  • error.isAuthError(): Check if error is authentication-related
  • error.isForbidden(): Check if error is permission-related
  • error.isNotFound(): Check if error is resource not found

Global Error Handler

Implement a global error handler for consistent error handling across your application:

import { SsoApiError } from '@drmhse/sso-sdk';

class ErrorHandler {
  static handle(error: unknown): void {
    if (error instanceof SsoApiError) {
      // Log to monitoring service
      console.error('AuthOS API Error:', {
        code: error.errorCode,
        status: error.statusCode,
        message: error.message,
        timestamp: error.timestamp
      });

      // Handle specific error types
      if (error.isAuthError()) {
        this.handleAuthError(error);
      } else if (error.isForbidden()) {
        this.handlePermissionError(error);
      } else {
        this.handleGenericError(error);
      }
    } else {
      // Handle non-API errors
      console.error('Unexpected error:', error);
      this.showGenericErrorMessage();
    }
  }

  private static handleAuthError(error: SsoApiError): void {
    // Clear tokens and redirect to login
    localStorage.removeItem('sso_access_token');
    localStorage.removeItem('sso_refresh_token');
    window.location.href = '/login';
  }

  private static handlePermissionError(error: SsoApiError): void {
    alert('You do not have permission to perform this action.');
  }

  private static handleGenericError(error: SsoApiError): void {
    alert(`An error occurred: ${error.message}`);
  }

  private static showGenericErrorMessage(): void {
    alert('An unexpected error occurred. Please try again later.');
  }
}

// Usage
try {
  await sso.user.getProfile();
} catch (error) {
  ErrorHandler.handle(error);
}

Best Practices

  1. Always check error types: Use instanceof SsoApiError to distinguish API errors from network or other errors.

  2. Use error codes, not status codes: Error codes are more specific than HTTP status codes. Use error.errorCode instead of error.statusCode for conditional logic.

  3. Handle token expiration gracefully: Implement automatic token refresh before redirecting users to login.

  4. Provide user-friendly messages: Don’t expose raw error messages to end users. Map error codes to user-friendly messages.

  5. Log errors for debugging: Always log errors with full context (code, status, message, timestamp) for debugging.

  6. Implement retry logic: For transient errors (network issues, server errors), implement exponential backoff retry logic.

  7. Use helper methods: Leverage the built-in helper methods (isAuthError(), isForbidden(), etc.) for cleaner code.