Enterprise authentication methods for Context APIs, including JWT tokens, SSO integration, and API key management
# 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"
// 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'
})
});
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"]
})
}
);
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
}
# 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"
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' });
}
});
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
};
}
}
}
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;
}
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();
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')
);
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
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);
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();
});
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);
});