09 — API Keys & Management: Issuing, Scoping, Rotating
API keys look like the simplest part of an API — a string, a header, done — which is exactly why the roadmap gives them their own major node. The model that finally clicked: an API key is a long-lived secret that identifies a caller, and "management" is the discipline that keeps that secret from becoming a liability — strong generation, scoping to the minimum permissions, hashing at rest, and rotation without service interruption. [1][2] The key itself is trivial; everything around it is the actual work.
The thread connecting the sub-nodes is that a key without management is a liability waiting to happen. An unscoped key that lives forever in plaintext and can't be rotated is the classic breach shape. The practices below exist to make a leaked key low-damage and recoverable.
What an API key is (and isn't)
An API key is a unique string a caller presents to identify themselves to an API — usually in a header (X-API-Key) or a query parameter [1]. Conceptually it's close to a long-lived token: the server receives it, looks it up (or hashes it and looks up the hash), and maps it to a caller and a set of permissions. The simplest way of thinking is "a username and password rolled into one string, for machines."
The distinctions I had to keep straight: a key identifies a calling program, not a human user — it's the right tool for server-to-server or script access, not for login flows (those want OAuth/OIDC from the previous posts). And keys are conventionally bearer secrets — anyone who holds the key can use it — which is why everything below is about limiting the blast radius if one leaks.
Key generation: entropy is the only metric
Generation is about producing keys that resist brute force [3]. Two properties matter: length and randomness (entropy). A 32+ byte cryptographically random key, base64- or hex-encoded, is the floor. Predictable keys (key-1, key-2, timestamps, hashes of the company name) are the kind of thing that gets an API scraped by a script in an afternoon. The rule: use your language's cryptographically secure RNG, not a regular one, and never derive a key from anything guessable.
The other generation discipline is uniqueness — each issued key must be globally unique so a lookup can't collide. A long enough random key makes collisions astronomically unlikely, but storing keys with a unique prefix or ID (separate from the secret itself) makes management and logging easier without exposing the secret.
Hashing at rest and the prefix trick
A rule I now treat as non-negotiable: never store API keys in plaintext. Store a hash (SHA-256 is fine; you don't need a password hash because keys are high-entropy) and compare hashes on each request [2]. If your database leaks, the hashes are useless to an attacker. The downside is you can't show a caller their key again after creation — which is correct behavior; you show it once and tell them to store it.
A common pattern that solves the "I can't recognize my own keys" problem is the prefix + secret format (sk_live_abc123...). The prefix is non-secret and identifies the key in your UI and logs; the secret part is what's hashed. Stripe, GitHub, and others use this shape. It lets a customer find a leaked key in their code by its prefix without the prefix itself being enough to authenticate.
Scopes and permissions: least privilege
This is the single most important management practice. Scopes are labels attached to a key (or token) that declare what it's allowed to do [4]. Instead of one all-powerful key per caller, you issue scoped credentials:
key: sk_live_orders_read_...
scopes: [ orders:read ] // can fetch orders, nothing else
key: sk_live_admin_...
scopes: [ orders:read, orders:write, users:read ]The principle at work is least privilege: a key should be able to do only what its specific job requires, and nothing more [4]. A reporting script that only reads orders gets an orders:read key, not a full-access one. The payoff is blast-radius reduction — when (not if) a key leaks, the damage is bounded by its scopes. A read-only leak is annoying; a full-access leak is an incident. Scopes turn a catastrophic event into a contained one.
The discipline is to make scopes granular enough to be useful (resource:action pairs like orders:write, users:read) and to resist the temptation to hand out * scopes except for truly trusted internal callers. Scopes come from OAuth 2.0 originally, but they apply identically to plain API key systems.
Rotation: replacing a key without downtime
Rotation is replacing an existing key with a new one — on a schedule or after a suspected compromise — without breaking the consumer's service [3]. The hardest part isn't generating the new key; it's doing the swap without a window where the old key is dead and the new one isn't deployed yet.
The mechanics I rely on:
- Overlap window. When rotating, issue the new key first, let the consumer deploy it, and only then revoke the old one. A scheduled rotation should always have a grace period measured in days, not seconds.
- Two active keys. Support at least two valid keys per caller during rotation. The consumer adds the new one, confirms it works, then you revoke the old.
- Scheduled rotation. Don't rely on "we'll rotate when something goes wrong." Set a cadence (90 days, 1 year) and automate reminders. Long-lived keys are debt.
- Emergency rotation. Have a documented process for "revoke this key now" so a suspected leak doesn't become an unprepared fire drill.
The model: rotation should be boring. If it's exciting, you haven't practiced it, and the first time you really need it will be the worst possible moment to learn.
Management as a first-class concern
Stepping back, "API management" is the umbrella over all of this — the tools and practices for governing keys across their whole lifecycle [1][2]. That includes the things above plus: a way for consumers to mint and revoke their own keys (so support tickets don't bottleneck rotation), analytics on key usage (which keys are actually being used; which are stale and revocable), rate limits per key, and audit logs of when each key was used. The pattern I look for: a key should be self-service to rotate, scoped by default, observable in use, and revocable instantly. Any of those missing and the management story is incomplete.
How I use this
When shipping an API that takes keys, I treat four things as table stakes: cryptographically random generation, hashing at rest (never plaintext), scopes from day one (even if there's only one scope initially — adding scoping later is painful), and a documented rotation path with an overlap window. When I issue a key for my own use against a third-party API, I scope it to the minimum the script needs and rotate it on a schedule rather than letting it live forever in a .env. And I treat any key in version control or logs as compromised-by-default — rotate immediately, because the cost of rotation is trivial next to the cost of a live leaked full-scope key.
References
[1] AKeyless, "What is API Key Management?," 2024. [Online]. Available: https://www.akeyless.io/secrets-management-glossary/api-key-management/
[2] Infisical, "API Key Management - Definition and Best Practices," 2024. [Online]. Available: https://infisical.com/blog/api-key-management
[3] CodeSignal, "API Key Generation Basics," 2024. [Online]. Available: https://codesignal.com/learn/courses/api-key-authentication-security/lessons/api-key-generation-basics
[4] Auth0, "Permissions, Privileges, and Scopes," 2024. [Online]. Available: https://auth0.com/blog/permissions-privileges-and-scopes/
Knowledge check · Question 1 of 5
Where should an API key be stored on the server side?
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!