Security Architecture

Why Token Provider?

Your organization’s API key never touches the browser for security reasons:

  • API Key Protection: Your Animus API key stays secure on your backend
  • User Authentication: You control who can access your AI services
  • Token Expiration: JWT tokens automatically expire for enhanced security
  • Audit Trail: Track usage through your own authentication system

Authentication Flow

Backend Implementation

Basic Token Provider Endpoint

Here’s how to implement a secure token provider endpoint in different backend frameworks:

// server.js
const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors());
app.use(express.json());

// Your Animus API key (keep this secure!)
const ANIMUS_API_KEY = process.env.ANIMUS_API_KEY;

app.post('/api/get-animus-token', async (req, res) => {
  try {
    // 1. Authenticate your user (implement your own logic)
    const userToken = req.headers.authorization;
    if (!userToken || !isValidUserToken(userToken)) {
      return res.status(401).json({ error: 'Unauthorized' });
    }

    // 2. Call Animus Auth Service
    const response = await fetch('https://api.animusai.co/auth/generate-token', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'apikey': ANIMUS_API_KEY
      }
    });

    if (!response.ok) {
      throw new Error(`Animus auth failed: ${response.status}`);
    }

    const data = await response.json();

    // 3. Return only the JWT token
    res.json({
      accessToken: data.accessToken
    });

  } catch (error) {
    console.error('Token generation error:', error);
    res.status(500).json({ error: 'Failed to generate token' });
  }
});

// Your user authentication logic
function isValidUserToken(token) {
  // Implement your authentication logic here
  // This could check JWT tokens, session cookies, API keys, etc.
  return token === 'Bearer valid-user-token'; // Simplified example
}

app.listen(3001, () => {
  console.log('Token provider server running on port 3001');
});

Frontend Integration

Basic SDK Configuration

Once your backend is set up, configure the SDK to use your token provider:

import { AnimusClient } from 'animus-client';

const client = new AnimusClient({
  tokenProviderUrl: 'https://your-backend.com/api/get-animus-token',
  chat: {
    model: 'vivian-llama3.1-70b-1.0-fp8',
    systemMessage: 'You are a helpful assistant.'
  }
});

// The SDK will automatically call your token provider when needed
client.chat.send("Hello!"); // Event-driven - listen for messageComplete event

User Authentication Integration

The SDK doesn’t have built-in user authentication features. Instead, you handle user authentication in your backend token provider endpoint. Here’s the recommended pattern:

// Your backend handles user authentication
// The SDK only needs the token provider URL
const client = new AnimusClient({
  tokenProviderUrl: 'https://your-backend.com/api/get-animus-token',
  chat: {
    model: 'vivian-llama3.1-70b-1.0-fp8',
    systemMessage: 'You are a helpful assistant.'
  }
});

// Your backend endpoint should:
// 1. Validate the user's session/token
// 2. Only return Animus tokens for authenticated users
// 3. Return 401 for unauthenticated requests

Managing User Sessions

Since the SDK doesn’t handle user authentication directly, you’ll need to manage user sessions at the application level:

class AuthenticatedApp {
  private client: AnimusClient | null = null;
  private userSession: string | null = null;

  constructor() {
    // Initialize without client
  }

  async login(userCredentials: any) {
    // Handle user login with your auth system
    this.userSession = await this.authenticateUser(userCredentials);
    
    // Create SDK client after successful login
    // Your backend will validate the session when SDK requests tokens
    this.client = new AnimusClient({
      tokenProviderUrl: 'https://your-backend.com/api/get-animus-token',
      chat: {
        model: 'vivian-llama3.1-70b-1.0-fp8',
        systemMessage: 'You are a helpful assistant.'
      }
    });
  }

  async sendMessage(message: string) {
    if (!this.client || !this.userSession) {
      throw new Error('User not authenticated');
    }
    // Use event-driven approach
    this.client.chat.send(message);
    // Listen for messageComplete event to get response
  }

  logout() {
    this.userSession = null;
    if (this.client) {
      this.client.clearAuthToken(); // Clear cached Animus token
    }
    this.client = null;
  }

  private async authenticateUser(credentials: any): Promise<string> {
    // Your user authentication logic here
    // Return session token/ID
    return 'user-session-token';
  }
}

// Usage
const app = new AuthenticatedApp();

// User login
await app.login({ username: 'user', password: 'pass' });
const response = await app.sendMessage("Hello!");

// User logout
app.logout();

Advanced Authentication

Custom Token Storage

Control where Animus tokens are stored in the browser:

const client = new AnimusClient({
  tokenProviderUrl: 'https://your-backend.com/api/get-animus-token',
  tokenStorage: 'localStorage', // Persists across browser sessions
  chat: {
    model: 'vivian-llama3.1-70b-1.0-fp8',
    systemMessage: 'You are a helpful assistant.'
  }
});

Token Refresh Handling

Token refresh happens automatically, but you can handle authentication errors:

import { AuthenticationError } from 'animus-client';

try {
  // Event-driven approach - no await needed
  client.chat.send("Hello!");
  
  // Or use completions() for direct API calls
  const response = await client.chat.completions({
    messages: [{ role: 'user', content: 'Hello!' }]
  });
} catch (error) {
  if (error instanceof AuthenticationError) {
    // Token might be expired or invalid
    // Your backend token provider may have rejected the request
    console.log('Authentication failed, redirecting to login...');
    redirectToLogin();
  }
}

Manual Token Management

Clear the stored Animus token when needed:

// Clear stored Animus token (forces refresh on next request)
client.clearAuthToken();

// Next request will call your token provider again
client.chat.send("This will fetch a new token");

Note: The SDK doesn’t provide methods to check if a token exists or inspect token details. Token management is handled automatically, and you only need to clear tokens when necessary (e.g., on user logout).

CORS Configuration

Configure CORS properly for production:

// Production CORS setup
app.use(cors({
  origin: ['https://your-app.com', 'https://www.your-app.com'],
  credentials: true,
  methods: ['POST'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

Troubleshooting

Common Issues

Token Provider URL Not Found (404)

// Ensure your backend endpoint is correct
const client = new AnimusClient({
  tokenProviderUrl: 'https://your-backend.com/api/get-animus-token', // Check this URL
});

CORS Errors

// Backend: Enable CORS for your frontend domain
app.use(cors({
  origin: 'https://your-frontend-domain.com'
}));

Authentication Loops

// Check that your backend returns the correct format
// Expected response: { "accessToken": "jwt_token_here" }

Debug Mode

Enable debug logging to troubleshoot authentication issues:

// Add debug logging to your token provider
console.log('Token request received:', {
  headers: req.headers,
  body: req.body,
  timestamp: new Date().toISOString()
});

Next Steps