API Security

API Keys

Ein API Key ist ein langlebiges Credential für Maschinen. Erstellen Sie ihn einmal, speichern Sie nur einen Hash, begrenzen Sie den Scope strikt und implementieren Sie eine Möglichkeit zur Rotation und zum Widerruf, bevor Sie diese jemals benötigen.

intermediate14 min readUpdated 16. Sept. 2026
keys.ts
ts
// keys.ts
import crypto from "node:crypto";

export function generateApiKey(env: "live" | "test") {
  const secret = crypto.randomBytes(32).toString("base64url");
  const prefix = `sk_${env}_`;
  const key = `${prefix}${secret}`;

  return {
    key,                        // returned to the user once
    prefix: key.slice(0, 12),   // stored and indexed for lookup
    hash: hashKey(key),         // stored instead of the key
  };
}

export function hashKey(key: string): string {
  return crypto.createHash("sha256").update(key).digest("hex");
}

export function timingSafeEqual(a: string, b: string): boolean {
  const left = Buffer.from(a);
  const right = Buffer.from(b);
  return left.length === right.length && crypto.timingSafeEqual(left, right);
}
Verwendung
Server-to-Server und öffentliche APIs
Format
Präfix plus ein zufälliges Secret
Gespeichert als
SHA-256 Hash
Anzeige
Einmalig bei der Erstellung
Transport
Authorization Header
Scoping
Berechtigungen und Rate Limits
Rotation
Neuen Key erstellen, dann alten widerrufen
Leak-Risiko
Git-Historie, Logs, Client-Code

Warum es wichtig ist

Was ein gutes Key-System bietet

Ein Credential, viele Dienste

Ein Key authentifiziert eine Maschine ohne Login-Flow. Er ist einfach auszustellen, zu senden und zu rotieren, weshalb jede Developer Platform sie einsetzt.

Hashed at Rest

Speichern Sie nur einen Hash, genau wie bei einem Passwort. Ein gestohlener Datenbank-Dump liefert dem Angreifer somit nichts, was er an Ihre API senden könnte.

Scoped und Rate Limited

Jeder Key besitzt eigene Berechtigungen und Quotas. Ein kompromittierter Key kann also nur das tun, was erlaubt war, und nur in der vorgesehenen Geschwindigkeit.

Das Gesamtbild

Drei Eigenschaften eines sicheren Keys

Ein zufälliges Secret, das nicht erraten werden kann, ein Hash at Rest, der nicht aus einem Dump wiederhergestellt werden kann, und ein Scope, der den Schaden bei einem Leak begrenzt.

Secret

Identifizieren

Ein zufälliger String mit hoher Entropie ist das Credential selbst. Seine einzige Aufgabe ist es, nicht erratbar und für einen Aufrufer eindeutig zu sein.

Scope

Begrenzen

Ein Key trägt einen Satz an Berechtigungen. Der Verifizierer prüft die Aktion gegen den Scope, bevor der Handler ausgeführt wird – genau wie bei Benutzerrollen.

Quota

Schützen

Keys bieten eine natürliche Gruppierung für Rate Limits, sodass eine einzelne, überlastete Integration nicht die Kapazität für alle anderen erschöpft.

HTML5 auf einen Blick

Die Komponenten, die Sie bauen werden

Format

Ein lesbares Präfix plus ein langes zufälliges Secret, z. B. sk_live_8f2a...

Hashing

Speichern Sie einen SHA-256 Hash; vergleichen Sie diesen mit einer Constant-Time-Funktion.

Scopes

Granulare Berechtigungen wie projects:read und deploy:write.

Rate Limits

Weisen Sie jedem Key eine Quota zu und setzen Sie diese pro Key durch.

Rotation

Erstellen Sie einen Ersatz, migrieren Sie den Traffic und widerrufen Sie dann den alten Key.

Monitoring

Tracken Sie last_used_at und alarmieren Sie bei plötzlichen Verhaltensänderungen.

Datenmodell

Die api_keys Tabelle

Es werden nur ein Hash und ein kurzes Präfix gespeichert. Der vollständige Key existiert nur einmal in der Antwort bei der Erstellung und kann nie wiederhergestellt werden.

Die api_keys TabellePostgreSQL table
  • idbigserialSurrogat-Primärschlüssel
  • nametextLesbares Label, z. B. CI deploy oder mobile app
  • prefixtextAnfangsbuchstaben des Keys, indiziert für schnelle Lookups
  • key_hashtextSHA-256 des vollständigen Keys, niemals der Key selbst
  • scopestext[]Berechtigungen, die der Key ausüben darf
  • owner_idbigintDer Benutzer oder Dienst, der den Key erstellt hat
  • last_used_attimestamptzWird bei Nutzung aktualisiert für Anomalieerkennung und Bereinigung
  • revoked_attimestamptzWird gesetzt, wenn der Key widerrufen wird; null bedeutet aktiv

Es werden nur ein Hash und ein kurzes Präfix gespeichert. Der vollständige Key existiert nur einmal in der Antwort bei der Erstellung und kann nie wiederhergestellt werden.

Ablauf

Ausstellung und Verifizierung eines Keys

Der vollständige Key existiert in genau einer Antwort; alles danach basiert auf einem Hash und einem Präfix.

  1. 1

    Zufälliges Secret generieren

    Ziehen Sie mindestens 128 Bit aus einem CSPRNG und kombinieren Sie diese mit einem lesbaren Präfix.

  2. 2

    Einmalig anzeigen

    Geben Sie den vollständigen Key in der Antwort der Erstellung zurück und speichern oder zeigen Sie ihn nie wieder an.

  3. 3

    Hash und Präfix speichern

    Speichern Sie den Hash, das kurze Präfix, die Scopes und den Besitzer in der api_keys Tabelle.

  4. 4

    Im Header senden

    Der Client übermittelt den Key bei jeder Anfrage im Authorization Header oder einem dedizierten Header.

  5. 5

    Hashen und Vergleichen

    Suchen Sie den Key über das Präfix, hashen Sie den übermittelten Wert und vergleichen Sie ihn in Constant Time.

  6. 6

    Scopes zuweisen

    Laden Sie die Berechtigungen und die Quota des Keys in die Anfrage, damit der Handler diese durchsetzen kann.

  7. 7

    Rotieren oder Widerrufen

    Erstellen Sie einen Ersatz, migrieren Sie den Traffic und markieren Sie den alten Key als widerrufen.

