How FreeLLMAPI Authenticates Client Applications: Two-Key Model Explained

FreeLLMAPI authenticates requests using either a unified API key for general access or per-profile keys that enforce custom system prompts, with both token types validated through constant-time comparison and SHA-256 hash lookups.

FreeLLMAPI implements a dual-credential authentication system designed for flexible LLM inference access. Whether you need a single master key for internal tools or scoped keys that automatically inject system prompts for external clients, the authentication flow is handled by the resolveAuth function in server/src/lib/system-prompt.ts.

Unified API Key: Global Access Without System Prompt Enforcement

The unified API key serves as the server's master credential. It's generated once during initialization and stored in the database's settings table.

Key characteristics:

  • Storage location: server/src/db/index.ts — the getUnifiedApiKey() function retrieves the plaintext value from the settings table under key unified_api_key
  • Security model: Full inference access, but no automatic system prompt injection
  • Request format: Use either Authorization: Bearer <key> or x-api-key: <key> header

The unified key validation uses constant-time string comparison to prevent timing attacks:

if (timingSafeStringEqual(token, getUnifiedApiKey())) {
  return { kind: 'unified', systemPrompt: null };
}

Client Profile Keys: Scoped Access with Enforced System Prompts

For multi-tenant scenarios, client profile keys provide isolated access with mandatory prompt injection.

How Profile Keys Work

Aspect Implementation
Prefix sk-cp- (defined as CLIENT_PROFILE_KEY_PREFIX)
Generation mintClientProfileKey() in server/src/routes/client-profiles.ts
Storage SHA-256 hash in token_hash column; plaintext never persisted
Lookup Hash comparison against client_profiles table
Enforcement System prompt prepended to every inference request

The Authentication Resolution Flow

The resolveAuth function in server/src/lib/system-prompt.ts handles both credential types:

export function resolveAuth(token: string | undefined): ResolvedAuth | null {
  if (!token) return null;
  
  // Check unified key first
  if (timingSafeStringEqual(token, getUnifiedApiKey())) {
    return { kind: 'unified', systemPrompt: null };
  }
  
  // Must be a client profile key
  if (!token.startsWith(CLIENT_PROFILE_KEY_PREFIX)) return null;
  
  const row = getDb()
    .prepare('SELECT id, name, system_prompt, enabled FROM client_profiles WHERE token_hash = ?')
    .get(hashClientProfileKey(token)) as ProfileRow | undefined;
    
  if (!row || !row.enabled) return null;  // Disabled = unknown (security through obscurity)
  
  const prompt = row.system_prompt?.trim().length ? row.system_prompt : null;
  return { 
    kind: 'profile', 
    profileId: row.id, 
    name: row.name, 
    systemPrompt: prompt 
  };
}

Security Design: Disabled Profiles Are Indistinguishable from Invalid Keys

Notice that !row || !row.enabled returns null in both cases. This prevents clients from fingerprinting whether a key was revoked versus never existed.

Practical Code Examples

Authenticate with the Unified Key

curl -X POST https://api.freellmapi.com/v1/chat/completions \
  -H "Authorization: Bearer $(cat ~/.freellmapi/unified.key)" \
  -H "Content-Type: application/json" \
  -d '{
        "model": "gpt-4o-mini",
        "messages": [{ "role": "user", "content": "Hello!" }]
      }'

Authenticate with a Client Profile Key (System Prompt Enforced)

curl -X POST https://api.freellmapi.com/v1/chat/completions \
  -H "x-api-key: sk-cp-0123abcd4567efgh8901ijkl" \
  -H "Content-Type: application/json" \
  -d '{
        "model": "gpt-4o-mini",
        "messages": [{ "role": "user", "content": "Explain quantum tunneling." }]
      }'

Create a New Client Profile (Dashboard Session Required)

curl -X POST https://api.freellmapi.com/api/client-profiles \
  -H "Cookie: session=YOUR_DASHBOARD_SESSION" \
  -H "Content-Type: application/json" \
  -d '{"name":"MyBot","systemPrompt":"You are a helpful assistant that never discloses confidential info."}'

Response contains the plaintext key exactly once:

{
  "id": 3,
  "name": "MyBot",
  "maskedKey": "[encrypted]",
  "systemPrompt": "You are a helpful assistant ...",
  "enabled": true,
  "createdAt": "2024-10-01T12:34:56Z",
  "updatedAt": "2024-10-01T12:34:56Z",
  "key": "sk-cp-7f9e1c8d5b3a4e6f..."
}

Rotate a Compromised Profile Key

curl -X POST https://api.freellmapi.com/api/client-profiles/3/rotate \
  -H "Cookie: session=YOUR_DASHBOARD_SESSION"

This invalidates the old hash and generates a new sk-cp- prefix key.

Key Source Files for FreeLLMAPI Authentication

File Responsibility
server/src/lib/system-prompt.ts Core resolveAuth logic, hashClientProfileKey(), prefix constants
server/src/db/index.ts getUnifiedApiKey() database accessor
server/src/routes/client-profiles.ts Profile CRUD, key generation (mintClientProfileKey), encrypted display
Middleware (inference routes) requireInferenceAuth wraps resolveAuth for all /v1/* endpoints

Summary

  • FreeLLMAPI authentication supports two credential types: a unified key for simple access and profile-scoped keys with prompt enforcement
  • Constant-time comparison (timingSafeStringEqual) prevents timing attacks against the unified key
  • SHA-256 hashing ensures plaintext profile keys never touch persistent storage — only hashes do
  • System prompt injection happens server-side via prependSystemPrompt, making override impossible
  • Status hiding: Disabled profiles return identical errors to non-existent keys, preventing information leakage

Frequently Asked Questions

What happens if I send a request without any authentication header?

The resolveAuth function receives undefined and returns null, causing the middleware to reject the request with a 401 Unauthorized response. FreeLLMAPI requires either Authorization: Bearer <token> or x-api-key: <token> on all inference endpoints.

Can I use the same header format for both unified and profile keys?

Yes. Both credential types accept Authorization: Bearer <token> or x-api-key: <token>. The server detects the key type by checking for the sk-cp- prefix after the unified key comparison fails.

How does FreeLLMAPI prevent clients from bypassing enforced system prompts?

The system prompt retrieved during resolveAuth is prepended to the message array in prependSystemPrompt before forwarding to the LLM provider. Since this happens server-side after authentication, the caller has no mechanism to inspect, modify, or remove the injected prompt.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →