Context Enterprise APIs use multiple layers of authentication to ensure secure access to Context Engine and Background Agent services. This guide covers all authentication methods and best practices for enterprise deployments.

Authentication Overview

Context Enterprise supports three primary authentication methods:

API Keys

Long-lived credentials for server-to-server communication

JWT Tokens

Short-lived tokens for secure API access with automatic expiration

Enterprise SSO

Integration with your existing identity provider (Okta, Azure AD, SAML)

API Key Authentication

Obtaining Enterprise API Keys

Enterprise API keys are provided during your onboarding process. Contact enterprise@context.ai to receive your credentials.
# Your enterprise credentials (provided by Context team)
CONTEXT_ENTERPRISE_ORG_ID="org_abc123def456"
CONTEXT_API_KEY="ctx_ent_live_sk_1234567890abcdef"
CONTEXT_API_SECRET="ctx_ent_secret_abcdef1234567890"

API Key Usage

API keys are used for initial authentication and JWT token generation:
// Using API keys for authentication
const headers = {
  'X-API-Key': process.env.CONTEXT_API_KEY,
  'Content-Type': 'application/json'
};

const response = await fetch('https://api.context.ai/v1/auth/enterprise/token', {
  method: 'POST',
  headers: headers,
  body: JSON.stringify({
    grant_type: 'client_credentials',
    client_id: process.env.CONTEXT_ENTERPRISE_ORG_ID,
    client_secret: process.env.CONTEXT_API_SECRET,
    scope: 'context-engine:read context-engine:write background-agents:read background-agents:write'
  })
});

JWT Token Authentication

Token Generation

JWT tokens provide secure, time-limited access to Context APIs:
class ContextAuthManager {
  constructor(apiKey, orgId, apiSecret) {
    this.apiKey = apiKey;
    this.orgId = orgId;
    this.apiSecret = apiSecret;
    this.token = null;
    this.tokenExpiry = null;
  }

  async getValidToken() {
    // Check if we have a valid token
    if (this.token && this.tokenExpiry > Date.now() + 300000) { // 5 min buffer
      return this.token;
    }

    // Generate new token
    const response = await fetch('https://api.context.ai/v1/auth/enterprise/token', {
      method: 'POST',
      headers: {
        'X-API-Key': this.apiKey,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        grant_type: 'client_credentials',
        client_id: this.orgId,
        client_secret: this.apiSecret,
        scope: 'context-engine:read context-engine:write background-agents:read background-agents:write'
      })
    });

    if (!response.ok) {
      throw new Error(`Authentication failed: ${response.statusText}`);
    }

    const data = await response.json();
    this.token = data.access_token;
    this.tokenExpiry = Date.now() + (data.expires_in * 1000);

    return this.token;
  }

  async makeAuthenticatedRequest(url, options = {}) {
    const token = await this.getValidToken();
    
    const headers = {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
      ...options.headers
    };

    return fetch(url, { ...options, headers });
  }
}

// Usage
const authManager = new ContextAuthManager(
  process.env.CONTEXT_API_KEY,
  process.env.CONTEXT_ENTERPRISE_ORG_ID,
  process.env.CONTEXT_API_SECRET
);

// Make authenticated API calls
const response = await authManager.makeAuthenticatedRequest(
  'https://api.context.ai/v1/context-engine/query',
  {
    method: 'POST',
    body: JSON.stringify({
      query: "Analyze market trends",
      sources: ["financial_reports"]
    })
  }
);

Token Validation

Validate JWT tokens before making API calls:
function validateToken(token) {
  try {
    // Decode JWT payload (without verification - for checking expiry)
    const payload = JSON.parse(atob(token.split('.')[1]));
    
    const now = Math.floor(Date.now() / 1000);
    const isExpired = payload.exp < now;
    
    return {
      isValid: !isExpired,
      expiresAt: payload.exp * 1000,
      timeToExpiry: (payload.exp - now) * 1000,
      organizationId: payload.org_id,
      scopes: payload.scope ? payload.scope.split(' ') : []
    };
  } catch (error) {
    return {
      isValid: false,
      error: 'Invalid token format'
    };
  }
}

// Usage
const tokenInfo = validateToken(jwtToken);
if (!tokenInfo.isValid) {
  console.log('Token needs refresh');
  // Refresh token logic here
}

Enterprise SSO Integration

Okta Integration

Configure Okta SSO for user authentication in enterprise deployments:

Okta Configuration