Der vollständige Leitfaden

API Keys: Alles was Sie wissen müssen

Was ein API key ist

Ein API key ist ein langlebiger, geheimer String, der eine Anwendung anstelle einer Person identifiziert. Ein Client sendet ihn mit jeder Anfrage, der Server erkennt ihn und der Zugriff wird basierend auf den Berechtigungen des Keys gewährt. Das ist das Grundprinzip.

Es handelt sich dabei um ein bewusst einfach gehaltenes Credential. Es gibt keinen Login, keinen Consent-Screen und keinen Token-Austausch. Ein Entwickler registriert sich, erstellt einen Key, fügt ihn in eine Konfigurationsdatei ein und der Code funktioniert. Diese geringe Reibung ist der Grund, warum fast jede Entwicklerplattform – ob für Zahlungen, Karten, E-Mails oder Infrastruktur – Keys ausgibt.

Aber einfach bedeutet nicht nachlässig. Ein Key ist ein Bearer-Credential: Wer ihn besitzt, kann ihn verwenden, genau wie Bargeld. Es gibt keinen zweiten Faktor und keine Signatur. Das bedeutet, dass die Sicherheit des gesamten Systems davon abhängt, wie gut Sie Keys generieren, wie sorgfältig Sie diese speichern, wie eng Sie ihren Scope definieren und wie schnell Sie einen Key widerrufen können, der geleakt ist. In diesem Guide geht es darum, all diese vier Punkte optimal umzusetzen.

Wann ein Key das richtige Werkzeug ist

Keys sind nicht die Lösung für jedes Authentifizierungsproblem, und ihr Einsatz am falschen Ort birgt echte Risiken.

Greifen Sie zu einem API key, wenn ein Server mit einem anderen Server kommuniziert, wenn der Aufrufer eine Anwendung ist, der Sie ein langlebiges Secret anvertrauen können, oder wenn Sie eine öffentliche API für Entwickler anbieten. CI-Pipelines, Backend-Integrationen, Monitoring-Agents und Drittanbieter-Dienste sind hierfür prädestiniert. Der Key wird in einem Secret Manager oder einer Umgebungsvariablen gespeichert und gelangt niemals in einen Browser.

Greifen Sie zu OAuth, wenn Sie im Namen eines Benutzers handeln müssen. Wenn Ihre Integration beispielsweise den Kalender von jemandem lesen oder E-Mails in dessen Namen versenden muss, benötigen Sie einen Consent-Flow und einen Token, der diese Delegation repräsentiert. Ein Key kann nicht ausdrücken: „Dies sind Alices Daten und Alice hat zugestimmt“.

Greifen Sie zu einem JWT, wenn Sie einen kurzlebigen, verifizierbaren Token benötigen, der Claims enthält und ohne eine gemeinsame Datenbank geprüft werden kann. Service-to-Service-Authentifizierung in einem Mesh oder eine signierte Download-URL sind gute Beispiele hierfür.

Der gefährliche Fall ist ein public client. Ein Key, der in eine mobile App, eine Desktop-Binary oder ein Browser-Bundle eingebettet ist, ist nicht geheim: Jeder kann ihn extrahieren. Wenn Ihr Produkt benötigt, dass diese Clients Ihre API aufrufen, schalten Sie einen Backend-Proxy davor oder stellen Sie kurzlebige Token über Ihren eigenen Server aus, nachdem der Benutzer authentifiziert wurde. Liefern Sie niemals einen langlebigen Key innerhalb von Client-Code aus.

Die Struktur eines guten Keys

Ein guter Key ist nicht erratbar und selbsterklärend. Er besteht aus zwei Teilen: einem kurzen, lesbaren Prefix und einem langen, zufälligen Secret.

The shape of an API key
sk_live_8f2a9c1d4e6b7a0f3c5d8e2b1a4f7c9d6e3b0a8f5c2d1e4b7a9c6f0d3e8b1a4
prefixenvironment and type, safe to log and scan for
secret256 bits of cryptographically secure randomness

Der Prefix erfüllt zwei Aufgaben. Er identifiziert auf einen Blick die Umgebung und den Typ des Keys und bietet dem Server einen indizierten Handle für die Suche, ohne dass das Secret gespeichert werden muss. Ein festes, erkennbares Format macht versehentliche Leaks zudem für Secret-Scanner detektierbar, die sk_live_ in einem Commit erkennen und blockieren können.

Das Secret muss aus einer kryptografisch sicheren Zufallsquelle stammen – niemals aus Math.random, einem Zeitstempel oder einer UUID, die eine Struktur preisgibt. 128 Bit Entropie sind das Minimum; 256 Bit sind ein sicherer Standard und kosten keine zusätzlichen Ressourcen. Eine Base64url-Kodierung stellt sicher, dass der Key sicher in URLs und Header eingefügt werden kann, ohne dass ein Escaping nötig ist.

const secret = crypto.randomBytes(32).toString("base64url");
const key = `sk_live_${secret}`;

Widerstehen Sie dem Drang, Informationen in den Key zu kodieren, wie etwa die User-ID oder ein Erstellungsdatum. Alles, was im Key lesbar ist, stellt eine Information dar, die ein Angreifer gewinnen kann, und alles, was aus vorhersagbaren Daten abgeleitet wird, schwächt die Zufälligkeit. Der Prefix ist der einzige lesbare Teil, den Sie benötigen, und er sollte keinerlei sensible Daten preisgeben.

Einmal anzeigen, dann vergessen

Der vollständige Key sollte nach der Erstellung an genau einer Stelle existieren: in der Response, die ihn an den Benutzer zurückgibt. Ab diesem Moment speichert der Server nur noch einen Hash und einen Prefix. Das bedeutet, dass er einen vorgelegten Key zwar verifizieren, ihn aber niemals wieder reproduzieren kann.

Dies ist dieselbe Eigenschaft wie bei der Passwortspeicherung und verändert die Konsequenzen eines Datenlecks grundlegend. Wenn ein Angreifer die api_keys Tabelle dumpen sollte, erhält er Hashes, die nicht an deine API gesendet werden können. Ohne Hashing würde ein einziges Datenbank-Leak oder ein exponiertes Backup sofort die Keys aller Kunden preisgeben.

Die User Experience ergibt sich aus dieser technischen Einschränkung. Das Dashboard zeigt den Key einmalig an, zusammen mit einem deutlichen Warnhinweis, ihn sofort zu kopieren. Danach werden nur noch der Prefix und Metadaten wie Scopes und die letzte Nutzung angezeigt. Wenn ein Benutzer einen Key verliert, muss er diesen rotieren; eine Wiederherstellung ist nicht möglich. Schreibe dies explizit in deine Dokumentation, damit niemand erwartet, den Key später noch finden zu können.

res.status(201).json({
  id: row.id,
  name: row.name,
  prefix: row.prefix,
  key, // shown once, never retrievable again
  warning: "Store this key now. You will not be able to see it again.",
});

Speichere einen Hash, niemals den Key

Hashing ist die wichtigste Sicherheitsmaßnahme in einem API-Key-System. Gleichzeitig ist es der Punkt, den Teams am häufigsten überspringen – meistens, weil sie den Key später erneut anzeigen möchten. Tu das nicht.

Verwende einen schnellen kryptografischen Hash wie SHA-256. Im Gegensatz zu Passwörtern besitzen Keys volle Entropie, sodass es kein Dictionary für Angriffe gibt und keine langsame KDF benötigt wird. Der schnelle Hash hält zudem die Verifizierung im „hot path“ kostengünstig.

function hashKey(key: string): string {
  return crypto.createHash("sha256").update(key).digest("hex");
}

Der Vergleich muss in konstanter Zeit (constant time) erfolgen. Ein naiver === von Strings bricht beim ersten unterschiedlichen Byte ab, was preisgibt, wie viel eines geratenen Keys korrekt war. Über ein Netzwerk ist dies meist ein theoretisches Problem, aber es ist trivial zu vermeiden und gehört zu einer guten Hygiene. Vergleiche Buffer gleicher Länge mit crypto.timingSafeEqual.

const a = Buffer.from(hashKey(presented));
const b = Buffer.from(record.keyHash);
const ok = a.length === b.length && crypto.timingSafeEqual(a, b);

Wenn du den Hash vollständig aus der Datenbank ausblenden möchtest, fügt ein keyed hash mit einem serverseitigen Pepper ein zweites Geheimnis hinzu, das ein Angreifer ebenfalls erbeuten müsste. Das ist für Hochsicherheitssysteme optional, aber das Hashen an sich ist nicht optional.

Keys suchen ohne Full-Scan

Beim Hashing gibt es ein praktisches Problem: Man kann die Datenbank nicht nach dem Hash abfragen, ohne zuerst den eingehenden Key zu hashen – und man kann den eingehenden Key nicht hashen, bevor man weiß, mit welcher Zeile er verglichen werden soll. Jede Zeile bei jeder Anfrage zu hashen, ist keine Option.

Die Lösung ist der Prefix-Index. Speichern Sie die ersten ein Dutzend Zeichen des Keys in einer Plaintext-prefix-Spalte mit einem Unique-Index. Wenn eine Anfrage eingeht, lesen Sie den Prefix aus, finden die eine passende Zeile, hashen dann den vollständigen übergebenen Key und vergleichen diesen mit dem key_hash dieser Zeile. Ein indexierter Lookup, ein Hash, ein Vergleich in konstanter Zeit.

const prefix = key.slice(0, 12);
const record = await db.apiKey.findByPrefix(prefix);
if (!record || record.revokedAt) return res.status(401).end();

const presented = hashKey(key);
if (!timingSafeEqual(presented, record.keyHash)) {
  return res.status(401).end();
}

Ein paar Details sorgen hier für die nötige Sicherheit. Der Prefix muss lang genug sein, damit Kollisionen selten sind, aber kurz genug, um ein nützliches Label zu sein; zwölf Zeichen sind eine gängige Wahl. Falls es doch zu einer Kollision kommt, schlägt der Unique-Index bei der Erstellung fehl und Sie generieren den Key neu. Geben Sie bei einem unbekannten Prefix und einem falschen Secret den gleichen Fehler zurück, damit die Antwort nicht verrät, ob ein Prefix existiert. Und aktualisieren Sie last_used_at asynchron oder in einem Batch-Write, da ein synchrones Update bei jeder Anfrage einen Lesezugriff in einen Schreibzugriff verwandelt und Ihre Datenbanklast verdoppelt.

Keys auf Berechtigungen und Limits einschränken (Scoping)

Ein Key, der alles kann, ist ein Key, dessen Leak eine Katastrophe darstellt. Beschränken Sie jeden Key auf die kleinstmögliche Menge an Berechtigungen, die für seine Aufgabe erforderlich sind.

Scopes sind einfache Berechtigungs-Strings – dieselben Atome, die auch bei RBAC verwendet werden: projects:read, deploy:write, billing:manage. Speichern Sie diese am Key, hängen Sie sie nach der Verifizierung an den Request an und setzen Sie sie genau so durch, wie Sie es bei den Berechtigungen eines Benutzers tun würden.

