API-Schlüssel

Die API-Schlüssel-Seite eines KI-Produkts, wie in den Konsolen von OpenAI und Anthropic. Schlüssel mit eingeschränkten Berechtigungen und Ablaufdatum erstellen, das Secret einmal sehen mit einem Kopieren, das sich bestätigt, mit Rückgängig widerrufen, direkt umbenennen, mit Übergangsfrist rotieren und die Nutzung pro Schlüssel sehen.

OpenAI, Anthropic und Vercel liefern alle dieselbe API-Schlüssel-Seite: eine Liste von Schlüsseln, von denen du nur das Ende siehst, ein Dialog für einen neuen und eine einzige Gelegenheit, das Secret zu kopieren. API keys ist diese Seite als ein Block, der in jeden Settings-Bereich passt. Er kümmert sich um die Stellen, an denen es schiefgeht: Secrets, die verloren gehen, weil ein Dialog geschlossen wurde, versehentlich widerrufene Schlüssel und Listen, die springen, wenn etwas verschwindet.

Jede Zeile zeigt den Namen des Schlüssels, das maskierte Secret wie hx_live_…a3F9, seine Berechtigungen, das Projekt, wann er erstellt und wann er zuletzt verwendet wurde, oder Never used. Schlüssel, die innerhalb einer Woche ablaufen, bekommen ein Warn-Badge, abgelaufene sagen das ausdrücklich. Übergib usage, und auf breiteren Bildschirmen steht neben dem Schlüssel ein kleines Diagramm der täglichen Anfragen.

Create key öffnet einen Dialog mit Name, Projekt, Berechtigungen (alle, nur lesen oder beschränkt auf die angehakten Ressourcen) und einem Ablauf von 30 Tagen, 90 Tagen, nie oder einem gewählten Datum. Danach wird derselbe Dialog zur Enthüllung: der vollständige Schlüssel in einer Monospace-Box, ein Copy-Button, der sich mit einem Häkchen bestätigt, und eine Checkbox, die abgehakt sein muss, damit Done funktioniert. Wer vor dem Kopieren schließen will, behält den Dialog offen, mit Warnung und einem Button „Close anyway“. Auch der Tab fragt vor dem Schließen nach.

Das Widerrufen fragt zuerst nach und nennt den Schlüssel. Dann klappt die Zeile weg, und ein Toast bietet fünf Sekunden lang Undo an; onRevoke läuft erst, wenn dieses Fenster schließt, sodass Undo nichts von deinem Server braucht. Verlässt man die Seite oder erstellt einen weiteren Schlüssel, werden wartende Widerrufe sofort gesendet. Rotate key tauscht ein neues Secret ein und kann das alte eine Stunde, einen Tag oder eine Woche weiter funktionieren lassen. Direkt umbenennen mit F2 oder Doppelklick, Enter zum Speichern und Escape zum Abbrechen.

  1. Die Pro-Registry zu components.json hinzufügen

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. Token hinzufügen

    Erstelle auf deiner Kontoseite einen Token und trage ihn in .env.local als HEXTAUI_PRO_TOKEN ein.

  3. Den Block hinzufügen

    pnpm dlx shadcn@latest add @hextaui-pro/api-keys

An deine API anbinden

ApiKeySettings zeigt die Schlüssel, die du übergibst, und ruft dich auf, sie zu erstellen, zu widerrufen und umzubenennen. Wirf aus einem beliebigen Callback einen Fehler, und die Person sieht den Grund, ihre Eingabe bleibt erhalten. Zeige ApiKeySettingsSkeleton, während die Liste lädt.

"use client"

import * as React from "react"
import { IconKey } from "@tabler/icons-react"

import { SettingsSection, SettingsShell, type SettingsSectionItem } from "../settings/settings"
import {
  ApiKeySettings,
  ApiKeySettingsSkeleton,
  type ApiKey,
  type ApiKeyInput,
  type ApiKeySecret,
} from "@/components/blocks/api-keys/api-keys"

const sections: SettingsSectionItem[] = [
  { id: "api-keys", label: "API keys", icon: <IconKey /> },
]

async function request<T>(input: string, init?: RequestInit) {
  const response = await fetch(input, {
    ...init,
    headers: { "content-type": "application/json", ...init?.headers },
  })
  if (!response.ok) {
    const body = (await response.json().catch(() => ({}))) as { error?: string }
    throw new Error(body.error ?? "Something went wrong.")
  }
  return (response.status === 204 ? null : await response.json()) as T
}