# sso-config.yml
sso:
  provider: "okta"
  configuration:
    domain: "yourcompany.okta.com"
    client_id: "${OKTA_CLIENT_ID}"
    client_secret: "${OKTA_CLIENT_SECRET}"
    redirect_uri: "https://api.yourcompany.com/auth/callback"
    scopes: ["openid", "profile", "email", "groups"]
    
  # Map Okta attributes to Context permissions
  attribute_mapping:
    email: "preferred_username"
    name: "name" 
    department: "department"
    context_role: "context_role"
    
  # Define role-based permissions
  role_permissions:
    "context_admin":
      - "context-engine:read"
      - "context-engine:write" 
      - "background-agents:read"
      - "background-agents:write"
      - "admin:users"
      - "admin:settings"
    "context_analyst":
      - "context-engine:read"
      - "background-agents:read"
      - "background-agents:write"
    "context_viewer":
      - "context-engine:read"
      - "background-agents:read"

Okta SSO Implementation

const OktaJwtVerifier = require('@okta/jwt-verifier');

class OktaSSO {
  constructor(oktaConfig) {
    this.oktaVerifier = new OktaJwtVerifier({
      issuer: `https://${oktaConfig.domain}/oauth2/default`,
      clientId: oktaConfig.client_id,
      assertClaims: {
        aud: 'api://context-enterprise',
        cid: oktaConfig.client_id
      }
    });
  }

  async verifyToken(oktaToken) {
    try {
      const jwt = await this.oktaVerifier.verifyAccessToken(oktaToken, 'api://context-enterprise');
      
      return {
        isValid: true,
        user: {
          id: jwt.claims.uid,
          email: jwt.claims.sub,
          name: jwt.claims.name,
          department: jwt.claims.department,
          groups: jwt.claims.groups || []
        }
      };
    } catch (error) {
      return {
        isValid: false,
        error: error.message
      };
    }
  }

  async generateContextToken(oktaUser) {
    // Convert Okta user to Context permissions
    const contextPermissions = this.mapUserToPermissions(oktaUser);
    
    // Generate Context JWT token
    const response = await fetch('https://api.context.ai/v1/auth/enterprise/user-token', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.CONTEXT_API_KEY,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        user_id: oktaUser.id,
        email: oktaUser.email,
        permissions: contextPermissions,
        organization_id: process.env.CONTEXT_ENTERPRISE_ORG_ID
      })
    });

    return await response.json();
  }

  mapUserToPermissions(user) {
    const rolePermissions = {
      'context_admin': ['context-engine:read', 'context-engine:write', 'background-agents:read', 'background-agents:write'],
      'context_analyst': ['context-engine:read', 'background-agents:read', 'background-agents:write'],
      'context_viewer': ['context-engine:read', 'background-agents:read']
    };

    // Extract role from user groups or department
    const userRole = user.groups.find(group => group.startsWith('context_')) || 'context_viewer';
    
    return rolePermissions[userRole] || rolePermissions['context_viewer'];
  }
}

// Express.js middleware for SSO authentication
app.use('/auth/okta/callback', async (req, res) => {
  const { code } = req.query;
  
  try {
    // Exchange authorization code for tokens
    const tokenResponse = await exchangeCodeForTokens(code);
    const oktaToken = tokenResponse.access_token;
    
    // Verify Okta token
    const oktaSSO = new OktaSSO(oktaConfig);
    const userInfo = await oktaSSO.verifyToken(oktaToken);
    
    if (userInfo.isValid) {
      // Generate Context token
      const contextToken = await oktaSSO.generateContextToken(userInfo.user);
      
      // Store in secure session
      req.session.contextToken = contextToken.access_token;
      req.session.user = userInfo.user;
      
      res.redirect('/dashboard');
    } else {
      res.status(401).json({ error: 'Invalid authentication' });
    }
  } catch (error) {
    console.error('SSO authentication error:', error);
    res.status(500).json({ error: 'Authentication failed' });
  }
});

Azure AD Integration

Configure Azure AD for enterprise SSO:
const { Client } = require('@azure/msal-node');

class AzureADSSO {
  constructor(azureConfig) {
    this.msalClient = new Client({
      auth: {
        clientId: azureConfig.client_id,
        authority: `https://login.microsoftonline.com/${azureConfig.tenant_id}`,
        clientSecret: azureConfig.client_secret
      }
    });
  }

  async verifyToken(azureToken) {
    try {
      // Verify Azure AD token
      const response = await this.msalClient.acquireTokenSilent({
        account: azureToken.account,
        scopes: ['User.Read']
      });

      return {
        isValid: true,
        user: {
          id: response.account.homeAccountId,
          email: response.account.username,
          name: response.account.name,
          tenantId: response.account.tenantId
        }
      };
    } catch (error) {
      return {
        isValid: false,
        error: error.message
      };
    }
  }
}

