API Key Authentication for MCP Servers
API keys are the simplest MCP auth model, and a legitimate choice for internal tools — but the naive env-var-list implementation has real gaps: no rotation without a redeploy, no per-key scoping, no hashing at rest.
API keys are the right choice for internal MCP tooling and low-blast-radius servers — they're simple to implement and simple for a small team to reason about. The naive implementation, though, has real gaps worth fixing before calling it production-ready.
The Naive Version (What Not to Stop At)
const validKeys = new Set(process.env.MCP_API_KEYS?.split(",") || []);
const apiKey = request.headers["x-api-key"];
if (!validKeys.has(apiKey)) {
throw new Error("Invalid API key");
}
This works, but has three real gaps: every key can do everything (no scoping), rotating a key means redeploying with a new environment variable, and keys are compared/stored as plaintext.
A More Production-Ready Pattern
import { createHash, timingSafeEqual } from "node:crypto";
interface ApiKeyRecord {
hashedKey: string;
scopes: string[];
revoked: boolean;
}
function hashKey(key: string): string {
return createHash("sha256").update(key).digest("hex");
}
async function validateApiKey(rawKey: string, requiredScope: string, keyStore: ApiKeyRecord[]) {
const hashed = hashKey(rawKey);
const record = keyStore.find(k =>
timingSafeEqual(Buffer.from(k.hashedKey), Buffer.from(hashed))
);
if (!record || record.revoked) throw new Error("Invalid or revoked API key");
if (!record.scopes.includes(requiredScope)) throw new Error("Insufficient scope");
return record;
}
Three real improvements over the naive version:
- Keys are hashed at rest — you store and compare a SHA-256 hash, never the raw key, so a database leak doesn't directly expose usable keys.
- Comparison uses
timingSafeEqualinstead of standard string/Set equality, avoiding timing-attack-based key guessing. - Keys carry scopes and a revoked flag stored in a real data store rather than an environment variable — meaning you can revoke or scope a specific key without redeploying the whole server.
Rotation Without Downtime
Support at least two active keys per client at any time: issue the new key, let the client switch over, then revoke the old one — rather than a hard cutover that breaks every client using the old key simultaneously. This is standard practice for any API key system, and MCP servers are no exception.
When API Keys Are the Wrong Choice
If your MCP server has real end users who need to individually authenticate and authorize specific actions (rather than one shared key covering an entire internal team), that's the signal to move to OAuth instead — API keys represent "this caller is authorized," not "this specific end user delegated this specific permission," which is a meaningfully different guarantee.
Join the Discussion
Discussion (0)
No comments yet. Be the first to share your thoughts!