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 commonly returned by the API:
Authentication Errors
| Error Code | Status | Description | Resolution |
|---|---|---|---|
TOKEN_EXPIRED |
401 | JWT access token has expired | Refresh the token using sso.auth.refreshToken() |
UNAUTHORIZED |
401 | Invalid or missing authentication credentials | Re-authenticate the user |
JWT_ERROR |
401 | Malformed or invalid JWT token | Clear stored token and re-authenticate |
Authorization Errors
| Error Code | Status | Description | Resolution |
|---|---|---|---|
FORBIDDEN |
403 | User lacks required permissions | Check user role and permissions |
ORGANIZATION_NOT_ACTIVE |
403 | Organization is pending approval or suspended | Contact platform administrator |
Resource Errors
| Error Code | Status | Description | Resolution |
|---|---|---|---|
NOT_FOUND |
404 | Requested resource does not exist | Verify resource identifiers |
Device Flow Errors
| Error Code | Status | Description | Resolution |
|---|---|---|---|
DEVICE_CODE_PENDING |
400 | User has not authorized the device yet | Continue polling |
DEVICE_CODE_EXPIRED |
400 | Device code has expired | Request a new device code |
Validation Errors
| Error Code | Status | Description | Resolution |
|---|---|---|---|
BAD_REQUEST |
400 | Invalid request parameters | Check request payload |
SERVICE_LIMIT_EXCEEDED |
400 | Service creation limit reached for organization tier | Upgrade organization tier |
TEAM_LIMIT_EXCEEDED |
400 | Team member limit reached for organization tier | Upgrade organization tier |
INVITATION_EXPIRED |
400 | Invitation link has expired | Request a new invitation |
Server Errors
| Error Code | Status | Description | Resolution |
|---|---|---|---|
INTERNAL_SERVER_ERROR |
500 | Unexpected server error | Retry request or contact support |
DATABASE_ERROR |
500 | Database operation failed | Retry request or contact support |
OAUTH_ERROR |
500 | OAuth provider communication failed | Retry authentication flow |
STRIPE_ERROR |
500 | Billing system error | Contact support |
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) {
const tokens = await sso.auth.refreshToken(refreshToken);
sso.setAuthToken(tokens.access_token);
localStorage.setItem('sso_access_token', tokens.access_token);
localStorage.setItem('sso_refresh_token', tokens.refresh_token);
// 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 codeerror.isAuthError(): Check if error is authentication-relatederror.isForbidden(): Check if error is permission-relatederror.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
-
Always check error types: Use
instanceof SsoApiErrorto distinguish API errors from network or other errors. -
Use error codes, not status codes: Error codes are more specific than HTTP status codes. Use
error.errorCodeinstead oferror.statusCodefor conditional logic. -
Handle token expiration gracefully: Implement automatic token refresh before redirecting users to login.
-
Provide user-friendly messages: Don’t expose raw error messages to end users. Map error codes to user-friendly messages.
-
Log errors for debugging: Always log errors with full context (code, status, message, timestamp) for debugging.
-
Implement retry logic: For transient errors (network issues, server errors), implement exponential backoff retry logic.
-
Use helper methods: Leverage the built-in helper methods (
isAuthError(),isForbidden(), etc.) for cleaner code.