Blog / Keep credentials in data, not config

Keep credentials in data, not config

A deploy that might invalidate your API keys is a deploy you'll hesitate to run. That hesitation is a smell — and the cause is usually that secrets live in the wrong place.

Published

June 2026

Length

2 min read

Topics

Security · Architecture · Patterns

A deploy that might invalidate your API keys is a deploy you'll hesitate to run. That hesitation is a smell, and the cause is usually that secrets are living in the wrong place.

I caught myself hesitating in June. An AI agent publishes draft articles to this site through an MCP endpoint, authenticated by an API key, and with a release staged I had to stop and ask: will deploying override the agent's key? The answer was no — immediately, and by design — and the design is the part worth writing down.

When API keys are baked into configuration files or app settings, every deployment risks disturbing them, and rotating one means a config change and a release. When they live as rows in a database, deploys stop touching them entirely. The code that validates a key reads the table; shipping new code doesn't rewrite the table. A key keeps working across deployments, app restarts, and slot swaps, and the only things that revoke it are the explicit actions you take: stamping the revoked-at column, deleting the row.

Rows in a database does not mean plaintext in a database. A key here is minted from a CSPRNG, shown once at creation, and never stored: the row keeps a SHA-256 hash for lookup plus the first few characters for display in the admin list. A bare fast hash is fine in this one case — unlike a password, a 32-character random key can't be brute-forced from its digest. And each key carries a recognizable prefix, the same convention GitHub adopted for its token formats so that leaked credentials are identifiable on sight and by scanners. Validation is a hash lookup plus revoked/expired checks and a last-used timestamp — which quietly makes the key table its own audit trail.

That separation buys two things. Deploys get less scary, because the credential surface is simply out of their path. And revocation gets instant and precise — one row, one kill switch, no release required.

One honest boundary: this is the pattern for credentials your application issues and validates itself. Secrets your application consumes — the connection string, third-party keys, certificates — belong in a managed vault like Azure Key Vault, not in your own tables, because your database can't hold the secret that guards the database.

The principle generalizes past API keys: state that needs its own lifecycle — created, rotated, and revoked on its own schedule — belongs in data you can mutate at runtime, not in config you can only change by shipping.