API Keys & Service Credentials
API keys, personal access tokens, and service credentials are credentials for scripts and services. They often become “long-lived passwords” if not managed carefully.
Beginner mental hook: Treat API keys like passwords: scope them, rotate them, store them safely, and monitor their use.
Mental model
- Identity: which user/service does this key represent?
- Scope: what actions/resources are allowed (least privilege)?
- Lifecycle: creation, storage, rotation, and revocation.
- Telemetry: monitoring for unusual usage (IP, geo, time, volume, user agent).
experienced takeaway: Most incidents are not “broken crypto”; they are leakage (repos/logs), over-scoping, missing rotation, and lack of revocation/monitoring.
Practical note: API credential incidents are boring and brutal — one leaked token + missing scoping/rotation becomes a full environment compromise.
Common failure modes (what goes wrong)
A) Over-scoped tokens
- Root cause: one token can do everything.
- Attacker mindset: “If I steal one token, I get full control.”
- Defense: least privilege scopes, per-token permissions, and separate tokens per workload.
B) No rotation / no expiration
- Root cause: keys live forever; no process for rotation.
- Impact: breaches persist.
- Defense: expiry by default, rotation playbooks, and automated rollout support.
C) Leakage (repos/logs/client exposure)
- Root cause: keys in source control, logs, URLs, or exposed to browsers/mobile apps.
- Defense: secret scanning, redaction, never ship server secrets to clients, and tight logging policies.
D) No per-token revocation
- Root cause: can’t kill a single key without killing the whole account/service.
- Defense: token IDs, per-token revocation, and allow users to manage keys in UI/API.
Defensive patterns (Node.js)
Create token: show once, store only hash
import crypto from "crypto";
app.post("/tokens/create", requireStepUp, async (req, res) => {
const raw = crypto.randomBytes(32).toString("hex"); // show once
const hash = crypto.createHash("sha256").update(raw).digest("hex");
const token = await db.apiTokens.insert({
userId: req.session.userId,
tokenHash: hash,
scopes: ["reports:read"], // least privilege
expiresAt: Date.now() + 90*24*60*60*1000, // 90 days
revokedAt: null,
});
res.json({ ok: true, token: raw, tokenId: token.id }); // show raw once
}); Verify token: constant-time compare + scope checks
function hashToken(raw) {
return crypto.createHash("sha256").update(raw).digest("hex");
}
async function authApiToken(req, res, next) {
const raw = String(req.headers.authorization || "").replace("Bearer ", "");
if (!raw) return res.status(401).send("missing token");
const tokenRow = await db.apiTokens.findByHash(hashToken(raw));
if (!tokenRow || tokenRow.revokedAt || tokenRow.expiresAt < Date.now()) return res.status(401).send("invalid token");
req.token = tokenRow;
next();
}
function requireScope(scope) {
return (req, res, next) => req.token?.scopes?.includes(scope) ? next() : res.status(403).send("forbidden");
}
app.get("/reports", authApiToken, requireScope("reports:read"), (req, res) => res.json({ ok: true })); Guardrails: hash at rest, show once, scopes, expiry, per-token revocation, and monitoring.
Safe validation (defensive verification)
- Confirm tokens are never exposed to clients (no server secrets in browsers/mobile apps).
- Confirm tokens are scoped and least privilege by default.
- Confirm expiry and rotation exist; no “forever tokens” for sensitive systems.
- Confirm per-token revocation and key inventory UI/API exists.
- Confirm storage keeps only hashes (raw shown once) and logs redact secrets.
- Confirm monitoring and alerts on unusual usage and spikes.
Interview Questions & Answers (Easy → Hard)
Easy
- What is an API key?
A: Plain: a credential for scripts/services. Deep: should be scoped, rotated, and monitored like passwords. - Why is least privilege important?
A: Plain: limits damage. Deep: over-scoped tokens turn small leaks into full compromise. - Where should keys be stored?
A: Plain: in a vault. Deep: never in code, never in client bundles; use secret managers and access controls. - Do keys expire?
A: Plain: ideally yes. Deep: expiry + rotation reduces long-term breach impact. - Should we store raw keys in DB?
A: Plain: no. Deep: store hashes; show raw key once at creation. - What is per-token revocation?
A: Plain: disable one key. Deep: revoke a single credential without killing the whole account. - What do you log?
A: Plain: usage events. Deep: IP/UA/time + tokenId; never log raw secrets.
Medium
- Scenario: token leaked in Git repo. Immediate steps?
A: Plain: revoke and rotate. Deep: audit usage, rotate related secrets, add scanning and least privilege. - Scenario: token has “admin” scope. Why is that bad?
A: Plain: too much power. Deep: violates least privilege; split tokens by function and protect privileged ones with step-up approvals. - Scenario: service-to-service auth. Better than static API keys?
A: Plain: short-lived creds. Deep: mTLS/OIDC workload identity, short TTL tokens, automatic rotation. - Follow-up: how do you rotate without downtime?
A: Plain: overlap. Deep: allow two tokens/keys during rollout, then retire old; automate rollout and rollback. - Scenario: keys are used from browsers. Risk?
A: Plain: anyone can steal them. Deep: never expose server secrets to clients; use user auth + backend proxy. - Follow-up: what monitoring signals matter?
A: Plain: unusual activity. Deep: new IP/geo, spikes, unusual endpoints, failed auth patterns. - Scenario: no per-token revocation. Fix?
A: Plain: add token IDs. Deep: store token records with scopes, expiry, and revokeAt; show inventory UI/API.
Hard
- How do you do token hashing safely?
A: Plain: hash and compare. Deep: store SHA-256/HMAC-based hashes; constant-time compare; treat hash as secret-equivalent. - How do you design org-wide key governance?
A: Plain: policy + tooling. Deep: rotation SLAs, secret scanning, approvals for privileged scopes, telemetry baselines. - Scenario: compromised CI system leaks tokens. Response?
A: Plain: contain and rotate. Deep: revoke all CI-issued creds, rotate secrets, audit builds, restrict secret access, add attestations. - When would you use short-lived tokens instead of keys?
A: Plain: for automation at scale. Deep: workload identity, OIDC federation, and auto-rotated credentials reduce exposure. - What’s your incident runbook for leaked tokens?
A: Plain: revoke quickly. Deep: revoke/rotate, audit usage, notify, fix root cause, and add detection to prevent recurrence. - Follow-up: how do you balance UX and security for token creation?
A: Plain: keep it simple. Deep: show once, labels/scopes, expiry defaults, and warnings for privileged scopes.
Exploitation progression (attacker mindset)
- Find leaks: repos, logs, CI artifacts, client-side bundles, screenshots.
- Test scope: see what the key can do; over-scoped keys are high impact.
- Maintain access: lack of expiry/rotation makes keys valuable long-term.
- Hide activity: attackers prefer systems with weak telemetry and no per-token revocation.
Checklist
- Never ship server secrets to clients; keep tokens server-side.
- Least privilege scopes by default; separate tokens per workload.
- Expiry + rotation playbook; automated rollover support.
- Store only hashes; show raw token once; redact logs.
- Per-token revocation + inventory UI/API.
- Monitoring and anomaly detection; alerts on spikes/new IPs/geos.
Remediation playbook
- Kill leaks: enable secret scanning; rotate exposed tokens; scrub logs.
- Add scopes and least privilege defaults; split tokens by function/service.
- Add expiry by default + rotation workflows; encourage short-lived creds for automation.
- Implement hashed storage + show-once; add per-token revocation and inventory.
- Add monitoring + anomaly alerts; create incident runbooks for leaked tokens.