Skip to content

SDK Authentication Guide

The Altus 4 SDK provides sophisticated authentication management with multiple strategies, automatic token refresh, and enhanced security features. This guide covers all authentication patterns and best practices for different application types.

Authentication Strategies

The SDK supports multiple authentication approaches to fit different application architectures:

  1. Cookie-Based Refresh (Recommended for web apps)
  2. JWT with Manual Management (Traditional approach)
  3. API Key Authentication (Service-to-service)
  4. Hybrid Approaches (Multiple strategies combined)

Cookie-based refresh provides the highest security for browser-based applications by storing refresh tokens as HttpOnly cookies.

  • Enhanced Security: Refresh tokens stored as HttpOnly cookies are immune to XSS attacks
  • Automatic Management: SDK handles token refresh transparently
  • Persistent Sessions: Users stay logged in across browser sessions
  • No Local Storage: Sensitive tokens never stored in localStorage

Backend Requirements

Your API backend must support:

  1. Refresh Token Cookies: Set HttpOnly cookies on successful login
  2. Refresh Endpoint: POST /auth/refresh that validates cookies
  3. Logout Endpoint: POST /auth/logout that clears cookies
javascript
// Example backend login endpoint
app.post('/auth/login', async (req, res) => {
  const { email, password } = req.body;

  // Validate credentials
  const user = await authenticateUser(email, password);
  if (!user) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  // Generate tokens
  const accessToken = generateAccessToken(user.id);
  const refreshToken = generateRefreshToken(user.id);

  // Set HttpOnly refresh cookie
  res.cookie('refreshToken', refreshToken, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
    maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
    path: '/'
  });

  res.json({
    success: true,
    user,
    token: accessToken,
    expiresIn: 3600 // 1 hour
  });
});

// Example refresh endpoint
app.post('/auth/refresh', async (req, res) => {
  const { refreshToken } = req.cookies;

  if (!refreshToken || !isValidRefreshToken(refreshToken)) {
    return res.status(401).json({ error: 'Invalid refresh token' });
  }

  const userId = extractUserIdFromToken(refreshToken);
  const newAccessToken = generateAccessToken(userId);

  res.json({
    token: newAccessToken,
    expiresIn: 3600
  });
});

Client Implementation

Application Bootstrap

Restore user sessions on application startup:

typescript
import { Altus4SDK } from '@altus4/sdk';

const altus4 = new Altus4SDK({
  baseURL: '/api/v1', // Use relative URL for same-origin requests
});

async function initializeApp() {
  try {
    // Try to restore session from HttpOnly cookie
    const restored = await altus4.auth.restoreSession();

    if (restored && altus4.isAuthenticated()) {
      // User is authenticated, redirect to app
      console.log('Session restored successfully');
      showDashboard();
    } else {
      // No valid session, show login
      console.log('No session found, redirecting to login');
      showLoginForm();
    }
  } catch (error) {
    console.error('Failed to initialize auth:', error);
    showLoginForm();
  }
}

// Enhanced bootstrap with user profile
async function bootstrapApp() {
  try {
    // Initialize auth state and fetch user profile
    const initialized = await altus4.auth.initializeAuthState();

    if (initialized && altus4.isAuthenticated()) {
      const user = await altus4.getCurrentUser();
      if (user.success) {
        console.log('Welcome back,', user.data?.name);
        setCurrentUser(user.data);
      }
      showDashboard();
    } else {
      showLoginForm();
    }
  } catch (error) {
    console.error('App initialization failed:', error);
    showLoginForm();
  }
}

// Start app
bootstrapApp();

Login Flow

typescript
async function handleLogin(email: string, password: string) {
  try {
    const result = await altus4.login(email, password);

    if (result.success) {
      // Login successful, refresh cookie is automatically set by backend
      console.log('Login successful:', result.user);

      // Redirect to dashboard
      window.location.href = '/dashboard';
    } else {
      // Handle login errors
      showError(result.error?.message || 'Login failed');
    }
  } catch (error) {
    console.error('Login error:', error);
    showError('Network error, please try again');
  }
}

// React example
const LoginForm = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);

    try {
      const result = await altus4.login(email, password);

      if (result.success) {
        // Redirect handled by route guard
        history.push('/dashboard');
      } else {
        setError(result.error?.message);
      }
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* Form fields */}
    </form>
  );
};

Logout Flow

typescript
async function handleLogout() {
  try {
    const result = await altus4.logout();

    if (result.success) {
      // Refresh cookie cleared by backend
      console.log('Logout successful');

      // Redirect to login
      window.location.href = '/login';
    } else {
      console.error('Logout failed:', result.error);
      // Force redirect anyway for security
      window.location.href = '/login';
    }
  } catch (error) {
    console.error('Logout error:', error);
    // Force redirect for security
    window.location.href = '/login';
  }
}

Automatic Token Refresh

The SDK automatically handles token refresh on API requests:

typescript
// This request will automatically refresh the token if needed
try {
  const dashboard = await altus4.analytics.getDashboardAnalytics();
  // Token was valid or automatically refreshed
  displayDashboard(dashboard.data);
} catch (error) {
  if (error.response?.status === 401) {
    // Refresh failed, redirect to login
    window.location.href = '/login';
  } else {
    // Handle other errors
    showError('Failed to load dashboard');
  }
}

JWT Token Management

For applications that need manual token management or don't support cookies.

Manual Token Storage

typescript
// Store tokens manually
class TokenManager {
  private accessToken: string | null = null;
  private refreshToken: string | null = null;
  private expiryTime: number | null = null;

  setTokens(accessToken: string, refreshToken: string, expiresIn: number) {
    this.accessToken = accessToken;
    this.refreshToken = refreshToken;
    this.expiryTime = Date.now() + (expiresIn * 1000);

    // Store in secure storage
    this.saveToStorage();
  }

  isTokenExpired(): boolean {
    return this.expiryTime ? Date.now() >= this.expiryTime : true;
  }

  private saveToStorage() {
    // Use sessionStorage for enhanced security
    sessionStorage.setItem('accessToken', this.accessToken || '');
    sessionStorage.setItem('refreshToken', this.refreshToken || '');
    sessionStorage.setItem('expiryTime', this.expiryTime?.toString() || '');
  }

  private loadFromStorage() {
    this.accessToken = sessionStorage.getItem('accessToken');
    this.refreshToken = sessionStorage.getItem('refreshToken');
    const expiry = sessionStorage.getItem('expiryTime');
    this.expiryTime = expiry ? parseInt(expiry) : null;
  }
}

// Initialize SDK with manual token management
const tokenManager = new TokenManager();
const altus4 = new Altus4SDK({
  baseURL: '/api/v1',
});

// Login and store tokens
const result = await altus4.login(email, password);
if (result.success && result.token) {
  tokenManager.setTokens(result.token, result.refreshToken, result.expiresIn);
  altus4.setToken(result.token, result.expiresIn);
}

Token Refresh Implementation

typescript
class AuthManager {
  constructor(private sdk: Altus4SDK) {}

  async ensureValidToken(): Promise<boolean> {
    if (!this.sdk.isAuthenticated()) {
      return false;
    }

    try {
      // Try to refresh token if needed
      const refreshed = await this.sdk.refreshTokenIfNeeded();
      return refreshed;
    } catch (error) {
      console.error('Token refresh failed:', error);
      return false;
    }
  }

  async makeAuthenticatedRequest<T>(
    requestFn: () => Promise<T>
  ): Promise<T> {
    // Ensure token is valid before request
    const tokenValid = await this.ensureValidToken();

    if (!tokenValid) {
      throw new Error('Authentication required');
    }

    try {
      return await requestFn();
    } catch (error) {
      // Handle 401 responses
      if (error.response?.status === 401) {
        // Try one more time with fresh token
        const refreshed = await this.ensureValidToken();
        if (refreshed) {
          return await requestFn();
        }
        throw new Error('Authentication expired');
      }
      throw error;
    }
  }
}

// Usage
const authManager = new AuthManager(altus4);

const dashboard = await authManager.makeAuthenticatedRequest(
  () => altus4.analytics.getDashboardAnalytics()
);

API Key Authentication

For server-side applications and service-to-service communication.

Basic API Key Usage

typescript
// Initialize with API key
const altus4 = new Altus4SDK({
  baseURL: 'https://api.altus4.com/api/v1',
  apiKey: process.env.ALTUS4_API_KEY,
});