WebSocket Authentication

WebSocket Token Generation

For real-time features, generate WebSocket-specific tokens:
async function getWebSocketToken(apiToken) {
  const response = await fetch('https://api.context.ai/v1/auth/websocket-token', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      purposes: ['background_agent_updates', 'context_engine_stream'],
      expires_in: 3600 // 1 hour
    })
  });

  const data = await response.json();
  return data.websocket_token;
}

WebSocket Connection with Authentication

class AuthenticatedWebSocket {
  constructor(apiToken) {
    this.apiToken = apiToken;
    this.ws = null;
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
  }

  async connect() {
    try {
      const wsToken = await this.getWebSocketToken();
      
      this.ws = new WebSocket('wss://api.context.ai/v1/ws');
      
      this.ws.onopen = () => {
        console.log('WebSocket connected, authenticating...');
        this.authenticate(wsToken);
      };

      this.ws.onmessage = (event) => {
        this.handleMessage(JSON.parse(event.data));
      };

      this.ws.onclose = (event) => {
        console.log('WebSocket closed:', event.code, event.reason);
        this.handleReconnection();
      };

      this.ws.onerror = (error) => {
        console.error('WebSocket error:', error);
      };

    } catch (error) {
      console.error('Failed to establish WebSocket connection:', error);
    }
  }

  authenticate(wsToken) {
    this.send({
      type: 'authenticate',
      token: wsToken,
      organization_id: process.env.CONTEXT_ENTERPRISE_ORG_ID
    });
  }

  handleMessage(message) {
    switch (message.type) {
      case 'authenticated':
        console.log('WebSocket authenticated successfully');
        this.reconnectAttempts = 0;
        break;
        
      case 'authentication_failed':
        console.error('WebSocket authentication failed:', message.error);
        break;
        
      case 'task_update':
        console.log('Background task update:', message.data);
        break;
        
      default:
        console.log('Unknown message type:', message.type);
    }
  }

