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:
- JWT Token - Direct authentication with a token (for widgets configured to use JWT)
- Transport Function - Custom function that gives you complete control over API requests (for widgets configured to use a proxy)
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 calloptions- 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;
}
});