Skip to main content
Watch: Claude Code Just Killed After Effects

Auth & Data

API Key Auth Design (Hash-Only)

Use when adding programmatic API key auth to a service. Covers show-once issuance, storing only a sha256 hash plus a display prefix instead of the secret, verifying a bearer token by hashing it, and revocation. Also use when a key is currently stored in a form that could be read back, or the UI needs to show a key it must not keep. If a caller authenticates without a session, use this skill.

api-keysauthsecurityhashingbearer

1 file

Description

Use when adding programmatic API key auth to a service. Covers show-once issuance, storing only a sha256 hash plus a display prefix instead of the secret, verifying a bearer token by hashing it, and revocation. Also use when a key is currently stored in a form that could be read back, or the UI needs to show a key it must not keep. If a caller authenticates without a session, use this skill.

Install
SKILL.md

name: api-key-auth-design description: Use when adding programmatic API key auth to a service. Covers hash-only storage (never persist the raw key), show-once issuance, a display prefix for the UI, constant-time-safe lookup by hash, and revocation.

API Key Auth Design (Hash-Only)

When to trigger

Letting users call your API with a long-lived key instead of a session: issuing keys, authenticating requests, listing and revoking keys.

The rule: never store the secret

Treat an API key like a password. The full key is shown to the user exactly once at creation and never again. You persist only:

  • A sha256 hex hash of the full key (what you look up by).
  • A short display prefix (for example dd_live_ab12) so the UI can show which key is which.
  • Metadata: name, owner id, created_at, last_used_at, revoked_at.
import { createHash, randomBytes } from "node:crypto";
const KEY_PREFIX = "dd_live_";
const raw = KEY_PREFIX + randomBytes(16).toString("hex"); // shown once
const hash = createHash("sha256").update(raw).digest("hex"); // stored
const displayPrefix = raw.slice(0, 12); // stored for the UI

Authenticate by hashing the presented token

On each request, take the bearer token, hash it the same way, and look up the row by that hash. Reject if not found or if revoked_at is set. Touch last_used_at opportunistically. Because you look up by the hash of the presented value, a database dump never leaks usable keys.

Guardrails to build in

  • Cap active keys per user (for example 10) and count only non-revoked keys.
  • Revoke by setting revoked_at, not by deleting the row, so an audit trail and last-used time survive.
  • listKeys returns the display prefix and metadata, never the hash.

Pitfalls

  • Storing the raw key "so support can read it back" is the whole vulnerability. If you can display it later, so can an attacker with your database.
  • A predictable key (sequential id, short random) is guessable. Use at least 16 random bytes from a CSPRNG.
  • Looking up by a plaintext prefix instead of the full hash lets a short brute force enumerate keys. Look up by the full hash; the prefix is display-only.
  • Deleting a revoked key loses the record that it was ever used. Soft-revoke instead.
AI Development Stack

Get Smarter About AI Dev

New tutorials, open-source projects, and deep dives on coding agents - delivered weekly.

One email per weekReal code, not theoryFree forever