router.post(
  "/deployments",
  apiKeyAuth(),
  requireScope("deploy:write"),
  createDeployment
);

Hier wird das Prinzip der geringsten Privilegien (Least Privilege) konkret. Eine Monitoring-Integration benötigt lediglich metrics:read. Eine CI-Pipeline benötigt deploy:write, aber niemals billing:manage. Ein Partner mit Lesezugriff erhält Read-Scopes und sonst nichts. Wenn ein Key geleakt wird, ist der Schadensradius auf das beschränkt, was für diesen Key definiert wurde. Deshalb sollte der Standard eine kurze Liste sein, die ein Mensch bewusst erweitert.

Scopes machen zudem Rate Limits natürlich und fair. Da jeder Key ein eigenständiger Aufrufer ist, können Sie eine Quote pro Key zuweisen und diese in Ihrer Rate-Limiting-Layer durchsetzen. So kann ein außer Kontrolle geratenes Script nicht die Kapazitäten verbrauchen, die für alle anderen gedacht sind. Gestaffelte Preispläne lassen sich oft direkt auf diese Quoten abbilden: Free-Keys erhalten eine niedrige Obergrenze, Paid-Keys eine höhere.

Umgebung-Präfixe

Präfixe sind keine Dekoration. Sie kodieren die Umgebung und den Typ eines Keys, was einen der häufigsten und peinlichsten Fehlermodi verhindert: ein Test-Key, der auf die Production zeigt, oder ein Production-Key, der in einer Test-Suite verwendet wird und dadurch echte Daten verändert.

Eine gängige Konvention ist sk_live_ für Secret Keys in Production und sk_test_ für die Sandbox. Publishable oder Public Keys könnten pk_live_ verwenden. Die Benennung liegt bei Ihnen, aber halten Sie diese konsistent und dokumentieren Sie sie, da sich Ihre Nutzer darauf verlassen, auf einen Blick zu erkennen, worauf ein Key Zugriff hat.

sk_live_...  secret key, production, full access within its scopes
sk_test_...  secret key, sandbox, no real data
pk_live_...  publishable key, safe to embed in a browser

Das Präfix ermöglicht es Ihrem Server zudem, Anfragen bereits vor jeder Suche an die richtige Umgebung zu routen. Ein sk_test_ Key, der an die Production API gesendet wird, kann sofort mit einer klaren Nachricht abgelehnt werden, anstatt als undurchsichtiger Authentifizierungsfehler fehlzuschlagen. Und da das Präfix fest definiert und markant ist, können Secret-Scanner und Code-Review-Tools so konfiguriert werden, dass sie dieses kennzeichnen.

Einen Key senden: Header oder Query

Wo der Key übertragen wird, ist genauso wichtig wie, wie er gespeichert wird. Senden Sie ihn in einem Header.

GET /v1/projects HTTP/1.1
Host: api.example.com
Authorization: Bearer sk_live_8f2a9c...

Header werden nicht in den Standard-Access-Logs erfasst, erscheinen nicht im Browserverlauf und werden im Referer-Header nicht weitergeleitet, wenn eine Seite auf eine andere Website verlinkt. Zudem lassen sie sich in Logs und Proxies leicht schwärzen. Der Authorization-Header mit einem Bearer-Schema ist konventionell und wird von den meisten HTTP-Clients und Tools unterstützt; ein dedizierter X-API-Key-Header ist ebenso in Ordnung.

Query-Strings sind der falsche Ort. Sie werden von Access-Logs erfasst, von Intermediaries gecacht, im Browserverlauf sowie in Analytics-Tools gespeichert und über Referer geleakt. Ein Key in einer URL ist ein Key an einem Dutzend Orten, die Sie nicht kontrollieren.

GET /v1/projects?api_key=sk_live_8f2a9c... HTTP/1.1

Wenn ein Client tatsächlich keine Header setzen kann – etwa bei Legacy-Webhooks oder Embed-Szenarien –, verwenden Sie stattdessen einen kurzlebigen, eng gefassten Token im Query-String und lassen Sie diesen schnell ablaufen. Akzeptieren Sie niemals einen langlebigen Key in einer URL, und falls Ihre Logs einen enthalten könnten, bereinigen Sie diese.

Rotation und Widerruf

Jeder Schlüssel muss irgendwann ausgetauscht werden. Ein Mitarbeiter verlässt das Unternehmen, ein Laptop geht verloren, ein Schlüssel landet in einem öffentlichen Repository oder eine Routine-Richtlinie erfordert einfach eine periodische Rotation. Planen Sie dies von Anfang an ein, denn eine nachträgliche Implementierung der Rotation ist mühsam.

Bei der Rotation wird ein Ersatz ausgestellt, ohne dass der Client unterbrochen wird. Das Muster ist: Erstellen Sie einen neuen Schlüssel mit denselben Scopes, geben Sie diesen zurück, lassen Sie beide Schlüssel für ein kurzes Überlappungsfenster funktionieren und rufen Sie dann den alten widerrufen. Diese Überlappung ermöglicht eine Rotation ohne Ausfallzeiten (Zero-Downtime), und die Angabe der Dauer erlaubt es Integratoren, diese zu planen.

// 1. Issue the replacement and return it to the owner.
// 2. Keep both keys valid for the overlap window (e.g. 24 hours).
// 3. Revoke the old key, or let it expire automatically.
await db.apiKey.revoke(oldKeyId);