  async getWebSocketToken() {
    const response = await fetch('https://api.context.ai/v1/auth/websocket-token', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.apiToken}`,
        'Content-Type': 'application/json'
      }
    });

    const data = await response.json();
    return data.websocket_token;
  }

  handleReconnection() {
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      this.reconnectAttempts++;
      const delay = Math.pow(2, this.reconnectAttempts) * 1000; // Exponential backoff
      
      console.log(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
      
      setTimeout(() => {
        this.connect();
      }, delay);
    } else {
      console.error('Max reconnection attempts reached');
    }
  }

  send(message) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(message));
    } else {
      console.error('WebSocket is not connected');
    }
  }

  close() {
    if (this.ws) {
      this.ws.close();
    }
  }
}

// Usage
const authManager = new ContextAuthManager(/* credentials */);
const wsToken = await authManager.getValidToken();
const authenticatedWS = new AuthenticatedWebSocket(wsToken);
await authenticatedWS.connect();

Security Best Practices

Token Security

Storage

  • Store tokens in secure, encrypted storage
  • Never log or expose tokens in client-side code
  • Use environment variables for API keys
  • Implement proper token rotation

Transmission

  • Always use HTTPS/WSS for API communications
  • Implement certificate pinning where possible
  • Use TLS 1.3 or higher
  • Validate SSL certificates

Access Control

class ContextAccessControl {
  constructor(userPermissions) {
    this.permissions = userPermissions;
  }

  canAccess(resource, action) {
    const requiredPermission = `${resource}:${action}`;
    return this.permissions.includes(requiredPermission) || 
           this.permissions.includes('admin:all');
  }

  validateRequest(req, res, next) {
    const { resource, action } = req.params;
    
    if (this.canAccess(resource, action)) {
      next();
    } else {
      res.status(403).json({
        error: 'Insufficient permissions',
        required: `${resource}:${action}`,
        available: this.permissions
      });
    }
  }

  // Middleware factory
  requirePermission(resource, action) {
    return (req, res, next) => {
      if (this.canAccess(resource, action)) {
        next();
      } else {
        res.status(403).json({ error: 'Access denied' });
      }
    };
  }
}

// Usage with Express.js
app.use('/api/v1/context-engine', 
  authenticateUser,
  accessControl.requirePermission('context-engine', 'read')
);

Rate Limiting and Monitoring

const rateLimit = require('express-rate-limit');
const slowDown = require('express-slow-down');

// Configure rate limiting by authentication method
const createRateLimit = (windowMs, max) => rateLimit({
  windowMs: windowMs,
  max: max,
  message: {
    error: 'Too many requests',
    retryAfter: Math.ceil(windowMs / 1000)
  },
  standardHeaders: true,
  legacyHeaders: false,
  keyGenerator: (req) => {
    // Different limits for different auth methods
    if (req.headers['x-api-key']) {
      return `api-key:${req.headers['x-api-key'].substring(0, 10)}`;
    } else if (req.user) {
      return `user:${req.user.id}`;
    }
    return req.ip;
  }
});

// Apply different limits for different endpoints
app.use('/api/v1/auth', createRateLimit(15 * 60 * 1000, 5)); // 5 requests per 15 minutes
app.use('/api/v1/context-engine', createRateLimit(60 * 1000, 100)); // 100 requests per minute
app.use('/api/v1/background-agents', createRateLimit(60 * 1000, 50)); // 50 requests per minute

Error Handling

Authentication Errors

Handle various authentication error scenarios:
class AuthErrorHandler {
  static handleAuthError(error, req, res, next) {
    switch (error.code) {
      case 'INVALID_API_KEY':
        res.status(401).json({
          error: 'Invalid API key',
          code: 'INVALID_API_KEY',
          message: 'The provided API key is invalid or has been revoked'
        });
        break;

      case 'TOKEN_EXPIRED':
        res.status(401).json({
          error: 'Token expired',
          code: 'TOKEN_EXPIRED',
          message: 'JWT token has expired. Please obtain a new token.',
          expires_at: error.expires_at
        });
        break;

      case 'INSUFFICIENT_PERMISSIONS':
        res.status(403).json({
          error: 'Insufficient permissions',
          code: 'INSUFFICIENT_PERMISSIONS',
          message: 'Your account does not have permission to access this resource',
          required_permissions: error.required_permissions,
          user_permissions: error.user_permissions
        });
        break;

      case 'RATE_LIMIT_EXCEEDED':
        res.status(429).json({
          error: 'Rate limit exceeded',
          code: 'RATE_LIMIT_EXCEEDED',
          message: 'Too many requests. Please try again later.',
          retry_after: error.retry_after,
          limit: error.limit
        });
        break;

      default:
        res.status(500).json({
          error: 'Authentication error',
          code: 'UNKNOWN_AUTH_ERROR',
          message: 'An unexpected authentication error occurred'
        });
    }
  }
}

// Apply error handler
app.use(AuthErrorHandler.handleAuthError);

Testing Authentication

Unit Tests

const test = require('ava');
const sinon = require('sinon');
const ContextAuthManager = require('./auth-manager');

test('should generate valid JWT token', async t => {
  const authManager = new ContextAuthManager(
    'test-api-key',
    'test-org-id',
    'test-secret'
  );

  // Mock fetch
  const mockResponse = {
    ok: true,
    json: () => Promise.resolve({
      access_token: 'test-jwt-token',
      expires_in: 3600
    })
  };
  
  sinon.stub(global, 'fetch').resolves(mockResponse);

  const token = await authManager.getValidToken();
  
  t.is(token, 'test-jwt-token');
  t.true(authManager.tokenExpiry > Date.now());
  
  global.fetch.restore();
});

test('should handle authentication errors', async t => {
  const authManager = new ContextAuthManager('invalid-key', 'test-org', 'test-secret');
  
  const mockResponse = {
    ok: false,
    statusText: 'Unauthorized'
  };
  
  sinon.stub(global, 'fetch').resolves(mockResponse);

  await t.throwsAsync(
    () => authManager.getValidToken(),
    { message: /Authentication failed/ }
  );
  
  global.fetch.restore();
});

Integration Tests

test('end-to-end authentication flow', async t => {
  // Test full authentication flow
  const response = await fetch(`${process.env.TEST_API_URL}/auth/enterprise/token`, {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.TEST_API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      grant_type: 'client_credentials',
      client_id: process.env.TEST_ORG_ID,
      client_secret: process.env.TEST_API_SECRET,
      scope: 'context-engine:read'
    })
  });

  t.is(response.status, 200);
  
  const data = await response.json();
  t.truthy(data.access_token);
  t.is(typeof data.expires_in, 'number');
  
  // Test using the token
  const apiResponse = await fetch(`${process.env.TEST_API_URL}/context-engine/health`, {
    headers: {
      'Authorization': `Bearer ${data.access_token}`
    }
  });
  
  t.is(apiResponse.status, 200);
});

Next Steps