export function ApiKeysPage() {
  const [keys, setKeys] = React.useState<ApiKey[] | null>(null)
  const [failed, setFailed] = React.useState(false)

  const load = React.useCallback(() => {
    setFailed(false)
    request<{ keys: ApiKey[] }>("/api/keys")
      .then((data) => setKeys(data.keys))
      .catch(() => setFailed(true))
  }, [])

  React.useEffect(load, [load])

  const create = async (input: ApiKeyInput) => {
    const result = await request<ApiKeySecret>("/api/keys", {
      method: "POST",
      body: JSON.stringify(input),
    })
    setKeys((current) => [result.key, ...(current ?? [])])
    return result
  }

  const revoke = async (key: ApiKey) => {
    await request(`/api/keys/${key.id}`, { method: "DELETE" })
    setKeys((current) => current?.filter((item) => item.id !== key.id) ?? null)
  }

  const rename = async (key: ApiKey, name: string) => {
    const updated = await request<ApiKey>(`/api/keys/${key.id}`, {
      method: "PATCH",
      body: JSON.stringify({ name }),
    })
    setKeys((current) => current?.map((item) => (item.id === key.id ? updated : item)) ?? null)
  }

  return (
    <SettingsShell sections={sections} className="h-svh">
      <SettingsSection
        id="api-keys"
        status={failed ? "error" : keys ? "ready" : "loading"}
        skeleton={<ApiKeySettingsSkeleton />}
        onRetry={load}
      >
        <ApiKeySettings
          keys={keys ?? []}
          limit={10}
          onCreate={create}
          onRevoke={revoke}
          onRename={rename}
        />
      </SettingsSection>
    </SettingsShell>
  )
}

Schlüssel auf deinem Server erzeugen

generateApiKey nutzt crypto.getRandomValues und funktioniert deshalb in Node, Edge-Runtimes und Workers. Speichere einen Hash und die letzten vier Zeichen und sende das Secret nur einmal zurück.

import { generateApiKey, keyHint, type ApiKey, type ApiKeyInput } from "@/components/blocks/api-keys/api-keys"

async function sha256(value: string) {
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value))
  return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")
}

export async function createKey(
  input: ApiKeyInput,
  save: (record: ApiKey & { hash: string }) => Promise<void>
) {
  const secret = generateApiKey("hx_live_")
  const key: ApiKey = {
    id: crypto.randomUUID(),
    name: input.name.slice(0, 60),
    hint: keyHint(secret),
    permission: input.permission,
    resources: input.resources,
    project: input.project,
    createdAt: new Date().toISOString(),
    lastUsedAt: null,
    expiresAt: input.expiresAt ? new Date(input.expiresAt).toISOString() : null,
  }
  await save({ ...key, hash: await sha256(secret) })
  return { key, secret }
}

Rotation, eigene Scopes und kein Undo

Übergib onRotate, um Rotate key mit einer Übergangsfrist für das alte Secret hinzuzufügen. Setze undoTimeout auf 0, um im Moment der Bestätigung zu widerrufen, und übergib resources, prefix und snippet passend zu deiner API.

"use client"

import { ApiKeySettings, type ApiKey, type ApiKeyOption, type ApiKeySecret } from "@/components/blocks/api-keys/api-keys"

const resources: ApiKeyOption[] = [
  { value: "chat", label: "Chat", description: "Send messages and read replies." },
  { value: "search", label: "Search", description: "Query your indexed documents." },
]

export function RotatingKeys({
  keys,
  api,
}: {
  keys: ApiKey[]
  api: {
    create: (input: unknown) => Promise<ApiKeySecret>
    revoke: (id: string) => Promise<void>
    rotate: (id: string, expireOldInHours: number) => Promise<ApiKeySecret>
  }
}) {
  return (
    <ApiKeySettings
      keys={keys}
      prefix="sk_test_"
      resources={resources}
      undoTimeout={0}
      expiringSoon={14}
      snippet={`curl https://api.example.com/v1/chat \\\n  -H "Authorization: Bearer $EXAMPLE_KEY"`}
      onCreate={(input) => api.create(input)}
      onRevoke={(key) => api.revoke(key.id)}
      onRotate={(key, { expireOldIn }) => api.rotate(key.id, expireOldIn)}
    />
  )
}

Aufbau

Die Teile, die du zusammensetzt, von außen nach innen.

PartBeschreibung
ApiKeySettingsDie Liste, ihr Header mit Create key, der Hinweis zum Limit, der Leerzustand und alle Dialoge.
ApiKeySettingsSkeletonEin Lade-Platzhalter in der Form der Liste, für die skeleton-Prop von SettingsSection.
generateApiKeyErzeugt einen zufälligen Schlüssel mit Präfix aus crypto.getRandomValues.
keyHintDie letzten Zeichen eines Secrets, zum Speichern und Anzeigen als maskierter Schlüssel.