Ein Widerruf (Revocation) erfolgt sofort und dauerhaft. Setzen Sie revoked_at in der entsprechenden Zeile und lassen Sie die Verifizierungs-middleware jeden Schlüssel mit einem Nicht-Null-Wert ablehnen. Löschen Sie die Zeile nicht: Durch das Beibehalten bleibt der Audit-Trail erhalten und verhindert, dass dasselbe Präfix erneut verwendet wird. Ein Widerruf muss bereits bei der nächsten Anfrage greifen – das ist einfach, wenn der Schlüssel gegen die Datenbank geprüft wird, aber unmöglich, wenn es sich um einen eigenständigen Token handelt.

Unterstützen Sie den Widerruf eines einzelnen Schlüssels, aller Schlüssel eines Benutzers sowie aller Schlüssel eines Tenants. Die letzten beiden Optionen sind quasi der „Ich glaube, wir wurden gehackt“-Button und sollten mit einem Klick ausführbar sein. Benachrichtigen Sie den Besitzer jedes Mal, wenn ein Schlüssel erstellt oder widerrufen wird, damit ein Angreifer, der Zugriff auf das Dashboard erhält, nicht unbemerkt neue Anmeldedaten erstellen kann.

Leaks: git, logs und Client-Code

Die meisten Kompromittierungen von API-Keys sind keine ausgeklügelten Angriffe. Meistens liegt ein Key einfach an einer Stelle, an der er nicht sein sollte.

Source control ist der Klassiker. Ein Key, der in eine Konfigurationsdatei, ein Test-Fixture oder ein .env kopiert und committet wurde, bleibt für immer in der git-Historie, selbst wenn ein späterer Commit ihn entfernt. Scannen Sie Commits und Pull Requests auf Key-Muster, bewahren Sie Secrets in einem Manager oder in CI-Variablen auf und rotieren Sie diese sofort, falls sie einmal committet wurden. Gehen Sie davon aus, dass ein Key in einem öffentlichen Repository bereits kompromittiert ist.

Logs sind die unterschätzte Gefahr. Ein Request-Logger, der vollständige URLs, Header oder Bodys ausgibt, kann Millionen von Keys erfassen. Konfigurieren Sie Ihren Logger so, dass Authorization- und X-API-Key-Header geschwärzt werden, loggen Sie niemals Request-Bodys an Auth-Endpoints und prüfen Sie die Log-Ausgabe auf Strings, die wie Keys aussehen.

Client-side code ist der fatale Fehler. Ein Key in einem Browser-Bundle, einer mobilen App oder einer Desktop-Binary kann innerhalb von Minuten extrahiert werden. Es gibt keine Obfuskation, die dieses Problem löst. Schalten Sie einen Server davor oder geben Sie kurzlebige Tokens aus.

Weitere Stellen, die Sie prüfen sollten: Fehlermeldungen und Stack Traces, Third-Party-Analytics, Screenshots in Bug-Reports und Developer-Laptops, die mit einem gemeinsamen Backup synchronisiert werden. Betrachten Sie jede dieser Stellen als mögliches Leak und geben Sie den Nutzern die Werkzeuge an die Hand, um zu reagieren, wenn es passiert.

Nutzung und Anomalien überwachen

Ein Key, der nie beobachtet wird, kann nicht geschützt werden. Protokollieren Sie über jede Nutzung ausreichend Informationen, um Missbrauch zu erkennen und Fragen nach einem Vorfall beantworten zu können.

Speichern Sie mindestens last_used_at und die Quelle der Anfrage. Darauf aufbauend können Sie Alerts erstellen, die relevante Muster erkennen: ein Key, der nach Monaten zum ersten Mal wieder verwendet wird, ein plötzlicher Anstieg der Anfragerate, ein Herkunftsland, das nicht zur Integration passt, oder eine Flut von 401-Fehlern, die darauf hindeutet, dass jemand Präfixe erraten möchte.

logger.info({
  event: "api_key.used",
  keyId: record.id,
  ownerId: record.ownerId,
  route: req.path,
  ip: req.ip,
});

Loggen Sie niemals den Key selbst, sondern nur seine ID und sein Präfix. Stellen Sie die Nutzung im Dashboard dar, damit Kunden sehen können, welche Keys aktiv sind und welche sie nicht zuordnen können. Versenden Sie E-Mails bei der Erstellung, Rotation und dem Widerruf und geben Sie den Besitzern die Möglichkeit, einen verdächtigen Key sofort zu deaktivieren.

Für eine umfassendere Behandlung des Schutzes einer API vor Missbrauch deckt der Guide zu rate limiting Quotas, Burst-Handling und das Zusammenspiel von pro-Key-Limits ab.

Ablaufdaten und Lifecycle-Policies

Ein Key ohne Ablaufdatum ist ein Key, den man so lange vergisst, bis er geleakt wird. Geben Sie jedem Key einen Lifecycle, selbst wenn der Standardwert großzügig bemessen ist.

Ein optionales Ablaufdatum ermöglicht es einem Nutzer, einen Key zu erstellen, der an einem gewählten Datum ungültig wird – ideal für externe Dienstleister, temporäre Integrationen oder einmalige Migrationen. Ein absolutes maximales Alter begrenzt die maximale Lebensdauer jedes Keys, nach der eine Rotation zwingend erforderlich ist. Viele Plattformen kombinieren beides: Keys haben standardmäßig eine Laufzeit von einem Jahr, können kürzer gewählt werden, dürfen aber niemals zwei Jahre überschreiten.

Tracken Sie einen State anstatt nur eines Booleans. Ein Key kann aktiv sein, bald ablaufen, abgelaufen oder widerrufen sein – und jeder dieser Zustände erfordert eine andere Reaktion. Keys, die kurz vor dem Ablauf stehen, sollten eine E-Mail auslösen, damit der Besitzer die Rotation vor einem Ausfall durchführt. Die Verifizierungs-middleware sollte abgelaufene und widerrufene Keys identisch behandeln: Ablehnung mit 401.

function isUsable(key: ApiKey): boolean {
  if (key.revokedAt) return false;
  if (key.expiresAt && key.expiresAt < new Date()) return false;
  return true;
}

