Skip to main content

Authentication Methods

The TekstiAI Widget supports multiple authentication methods to fit different security requirements and architectures.

Overview

The TekstiAI Widget supports two authentication methods, depending on how your widget is configured:

  1. JWT Token - Direct authentication with a token (for widgets configured to use JWT)
  2. Transport Function - Custom function that gives you complete control over API requests (for widgets configured to use a proxy)
info

The authentication method you use depends on your widget's configuration in the TekstiAI dashboard. Check with your TekstiAI administrator to determine which method your widget requires.

Method 1: JWT Token Authentication

The simplest way to authenticate is by providing a JWT token directly to the widget.

When to Use JWT Authentication

Use JWT when:

  • You're building a prototype or demo
  • Your application already has JWT tokens available in the frontend
  • Security requirements are moderate
  • You want the simplest possible setup

Avoid JWT when:

  • Tokens contain sensitive information
  • You need custom request handling
  • You require additional headers or logging
  • You're building an enterprise application with strict security requirements

Implementation

window.TekstiAI('boot', {
container: 'tekstiai-widget',
widget_id: 'your-widget-id',
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' // Your JWT token
});

Token Requirements

Your JWT token should:

  • Be valid and not expired
  • Have appropriate permissions for the widget
  • Be obtained securely from your authentication system

Security Considerations

⚠️ Important Security Notes:

  • JWT tokens in the frontend are visible in browser dev tools
  • Never include highly sensitive information in tokens
  • Implement short expiration times
  • Use HTTPS in production to prevent token interception
  • Consider token refresh mechanisms for long-lived sessions

Method 2: Transport Function

The transport function gives you complete control over how API requests are made, allowing you to implement custom authentication, logging, retry logic, and more.

When to Use Transport Function

Use Transport Function when:

  • You need maximum security
  • You want to proxy requests through your backend
  • You need custom headers, logging, or error handling
  • You're implementing client credentials flow
  • You need to integrate with existing infrastructure
  • You require rate limiting or request transformation

Implementation

window.TekstiAI('boot', {
container: 'tekstiai-widget',
widget_id: 'your-widget-id',

transport_function: async (endpoint, options) => {
// Your custom request handling logic here
const response = await fetch(endpoint, options);
return response;
}
});

Transport Function Signature

The transport function receives two parameters:

type TransportFunction = (
endpoint: string,
options: {
method: string
headers?: Record<string, string>
body?: any
signal?: AbortSignal
}
) => Promise<Response>

Parameters:

  • endpoint - The full API URL to call
  • options - Standard fetch options (method, headers, body, signal)

Returns:

  • A Promise<Response> - Standard Fetch API Response object

Advanced Transport Function Examples

Example 1: Adding Custom Headers

transport_function: async (endpoint, options) => {
// Add custom headers to every request
const customHeaders = {
...options.headers,
'X-User-ID': getCurrentUserId(),
'X-Session-ID': getSessionId(),
'X-Request-Timestamp': new Date().toISOString()
};

const response = await fetch(endpoint, {
...options,
headers: customHeaders
});

return response;
}

Example 2: Backend Proxy Pattern

The most secure approach - proxy all requests through your backend:

transport_function: async (endpoint, options) => {
// Call your backend instead of TekstiAI directly
const response = await fetch('/api/tekstiai-proxy', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getYourBackendToken()}`,
},
body: JSON.stringify({
target_endpoint: endpoint,
target_method: options.method,
target_headers: options.headers,
target_body: options.body
}),
signal: options.signal
});

return response;
}

Backend Implementation (Node.js/Express Example)

// Backend endpoint: /api/tekstiai-proxy
app.post('/api/tekstiai-proxy', async (req, res) => {
try {
// 1. Authenticate the user
const user = await authenticateUser(req);
if (!user) {
return res.status(401).json({ error: 'Unauthorized' });
}

// 2. Get TekstiAI credentials securely from environment
const clientId = process.env.TEKSTIAI_CLIENT_ID;
const clientSecret = process.env.TEKSTIAI_CLIENT_SECRET;

// 3. Get TekstiAI token using client credentials
const tekstiaiToken = await getTekstiAIToken(clientId, clientSecret);

// 4. Forward the request to TekstiAI
const { target_endpoint, target_method, target_body } = req.body;

const response = await fetch(target_endpoint, {
method: target_method,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${tekstiaiToken}`
},
body: target_body ? JSON.stringify(target_body) : undefined
});

// 5. Return the response
const data = await response.json();
res.status(response.status).json(data);

} catch (error) {
console.error('Proxy error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});

Example 3: Request Logging and Monitoring

transport_function: async (endpoint, options) => {
const requestId = generateRequestId();
const startTime = Date.now();

// Log request
console.log(`[${requestId}] Request to ${endpoint}`, {
method: options.method,
timestamp: new Date().toISOString()
});

try {
const response = await fetch(endpoint, options);

// Log response
const duration = Date.now() - startTime;
console.log(`[${requestId}] Response ${response.status}`, {
duration: `${duration}ms`,
ok: response.ok
});

// Send metrics to your monitoring system
sendMetrics({
endpoint,
method: options.method,
status: response.status,
duration
});

return response;

} catch (error) {
// Log error
console.error(`[${requestId}] Request failed`, error);

// Send error to error tracking
reportError(error, { requestId, endpoint });

throw error;
}
}

Example 4: Retry Logic with Exponential Backoff

transport_function: async (endpoint, options) => {
const maxRetries = 3;
let lastError;

for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(endpoint, options);

// Retry on 5xx errors
if (response.status >= 500 && attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}

return response;

} catch (error) {
lastError = error;
if (attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}

throw lastError || new Error('Max retries exceeded');
}

Example 5: Rate Limiting

// Simple in-memory rate limiter
class RateLimiter {
constructor(maxRequests, windowMs) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
this.requests = [];
}

async acquire() {
const now = Date.now();
this.requests = this.requests.filter(time => now - time < this.windowMs);

if (this.requests.length >= this.maxRequests) {
const oldestRequest = this.requests[0];
const waitTime = this.windowMs - (now - oldestRequest);
await new Promise(resolve => setTimeout(resolve, waitTime));
return this.acquire();
}

this.requests.push(now);
}
}

const rateLimiter = new RateLimiter(10, 60000); // 10 requests per minute

window.TekstiAI('boot', {
// ... other config
transport_function: async (endpoint, options) => {
// Wait for rate limit
await rateLimiter.acquire();

const response = await fetch(endpoint, options);
return response;
}
});