ApiKeySettings

Akzeptiert auch alle div-Props.

PropTypStandard
keys{ id, name, hint, permission, resources?, project?, createdAt, lastUsedAt?, expiresAt?, usage? }. Datumswerte akzeptieren ein Date, einen ISO-String oder einen Timestamp. usage sind Anfragen pro Tag, die ältesten zuerst.
ApiKey[]–
onCreateErzeuge den Schlüssel und gib ihn mit seinem Secret zurück, das einmal angezeigt wird. Wirf einen Fehler, um die Meldung im Dialog zu zeigen.
(input: ApiKeyInput) => Promise<{ key, secret }>–
onRevokeWiderruft den Schlüssel. Läuft nach dem Undo-Fenster. Bei einem Fehler kommt der Schlüssel mit Try again zurück.
(key) => void | Promise<void>–
onRenameFügt Umbenennen, F2 und Doppelklick hinzu. Der neue Name erscheint sofort und wird zurückgesetzt, wenn das einen Fehler wirft.
(key, name) => void | Promise<void>–
onRotateFügt Rotate key hinzu. expireOldIn gibt an, wie viele Stunden das alte Secret weiter funktioniert, 0 für sofort.
(key, { expireOldIn }) => Promise<{ key, secret }>–
limitDie maximal erlaubte Anzahl Schlüssel. Am Limit wird Create key deaktiviert und der Hinweis sagt warum.
number–
resourcesWas ein eingeschränkter Schlüssel nutzen darf. Standardmäßig models, responses, embeddings, files, agents und usage.
{ value, label, description? }[]–
projectsFügt dem Erstellen-Dialog einen Projekt-Picker hinzu und zeigt das Projekt an jedem Schlüssel.
{ value, label }[]–
prefixWird in maskierten Schlüsseln vor den Hinweis gesetzt, es sei denn, ein Schlüssel hat ein eigenes Präfix.
string"hx_live_"
snippetDer im Leerzustand gezeigte Befehl, mit einem Copy-Button.
string–
undoTimeoutWie lange nach dem Widerrufen Undo angeboten wird, in ms. 0 widerruft sofort nach der Bestätigung.
number5000
expiringSoonTage vor Ablauf, ab denen ein Schlüssel das Warn-Badge bekommt.
number7
nowLegt die für relative Daten verwendete Zeit fest, für Tests und Screenshots.
Date–
PropTypStandard
rowsWie viele Platzhalter-Schlüssel angezeigt werden.
number3

generateApiKey

Gibt das Präfix plus zufällige Buchstaben und Ziffern zurück.

PropTypStandard
prefixWird an den Anfang des Schlüssels gesetzt.
string"hx_live_"
lengthZufällige Zeichen nach dem Präfix, von 8 bis 256.
number40
TasteAktion
F2Benennt den Schlüssel um, dessen Zeile den Fokus hat.
EnterBeim Umbenennen wird der Name gespeichert. Im Erstellen-Dialog wird der Schlüssel erstellt.
EscapeBeim Umbenennen bleibt der alte Name. In der Enthüllung wird einmal gewarnt, wenn der Schlüssel nicht kopiert wurde.
TabWechselt durch die Aktionen jedes Schlüssels, dann durch die Dialogfelder und Buttons.
  • Wird ein Schlüssel erstellt, springt der Fokus zu Copy, und eine Statusmeldung sagt, dass er jetzt kopiert werden muss, weil er nicht noch einmal angezeigt wird.
  • Bestätigungen nennen den Schlüssel und sein maskiertes Secret, und der Fokus beginnt auf Cancel.
  • Nach dem Widerrufen springt der Fokus zum nächsten Schlüssel, zum vorherigen oder zu Create key. Nach Undo kehrt er zum wiederhergestellten Schlüssel zurück. Nach dem Umbenennen kehrt er zu den Aktionen des Schlüssels zurück.
  • Eingeschränkte Badges nennen ihre Ressourcen, das Nutzungsdiagramm wird als Summe vorgelesen, und Umbenennen und Wiederherstellen werden angesagt.
  • Bei reduzierter Bewegung erscheinen und verschwinden Zeilen ohne Einklappen.

Gebaut mit

Die kostenlosen HextaUI-Komponenten, aus denen API keys besteht. Jede lässt sich einzeln installieren.

Code

4 Dateien, hinzugefügt zu components/blocks/api-keys.