Ein Ablaufdatum ist ein Sicherheitsnetz, kein Ersatz für den Widerruf (Revocation). Ein gestohlener Key, der erst in einem Jahr abläuft, ist immer noch gefährlich; daher bleiben Rotation und Monitoring die primären Kontrollmechanismen. Aber eine zeitliche Begrenzung bedeutet, dass ein Key, den jemand vergessen hat oder der von einem ausgeschiedenen Mitarbeiter zurückgelassen wurde, irgendwann von selbst aufhört zu funktionieren.

Testen der API-Key-Authentifizierung

Die Verifizierung von API-Keys besteht aus einer geringen Menge an Code, die jedoch einen weitreichenden Zugriff schützt. Testen Sie diese daher gründlich und konzentrieren Sie sich dabei primär auf Negativtests.

import request from "supertest";
import app from "../app.js";

test("rejects a request with no key", async () => {
  await request(app).get("/v1/projects").expect(401);
});

test("rejects a malformed key", async () => {
  await request(app)
    .get("/v1/projects")
    .set("Authorization", "Bearer not-a-real-key")
    .expect(401);
});

test("rejects a revoked key", async () => {
  const { key } = await createKey();
  await revokeKey(key);
  await request(app)
    .get("/v1/projects")
    .set("Authorization", `Bearer ${key}`)
    .expect(401);
});

test("rejects a key without the required scope", async () => {
  const { key } = await createKey({ scopes: ["projects:read"] });
  await request(app)
    .post("/v1/deployments")
    .set("Authorization", `Bearer ${key}`)
    .expect(403);
});

Fügen Sie Tests hinzu, die beweisen, dass der vollständige Key nicht gespeichert wird: Erstellen Sie einen Key, untersuchen Sie die entsprechende Tabellenzeile und stellen Sie sicher, dass key_hash der Hash ist und der Klartext nirgendwo auftaucht. Verifizieren Sie, dass last_used_at bei jeder Nutzung aktualisiert wird. Testen Sie zudem explizit die Überlappung bei der Rotation: Sowohl der alte als auch der neue Key sollten während des Zeitfensters funktionieren, danach jedoch nur noch der neue.

Testen Sie abschließend, ob Fehlerantworten nicht zwischen einem unbekannten Präfix und einem falschen Secret unterscheiden. Beide Fälle müssen denselben Status und denselben Body zurückgeben, da Sie ansonsten einem Angreifer die Möglichkeit geben, zu bestätigen, welche Präfixe existieren.

Aufbau der UI für die Schlüsselverwaltung

Der Verwaltungsbildschirm ist der Ort, an dem die Sicherheitsaspekte für die Nutzer sichtbar werden. Daher sollte das richtige Verhalten hier auch das einfachste sein.

Die Listenansicht zeigt für jeden Schlüssel den Namen, das Präfix, die Scopes, das Erstellungsdatum und die letzte Nutzung an – niemals das Secret. Sie bietet eine Revoke-Schaltfläche mit einer Bestätigung und macht „Schlüssel erstellen“ zum Standardweg für die Rotation. Im Erstellungsprozess kann der Nutzer Scopes und ein optionales Ablaufdatum wählen; anschließend wird der vollständige Schlüssel einmalig zusammen mit einem Kopier-Button und einer unmissverständlichen Warnung angezeigt.

sk_live_8f2a...   CI deploy      deploy:write       created 3 days ago
sk_live_1c4b...   Monitoring     metrics:read       last used 2 minutes ago
sk_test_9a7d...   Staging        projects:read      revoked yesterday

Nützliche Details, die man einbauen sollte: ein Zeitstempel für die „letzte Nutzung“, damit Nutzer Schlüssel identifizieren können, die sie nicht wiedererkennen, ein One-Click-Revoke für einzelne Schlüssel, ein „Alle widerrufen“ für das gesamte Konto sowie E-Mail-Benachrichtigungen bei jeder Erstellung und jedem Widerruf. Falls Sie ein Nutzungsdiagramm anzeigen, tun Sie dies pro Schlüssel, da dies die Einheit ist, über die Nutzer nachdenken.

Bauen Sie keinen „Schlüssel anzeigen“-Button. Dieser darf nicht existieren, wenn Sie korrekt hashen, und sein Fehlen ist ein Feature: Es bedeutet, dass ein Datenbank-Leak überlebbar ist. Erklären Sie in der UI, dass Schlüssel nur einmal angezeigt werden und die Rotation der Weg zur Wiederherstellung ist, damit diese Einschränkung bewusst und nicht wie ein Fehler wirkt.

Wahl der Kodierung und Länge

Die Wahl der Kodierung ist eine kleine Entscheidung mit einigen praktischen Auswirkungen. Base64url ist die gängige Wahl, da sie kompakt ist, in URLs und Headern sicher funktioniert und case-sensitive ist, was die Entropie pro Zeichen maximiert. Hex ist länger, aber leichter laut vorzulesen und in Logs abzugleichen. Base62 liegt zwischen diesen beiden Optionen und vermeidet + und / vollständig.

Kodierung 128 bits 256 bits Notizen
base64url 22 Zeichen 43 Zeichen Kompakt, URL-safe, case-sensitive
hex 32 Zeichen 64 Zeichen Länger, einfach zu kopieren, case-insensitive
base62 22 Zeichen 43 Zeichen Nur alphanumerisch, keine Symbole

Was auch immer Sie wählen, behandeln Sie das Secret als case-sensitive und wandeln Sie es vor dem Vergleich niemals in Kleinbuchstaben um. Ein häufiger Bug ist eine middleware oder ein Proxy, der Header-Werte normalisiert und dadurch stillschweigend Keys bricht, die Großbuchstaben enthalten. Dokumentieren Sie das exakte Format und gestalten Sie das Präfix so markant, dass ein Key in einem Support-Ticket erkennbar ist, ohne für einen Angreifer nützlich zu sein.