// All requests automatically include the API key
const results = await altus4.analytics.getDashboardAnalytics();

API Key Management

typescript
// Create initial API key after user authentication
async function setupAPIKey() {
  // First, authenticate with user credentials
  const loginResult = await altus4.login(email, password);

  if (loginResult.success) {
    // Create API key for service usage
    const apiKeyResult = await altus4.apiKeys.createApiKey({
      name: 'Production Service Key',
      environment: 'live',
      permissions: ['search', 'analytics'],
      rateLimitTier: 'pro',
    });

    if (apiKeyResult.success) {
      // Store API key securely
      process.env.ALTUS4_API_KEY = apiKeyResult.data?.key;

      // Reinitialize SDK with API key
      const serviceSDK = new Altus4SDK({
        baseURL: '/api/v1',
        apiKey: apiKeyResult.data?.key,
      });

      return serviceSDK;
    }
  }
}

Environment-Based Configuration

typescript
// Different API keys for different environments
class APIKeyManager {
  private getApiKey(): string {
    switch (process.env.NODE_ENV) {
      case 'development':
        return process.env.ALTUS4_DEV_API_KEY || '';
      case 'staging':
        return process.env.ALTUS4_STAGING_API_KEY || '';
      case 'production':
        return process.env.ALTUS4_PROD_API_KEY || '';
      default:
        return process.env.ALTUS4_API_KEY || '';
    }
  }

  createSDK(): Altus4SDK {
    return new Altus4SDK({
      baseURL: process.env.ALTUS4_API_URL,
      apiKey: this.getApiKey(),
      timeout: 30000,
    });
  }
}

// Usage
const keyManager = new APIKeyManager();
const altus4 = keyManager.createSDK();

Application Patterns

Single Page Application (SPA)

React/Vue/Angular applications with cookie-based authentication:

typescript
// App.tsx
import { Altus4SDK } from '@altus4/sdk';
import { createContext, useContext, useEffect, useState } from 'react';

interface AuthContextType {
  user: User | null;
  login: (email: string, password: string) => Promise<boolean>;
  logout: () => Promise<void>;
  loading: boolean;
}

const AuthContext = createContext<AuthContextType | null>(null);

