How Unified API Key Authentication Works in FreeLLMAPI: A Technical Deep Dive
FreeLLMAPI validates all requests to its /v1 proxy endpoints using a single unified API key that supports both OpenAI-style bearer tokens and Anthropic-style headers, verifying them through a timing-safe cryptographic comparison against a secret stored in SQLite.
FreeLLMAPI is an open-source LLM aggregation proxy that standardizes access to multiple providers behind a single interface. The unified API key authentication system serves as the gatekeeper for all public endpoints, ensuring that only authorized clients can access the underlying model APIs. This article examines the implementation details found in the tashfeenahmed/freellmapi repository, breaking down exactly how the server extracts, retrieves, and validates credentials without exposing provider secrets.
The Three-Step Authentication Flow
The authentication system consists of three tightly-coupled components that execute sequentially for every incoming request to protected routes like /v1/chat/completions or /v1/models.
Step 1: Extracting the Bearer Token
When a request reaches the proxy router, the extractApiToken function in server/src/routes/proxy.ts (lines 52-66) inspects the headers to locate the credential. The implementation supports two common authentication patterns used by major LLM providers.
export function extractApiToken(req: Request): string | undefined {
const bearer = req.headers.authorization?.replace(/^Bearer\s+/i, '').trim();
if (bearer) return bearer;
const apiKeyHeader = req.headers['x-api-key'];
const xApiKey = Array.isArray(apiKeyHeader) ? apiKeyHeader[0] : apiKeyHeader;
return xApiKey?.trim() || undefined;
}
This function checks for an Authorization header with a Bearer prefix, falling back to the x-api-key header used by Anthropic-style clients. If neither header is present, the function returns undefined and the request fails authentication.
Step 2: Retrieving the Stored Unified Key
The unified API key persists in the SQLite database under the settings table with the key name unified_api_key. The getUnifiedApiKey function in server/src/db/index.ts (lines 80-84) retrieves this value:
export function getUnifiedApiKey(): string {
const db = getDb();
const row = db.prepare("SELECT value FROM settings WHERE key = 'unified_api_key'").get() as { value: string };
return row.value;
}
The key is generated once during database initialization via regenerateUnifiedKey(), which creates a random string prefixed with freellmapi-. This single key protects all proxy endpoints, eliminating the need for per-user API key management for client access.
Step 3: Timing-Safe Validation
To prevent timing attacks that could leak the key through response time analysis, FreeLLMAPI avoids direct string comparison (===). Instead, it uses timingSafeStringEqual in server/src/routes/proxy.ts (lines 38-50):
export function timingSafeStringEqual(provided: string, expected: string): boolean {
const key = Buffer.alloc(32);
const a = crypto.createHmac('sha256', key).update(provided).digest();
const b = crypto.createHmac('sha256', key).update(expected).digest();
return crypto.timingSafeEqual(a, b);
}
This function generates a hidden 256-bit HMAC key, hashes both the provided and expected tokens, and uses Node.js's crypto.timingSafeEqual to compare the digests in constant time. This approach ensures that attackers cannot determine how many characters of their guess are correct based on server response times.
Endpoint Enforcement
Every public proxy handler implements the authentication guard before processing requests. For example, the /models endpoint in server/src/routes/proxy.ts demonstrates the complete validation sequence:
proxyRouter.get('/models', (req, res) => {
const token = extractApiToken(req);
const unifiedKey = getUnifiedApiKey();
if (!token || !timingSafeStringEqual(token, unifiedKey)) {
res.status(401).json({ error: { message: 'Invalid API key', type: 'authentication_error' } });
return;
}
// ...continue building the model catalog...
});
The same validation pattern appears in all chat completion and model listing routes, ensuring consistent protection across the entire API surface.
Client Usage Examples
Clients can authenticate using either the OpenAI-style Authorization header or the Anthropic-style x-api-key header.
OpenAI-Style Bearer Token:
curl https://api.your-domain.com/v1/chat/completions \
-H "Authorization: Bearer YOUR_UNIFIED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-mini",
"messages": [{ "role": "user", "content": "Hello!" }]
}'
Anthropic-Style API Key:
curl https://api.your-domain.com/v1/chat/completions \
-H "x-api-key: YOUR_UNIFIED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-mini",
"messages": [{ "role": "user", "content": "Hello!" }]
}'
Both requests succeed only if the header value matches exactly the unified key stored in the SQLite database.
Key Management and Regeneration
Administrators can regenerate the unified API key using the regenerateUnifiedKey function from server/src/db/index.ts:
import { regenerateUnifiedKey } from './server/src/db/index.js';
const newKey = regenerateUnifiedKey();
console.log('New unified API key:', newKey);
Important: Regeneration immediately invalidates all existing client tokens. You must distribute the new key to all clients before they can access the API again.
Summary
- FreeLLMAPI uses a single unified API key stored in SQLite to authenticate all requests to its
/v1proxy endpoints. - Dual header support allows clients to authenticate via
Authorization: Bearerorx-api-keyheaders, maximizing compatibility with OpenAI and Anthropic SDKs. - Timing-safe comparison via
crypto.timingSafeEqualprevents side-channel attacks that could expose the key through response timing analysis. - Centralized enforcement in
server/src/routes/proxy.tsensures every public endpoint validates credentials before processing requests.
Frequently Asked Questions
Where is the unified API key stored in FreeLLMAPI?
The unified API key resides in the SQLite database's settings table under the key name unified_api_key. The getUnifiedApiKey() function in server/src/db/index.ts retrieves this value, and it is generated automatically during database initialization if not present.
How does FreeLLMAPI prevent timing attacks on API keys?
Instead of using direct string comparison (===), FreeLLMAPI hashes both the provided and expected keys using HMAC-SHA256 with a random 256-bit buffer, then compares the digests using Node.js's crypto.timingSafeEqual(). This ensures the comparison takes constant time regardless of how many characters match, preventing attackers from guessing the key byte-by-byte through timing analysis.
Can I use standard OpenAI client libraries with FreeLLMAPI?
Yes. FreeLLMAPI's extractApiToken function specifically supports the Authorization: Bearer <token> format used by OpenAI SDKs. You can configure standard OpenAI clients to point to your FreeLLMAPI instance and provide the unified API key as the bearer token.
What happens when I regenerate the unified API key?
Calling regenerateUnifiedKey() creates a new random key prefixed with freellmapi- and stores it in the database, immediately invalidating the previous key. All existing client requests will receive 401 Invalid API key responses until they are updated with the new credential.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →