Lesson 24 / 25

API Keys and Service Accounts

Credentials for software, not people.

Managing machine credentials

API keys identify a calling application. Generate them with a CSPRNG, show them once, and store only a hash (a fast hash is acceptable because keys are long and random). A visible prefix (for example sk_live_ style) helps users and secret scanners recognise leaked keys. Scope each key to specific permissions and, where possible, IPs or environments; support multiple active keys so customers can rotate without downtime; record last_used_at and revoke unused keys. Service accounts are non-human identities inside your platform; prefer short-lived credentials issued from workload identity (cloud IAM roles, Kubernetes service account tokens, OIDC federation from CI) over long-lived static secrets. Keep secrets in a secrets manager, never in source control.

Issuing and checking API keys

Node.js; the prefix and id let you look up the key without scanning hashes.

export async function createApiKey(accountId: string, scopes: string[]) {
  const id = crypto.randomBytes(6).toString('hex');
  const secret = crypto.randomBytes(32).toString('base64url');
  await db.apiKeys.insert({ id, accountId, scopes, secretHash: sha256(secret), createdAt: new Date() });
  return `ak_${id}_${secret}`; // shown to the user once, never stored in plain text
}

export async function apiKeyAuth(req: Request, res: Response, next: NextFunction) {
  const m = /^ak_([0-9a-f]{12})_([A-Za-z0-9_-]+)$/.exec(req.get('x-api-key') ?? '');
  const key = m && (await db.apiKeys.findActive(m[1]));
  const ok = key && crypto.timingSafeEqual(Buffer.from(key.secretHash), Buffer.from(sha256(m![2])));
  if (!ok) return res.status(401).end();
  await db.apiKeys.touch(key.id);                   // last_used_at
  req.client = { accountId: key.accountId, scopes: key.scopes };
  next();
}

Turn on secret scanning

Distinctive key prefixes let platforms such as GitHub secret scanning detect leaked keys in repositories. Have a documented process to revoke and reissue quickly when that happens.

Quick check: How should a server store API keys it has issued?

  • Encrypted in the client's browser storage
  • In plain text so support can read them back
  • As hashes, showing the plaintext key to the user only once
  • Inside the JWT payload of every response
Answer

As hashes, showing the plaintext key to the user only once — A database leak should not reveal working keys.