export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  const altus4 = useMemo(() => new Altus4SDK({
    baseURL: '/api/v1',
  }), []);

  useEffect(() => {
    initializeAuth();
  }, []);

  const initializeAuth = async () => {
    try {
      setLoading(true);

      // Try to restore session
      const restored = await altus4.auth.restoreSession();

      if (restored && altus4.isAuthenticated()) {
        const userResult = await altus4.getCurrentUser();
        if (userResult.success) {
          setUser(userResult.data || null);
        }
      }
    } catch (error) {
      console.error('Auth initialization failed:', error);
    } finally {
      setLoading(false);
    }
  };

  const login = async (email: string, password: string): Promise<boolean> => {
    try {
      const result = await altus4.login(email, password);

      if (result.success) {
        setUser(result.user || null);
        return true;
      }
      return false;
    } catch (error) {
      console.error('Login failed:', error);
      return false;
    }
  };

  const logout = async () => {
    try {
      await altus4.logout();
    } catch (error) {
      console.error('Logout failed:', error);
    } finally {
      setUser(null);
    }
  };

  return (
    <AuthContext.Provider value={{ user, login, logout, loading }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
};

Server-Side Application

Express.js server with API key authentication:

typescript
// server.ts
import express from 'express';
import { Altus4SDK } from '@altus4/sdk';

const app = express();

// Initialize SDK with API key
const altus4 = new Altus4SDK({
  baseURL: process.env.ALTUS4_API_URL,
  apiKey: process.env.ALTUS4_API_KEY,
});

// Middleware to provide SDK to routes
app.use((req, res, next) => {
  req.altus4 = altus4;
  next();
});

// Search endpoint
app.get('/api/search', async (req, res) => {
  try {
    const { query, databases } = req.query;

    const searchResult = await req.altus4.database.searchDatabase({
      query: query as string,
      databases: databases as string[],
      limit: 20,
    });

    if (searchResult.success) {
      res.json(searchResult.data);
    } else {
      res.status(400).json({ error: searchResult.error?.message });
    }
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Analytics endpoint
app.get('/api/analytics/dashboard', async (req, res) => {
  try {
    const { period } = req.query;

    const analytics = await req.altus4.analytics.getDashboardAnalytics({
      period: period as 'day' | 'week' | 'month' | 'year',
    });

    if (analytics.success) {
      res.json(analytics.data);
    } else {
      res.status(400).json({ error: analytics.error?.message });
    }
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Microservices Architecture

Service-specific SDK instances with different API keys:

typescript
// services/search-service.ts
import { Altus4SDK } from '@altus4/sdk';

export class SearchService {
  private altus4: Altus4SDK;

  constructor() {
    this.altus4 = new Altus4SDK({
      baseURL: process.env.ALTUS4_API_URL,
      apiKey: process.env.ALTUS4_SEARCH_API_KEY, // Search-specific key
      timeout: 10000,
    });
  }

  async searchAllDatabases(query: string): Promise<SearchResult[]> {
    const result = await this.altus4.database.searchDatabase({
      query,
      mode: 'semantic',
      limit: 50,
    });

    if (!result.success) {
      throw new Error(`Search failed: ${result.error?.message}`);
    }

    return result.data?.results || [];
  }

  async getSearchSuggestions(partial: string): Promise<string[]> {
    const result = await this.altus4.database.getSearchSuggestions({
      partial,
      limit: 10,
    });

    return result.success ? result.data?.suggestions || [] : [];
  }
}

// services/analytics-service.ts
export class AnalyticsService {
  private altus4: Altus4SDK;

  constructor() {
    this.altus4 = new Altus4SDK({
      baseURL: process.env.ALTUS4_API_URL,
      apiKey: process.env.ALTUS4_ANALYTICS_API_KEY, // Analytics-specific key
      timeout: 30000,
    });
  }

  async getDashboardData(period: string): Promise<DashboardData> {
    const result = await this.altus4.analytics.getDashboardAnalytics({
      period: period as any,
    });

    if (!result.success) {
      throw new Error(`Analytics failed: ${result.error?.message}`);
    }

    return result.data || {};
  }
}

Security Best Practices

Token Security

  1. Never Log Tokens: Ensure tokens never appear in logs or error messages
  2. Secure Storage: Use sessionStorage over localStorage, HttpOnly cookies when possible
  3. Token Rotation: Implement regular token refresh and rotation
  4. Scope Limitation: Use minimum required permissions for API keys
typescript
// Secure logging that filters sensitive data
class SecureLogger {
  static log(message: string, data?: any) {
    const sanitizedData = this.sanitizeData(data);
    console.log(message, sanitizedData);
  }

  private static sanitizeData(data: any): any {
    if (!data) return data;

    const sensitive = ['token', 'password', 'apiKey', 'refreshToken'];
    const sanitized = { ...data };

    sensitive.forEach(key => {
      if (sanitized[key]) {
        sanitized[key] = '[REDACTED]';
      }
    });

    return sanitized;
  }
}

// Usage
SecureLogger.log('Login attempt', { email: user.email, token: 'abc123' });
// Output: Login attempt { email: "user@example.com", token: "[REDACTED]" }

API Key Management

typescript
class SecureAPIKeyManager {
  private apiKey: string;

  constructor() {
    this.apiKey = this.loadAPIKey();
  }

  private loadAPIKey(): string {
    const key = process.env.ALTUS4_API_KEY;

    if (!key) {
      throw new Error('ALTUS4_API_KEY environment variable is required');
    }

    // Validate key format
    if (!key.startsWith('altus4_sk_')) {
      throw new Error('Invalid API key format');
    }

    return key;
  }

  getAPIKey(): string {
    return this.apiKey;
  }

  // Rotate API key
  async rotateAPIKey(): Promise<string> {
    const altus4 = new Altus4SDK({
      baseURL: process.env.ALTUS4_API_URL,
      apiKey: this.apiKey,
    });

    // Create new key
    const newKeyResult = await altus4.apiKeys.createApiKey({
      name: 'Rotated Production Key',
      environment: 'live',
      permissions: ['search', 'analytics'],
      rateLimitTier: 'pro',
    });

    if (newKeyResult.success) {
      // Revoke old key
      const oldKeyId = this.extractKeyId(this.apiKey);
      await altus4.apiKeys.revokeApiKey(oldKeyId);

      // Update stored key
      this.apiKey = newKeyResult.data?.key || '';
      return this.apiKey;
    }

    throw new Error('Key rotation failed');
  }

  private extractKeyId(key: string): string {
    // Extract key ID from API key for revocation
    return key.substring(10); // Remove 'altus4_sk_' prefix
  }
}

Request Security

typescript
// Request interceptor for security headers
class SecureClient extends Altus4SDK {
  constructor(config: ClientConfig) {
    super({
      ...config,
      headers: {
        'X-Requested-With': 'XMLHttpRequest',
        'X-Client-Version': '1.0.0',
        ...config.headers,
      },
    });
  }

  // Override to add request logging
  async makeRequest<T>(requestFn: () => Promise<T>): Promise<T> {
    const startTime = Date.now();

    try {
      const result = await requestFn();

      // Log successful requests (without sensitive data)
      SecureLogger.log('Request completed', {
        duration: Date.now() - startTime,
        timestamp: new Date().toISOString(),
      });

      return result;
    } catch (error) {
      // Log errors (without sensitive data)
      SecureLogger.log('Request failed', {
        error: error.message,
        duration: Date.now() - startTime,
      });

      throw error;
    }
  }
}

Environment-Specific Security

typescript
class EnvironmentConfig {
  static getSecurityConfig() {
    const isProduction = process.env.NODE_ENV === 'production';

    return {
      // HTTPS enforcement
      enforceHTTPS: isProduction,

      // Token settings
      tokenStorage: isProduction ? 'cookie' : 'sessionStorage',
      tokenExpiry: isProduction ? 3600 : 86400, // 1 hour vs 24 hours

      // Request settings
      timeout: isProduction ? 30000 : 60000,
      retryAttempts: isProduction ? 3 : 1,

      // Logging
      debugMode: !isProduction,
      logLevel: isProduction ? 'error' : 'debug',
    };
  }

  static createSDK(): Altus4SDK {
    const config = this.getSecurityConfig();

    return new Altus4SDK({
      baseURL: process.env.ALTUS4_API_URL,
      timeout: config.timeout,
      debug: config.debugMode,
      headers: {
        'X-Environment': process.env.NODE_ENV || 'development',
      },
    });
  }
}

Migration Guide

typescript
// OLD: Local storage authentication
class OldAuthManager {
  login(email: string, password: string) {
    // ... authenticate
    localStorage.setItem('token', token);
    localStorage.setItem('refreshToken', refreshToken);
  }

  getToken(): string | null {
    return localStorage.getItem('token');
  }
}

// NEW: Cookie-based authentication
class NewAuthManager {
  constructor(private sdk: Altus4SDK) {}

  async login(email: string, password: string) {
    const result = await this.sdk.login(email, password);
    // Tokens are automatically managed by SDK + cookies
    return result;
  }

  async initializeAuth() {
    // Restore session from cookies
    return await this.sdk.auth.restoreSession();
  }
}

Migration Steps

  1. Update Backend: Add cookie support to login/refresh endpoints
  2. Update Client: Replace manual token storage with SDK session management
  3. Test Migration: Ensure existing sessions continue to work
  4. Deploy Gradually: Use feature flags for gradual rollout
typescript
// Migration-safe authentication
class MigrationAuthManager {
  constructor(private sdk: Altus4SDK) {}

  async initializeAuth(): Promise<boolean> {
    try {
      // Try new cookie-based auth first
      const restored = await this.sdk.auth.restoreSession();
      if (restored) return true;

      // Fallback to localStorage token
      const oldToken = localStorage.getItem('token');
      if (oldToken) {
        this.sdk.setToken(oldToken);

        // Clear old storage
        localStorage.removeItem('token');
        localStorage.removeItem('refreshToken');

        return true;
      }

      return false;
    } catch (error) {
      console.error('Auth migration failed:', error);
      return false;
    }
  }
}

The Altus 4 SDK's authentication system provides enterprise-grade security with developer-friendly APIs. Choose the authentication strategy that best fits your application architecture, and leverage the SDK's automatic token management for secure, seamless user experiences.