Machen Sie den Key über das Präfix hinaus nicht selbsterklärend. Das Einbetten der User-ID, einer Prüfsumme oder eines Ablaufdatums in den Key verleitet dazu, den Datenbank-Lookup zu überspringen; es bedeutet jedoch auch, dass der Key Informationen transportiert und ohnehin nicht ohne einen Lookup widerrufen werden kann. Halten Sie das Secret opak und überlassen Sie die Bedeutung der Datenbank.

Client-Secrets sicher speichern

Die serverseitige Speicherung ist nur die halbe Miete; auch der Client muss den Schlüssel sicher verwahren. Geben Sie Integratoren eine klare, fundierte Anleitung, da viele Entwickler dazu neigen, einen Schlüssel einfach in eine Datei einzufügen und diese zu committen.

Empfehlen Sie für Server Umgebungsvariablen, die von der Plattform oder einem Secret Manager injiziert werden, anstatt sie in eine eingecheckte .env zu schreiben. Nutzen Sie in der CI den Secret Store der Pipeline und maskieren Sie den Wert in den Logs. Für die lokale Entwicklung sollten die Daten aus einer .env geladen werden, die per git-ignore ausgeschlossen ist. Bevorzugen Sie sk_test_-Keys, damit ein Fehler nur Sandbox-Daten betrifft.

# Load from the environment, never hard-code.
export MYAPP_API_KEY="sk_live_8f2a9c..."
curl -H "Authorization: Bearer $MYAPP_API_KEY" https://api.example.com/v1/projects

Verweisen Sie die Nutzer auf Managed Secret Stores — AWS Secrets Manager, Google Secret Manager, Vault oder die integrierten Variablen der Plattform — und erklären Sie die Rotation so, dass sie direkt umsetzbar ist. Wenn Ihr SDK den Schlüssel standardmäßig aus einer Umgebungsvariable liest, werden die meisten Integrationen automatisch das Richtige tun, ohne dass sie explizit darauf hingewiesen werden müssen – das ist die effektivste Form der Sicherheitskontrolle.

Secret Keys und Publishable Keys

Viele Plattformen liefern zwei Arten von Schlüsseln aus, und deren Verwechslung führt zu ernsthaften Problemen. Ein Secret Key authentifiziert einen vertrauenswürdigen Server und darf niemals offengelegt werden. Ein Publishable Key ist dafür gedacht, in den Client-Code eingebettet zu werden; es ist sicher, diesen öffentlich zu machen, da er für sich allein genommen keine Berechtigungen gewährt.

sk_live_...  secret, server-only, carries scopes and a quota
pk_live_...  publishable, client-safe, identifies the account only

Ein Publishable Key ist nützlich für die Zuordnung (Attribution) und das Rate Limiting im Browser, aber jede privilegierte Operation muss weiterhin durch etwas autorisiert werden, das der Client nicht fälschen kann: einen kurzlebigen Token, der von Ihrem Server erstellt wurde, oder eine Benutzersession. Behandeln Sie den Publishable Key als Identifikator, nicht als Anmeldedaten, und lassen Sie niemals die bloße Anwesenheit dieses Schlüssels den Zugriff gewähren.

Eine konsistente Benennung beider Schlüssel macht den Unterschied auf einen Blick deutlich und sorgt dafür, dass Secret-Scanner, Code-Reviews und die Dokumentation dieselbe Regel verstärken. Wenn ein Entwickler jemals einen sk_ Key in den Front-End-Code kopiert, sollte bereits das Präfix den Fehler sichtbar machen, bevor er live geht.

Best Practices

  • Generieren Sie das Secret mithilfe eines CSPRNG mit mindestens 128 Bit Entropie, versehen mit einem Präfix zur besseren Lesbarkeit und Umgebungserkennung.
  • Speichern Sie nur einen SHA-256 Hash und ein kurzes Präfix; speichern Sie niemals den vollständigen Key dauerhaft.
  • Zeigen Sie den vollständigen Key genau einmal bei der Erstellung an und nutzen Sie die Rotation als Recovery-Pfad.
  • Vergleichen Sie Hashes in Constant Time und geben Sie bei unbekannten sowie ungültigen Keys denselben Fehler zurück.
  • Indexieren Sie das Präfix, sodass die Verifizierung nur einen Lookup und einen Hash-Vorgang erfordert.
  • Beschränken Sie jeden Key auf die minimal notwendigen Berechtigungen und weisen Sie jedem Key ein eigenes Rate Limit zu.
  • Kodieren Sie die Umgebung im Präfix und lehnen Sie Sandbox-Keys an der Production API ab.
  • Akzeptieren Sie Keys im Authorization Header, niemals in einem Query String.
  • Unterstützen Sie Zero-Downtime Rotation mit einem Überlappungsfenster und sorgen Sie für eine sofortige Revocation.
  • Überwachen Sie last_used_at, alarmieren Sie bei Anomalien und benachrichtigen Sie die Besitzer bei jeder Änderung der Credentials.
  • Trennen Sie Secret Keys von Publishable Keys und stellen Sie sicher, dass ein Publishable Key niemals allein den Zugriff gewährt.
  • Dokumentieren Sie das Key-Format, die Scopes und die Rotationsrichtlinie, damit Integratoren diese ohne Rätselraten anwenden können.

Häufige Fehler

  • Schlüssel im Klartext speichern und davon ausgehen, dass die Datenbank niemals geleakt wird.
  • Jede Zeile hashen, um eine Übereinstimmung zu finden, anstatt einen Prefix zu indexieren.
  • Math.random oder eine UUID als Secret zu verwenden und so den Suchraum zu verkleinern.
  • Einen langlebigen Schlüssel in ein Browser-Bundle, eine mobile App oder eine Desktop-Binary einzubetten.
  • Den Schlüssel in einen Query-String zu setzen, wodurch er in Logs und im Verlauf landet.
  • Jedem Schlüssel vollen Zugriff zu gewähren, weil sich das Scoping nach zu viel Arbeit anfühlte.
  • Schlüssel niemals zu rotieren oder ablaufen zu lassen, sodass ein Leak auf unbestimmte Zeit nutzbar bleibt.
  • Widerrufene Zeilen zu löschen und dadurch den Audit-Trail zu verlieren.
  • Vollständige Header oder URLs zu loggen und so Schlüssel in Observability-Tooling zu leaken.
  • Bei einem unbekannten Prefix einen anderen Fehler zurückzugeben als bei einem falschen Secret.

Wie geht es weiter?

API keys sind die einfachsten Anmeldedaten in deinem Werkzeugkasten, und die gleichen Prinzipien – im Ruhezustand hashen, eng scope-en, bei Bedarf rotieren – gelten überall. Wenn du kurzlebige, verifizierbare Tokens benötigst, die Claims enthalten, lies den JWT-Guide. Wenn der Aufrufer im Namen eines Benutzers handelt und eine Zustimmung benötigt, ist OAuth 2.0 das richtige Modell. Die Berechtigungs-Strings eines Keys sind dieselben Bausteine, die auch bei RBAC verwendet werden; dieser Guide ist daher die natürliche Ergänzung für das Scoping. Und da Keys ein idealer Ankerpunkt für Quotas sind, zeigt der Guide zu rate limiting, wie man verhindert, dass eine einzelne Integration die Kapazitäten für alle anderen erschöpft.

In der Praxis

Erstellen, Verifizieren, Scopen, Widerrufen

Die vier Operationen, die jedes API Key System benötigt, und nichts weiter.

routes/keys.ts
import crypto from "node:crypto";

router.post("/keys", requireAuth(), async (req, res) => {
  const { name, scopes } = req.body;
  const secret = crypto.randomBytes(32).toString("base64url");
  const key = `sk_live_${secret}`;

  const [row] = await db.apiKey.insert({
    name,
    ownerId: req.user.id,
    prefix: key.slice(0, 12),
    keyHash: crypto.createHash("sha256").update(key).digest("hex"),
    scopes,
  });

  // The only time the full key is ever returned.
  res.status(201).json({
    id: row.id,
    name: row.name,
    prefix: row.prefix,
    key,
  });
});

Hash speichern, nicht den Key

Ein Hash reicht aus, um einen präsentierten Key zu verifizieren, ist aber für jemanden, der die Tabelle stiehlt, nutzlos. Dies ist dieselbe Logik, die auch für Passwörter gilt.

Bevorzugt
CREATE TABLE api_keys (
  id         bigserial PRIMARY KEY,
  prefix     text NOT NULL,
  key_hash   text NOT NULL,
  scopes     text[] NOT NULL DEFAULT '{}',
  revoked_at timestamptz
);

CREATE INDEX api_keys_prefix_idx ON api_keys (prefix);
Vermeiden
CREATE TABLE api_keys (
  id  bigserial PRIMARY KEY,
  key text NOT NULL
  -- one database dump or backup leak
  -- hands over every customer's key
);

Keys im Header senden

Header werden standardmäßig nicht geloggt, erscheinen nicht im Browserverlauf und leaken nicht über den Referer-Header, wenn eine Seite woanders hin verlinkt.

Bevorzugt
GET /v1/projects HTTP/1.1
Host: api.example.com
Authorization: Bearer sk_live_8f2a9c...
Vermeiden
GET /v1/projects?api_key=sk_live_8f2a9c... HTTP/1.1
Host: api.example.com
# query strings land in access logs, proxies
# and browser history

Abwägungen

Sind API Keys das richtige Credential?

Keys sind simpel und universell, was sowohl ihre Stärke als auch ihre Schwäche ist. Verstehen Sie, worauf Sie verzichten.

Strengths

  • Extrem simpel für Clients

    Es gibt keinen Login-Flow, kein Refresh Token und keinen Clock Skew. Ein Client speichert einen String und sendet ihn – deshalb nutzen alle CLIs und Developer Platforms Keys.

  • Pro Integration widerrufbar

    Jeder Key ist ein benanntes Credential. Sie können den Key eines geleakten Skripts widerrufen, ohne andere Kunden oder Dienste zu beeinträchtigen.

  • Einfach zu scopen und zu messen

    Ein Key ist eine natürliche Einheit für Berechtigungen und Rate Limits. So können Sie einer Integration Read-Only-Zugriff und eine bescheidene Quota geben, ohne neue Logik bauen zu müssen.

Trade-offs

  • Von Natur aus langlebig

    Keys laufen normalerweise nicht ab, daher ist ein geleakter Key gefährlich, bis er bemerkt wird. Kurze Ablaufzeiten, Rotation und Monitoring verringern dieses Zeitfenster.

  • Kein Benutzerkontext

    Ein Key identifiziert einen Dienst, keine Person. Alles, was eine Zustimmung, delegierten Zugriff oder ein pro-Benutzer-Audit erfordert, gehört stattdessen zu OAuth.

  • Schwer geheim zu halten auf dem Client

    Ein Key, der in einer mobilen App oder einem Browser-Bundle eingebettet ist, ist öffentlich. Nutzen Sie für diese Clients einen Backend-Proxy oder ein kurzlebiges Token.

Häufig gestellte Fragen

Häufig gestellte Fragen

Keep learning

Related topics from the roadmap.

$ Lernen Sie jetzt

Bereit, API Key Management zu lernen?

Unser interaktives Tutorial führt Sie Schritt für Schritt durch API Key Management — mit Quizzen und echtem Code, den Sie im Browser ausführen können.