# Authentifizierung

> API-Schlüssel der Organisation, ihre Scopes, was ein Schlüssel darf und wie Sie ihn rotieren.

Jede Operation der API braucht einen API-Schlüssel Ihrer Organisation. Ausgenommen sind nur die öffentlichen
Abrufe von Pässen (Tag `public`), die ohne Schlüssel auskommen.

## Schlüssel senden

Senden Sie den Schlüssel im Header `Authorization` mit dem Schema `Bearer`:

```http title="Header"
Authorization: Bearer bp_live_...
```

```bash title="cURL"
curl -sS "$BATTERYPASS_URL/api/v1/registry" \
  -H "Authorization: Bearer $BATTERYPASS_API_KEY"
```

```ts title="TypeScript"
const res = await fetch(`${process.env.BATTERYPASS_URL}/api/v1/registry`, {
  headers: { Authorization: `Bearer ${process.env.BATTERYPASS_API_KEY}` },
});
console.log(res.status, await res.json());
```

```python title="Python"
import os

import requests

res = requests.get(
    f"{os.environ['BATTERYPASS_URL']}/api/v1/registry",
    headers={"Authorization": f"Bearer {os.environ['BATTERYPASS_API_KEY']}"},
    timeout=30,
)
print(res.status_code, res.json())
```

```go title="Go"
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest(http.MethodGet, os.Getenv("BATTERYPASS_URL")+"/api/v1/registry", nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("BATTERYPASS_API_KEY"))
	res, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(res.Status, string(body))
}
```

Die API liest Schlüssel nur aus diesem Header. Schreiben Sie einen Schlüssel nie in eine URL: Query-Strings
und Pfade landen in Logs, Browserverläufen und Proxys.

## Schlüssel der Organisation

- Ein Schlüssel gehört der **Organisation**, nicht einem Mitglied. Er funktioniert weiter, wenn das Mitglied,
  das ihn erstellt hat, die Organisation verlässt.
- **Eigentümer** erstellen und widerrufen Schlüssel im Portal unter **Einstellungen → API-Schlüssel**. Die
  Verwaltung von Schlüsseln ist nicht Teil der API.
- Das Geheimnis besteht aus `bp_live_` und 43 weiteren Zeichen. Es wird **einmal** angezeigt, beim Erstellen.
  BatteryPass speichert nur einen SHA-256-Hash und zeigt die ersten 12 Zeichen als Präfix, damit Sie Schlüssel
  unterscheiden können.
- Eine Organisation hat höchstens **25 aktive Schlüssel**. Ein widerrufener Schlüssel bleibt mit dem Zeitpunkt
  des Widerrufs gelistet und funktioniert nie wieder.
- Schlüssel haben **kein Ablaufdatum**. Sie beenden einen Schlüssel, indem Sie ihn widerrufen (siehe
  [Schlüssel rotieren](#schlüssel-rotieren)).

## Scopes

Ein Schlüssel hat einen oder mehrere Scopes. Jede Operation nennt den benötigten Scope in `x-api-key-scope` im
Vertrag und auf ihrer Seite in der [API-Referenz](https://passport.kabin.sh/docs/de/api-reference).

| Scope       | Erlaubt                                                                                                                                                                                                                                                              | Operationen                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `read`      | Lesen, was ein Mitglied mit Lesezugriff in der Organisation sieht: Modelle, Versionen, Einheiten, Werte mit Herkunft und Vollständigkeit, Nachweise, Standorte, Pässe mit Versionen, Unterschieden und Exporten, Lebenszyklusereignisse, Zustandsdaten, das Register | Jede Operation mit `x-api-key-scope: read`                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `condition` | Zustandsdaten und Lebenszyklusereignisse melden                                                                                                                                                                                                                      | [`updateCondition`](https://passport.kabin.sh/docs/de/api-reference/lifecycle/update-condition), [`createLifecycleEvent`](https://passport.kabin.sh/docs/de/api-reference/lifecycle/create-lifecycle-event)                                                                                                                                                                                                                                                                  |
| `import`    | Tabellenimporte von Einheiten bereitstellen: hochladen, zuordnen, prüfen und verfolgen; Mitglieder übernehmen und geben sie im Portal frei (ADR-026)                                                                                                                 | [`createImport`](https://passport.kabin.sh/docs/de/api-reference/imports/create-import), [`updateImportMapping`](https://passport.kabin.sh/docs/de/api-reference/imports/update-import-mapping), [`validateImport`](https://passport.kabin.sh/docs/de/api-reference/imports/validate-import), [`getImport`](https://passport.kabin.sh/docs/de/api-reference/imports/get-import), [`listImports`](https://passport.kabin.sh/docs/de/api-reference/imports/list-imports) |

Die Scopes schließen einander nicht ein. Ein Batteriemanagementsystem, das Messwerte meldet und außerdem den
aktuellen Zustand mit [`getCondition`](https://passport.kabin.sh/docs/de/api-reference/lifecycle/get-condition) liest, braucht beide.

Alle Operationen mit ihrem Scope listet der Vertrag Ihrer Instanz:

```bash title="cURL"
curl -sS "$BATTERYPASS_URL/api/v1/openapi.json" \
  | jq -r '.paths[][] | objects | select(has("operationId")) | "\(.operationId)\t\(.["x-api-key-scope"] // "public")"'
```

## Was ein Schlüssel darf

Ein Schlüssel handelt in seiner Organisation mit den Rechten eines **Mitglieds mit Lesezugriff** (Rolle
`viewer`). Wo die Beschreibung einer Operation eine Rolle nennt („any member“, „editors and above“), zählt ein
Schlüssel als `viewer`. Die Ausnahmen: Die beiden Operationen des Scopes `condition` akzeptieren einen
Schlüssel mit diesem Scope, und die Importoperationen des Scopes `import` einen Schlüssel mit jenem.

Ein Schlüssel kann nie:

- deklarierte Daten anlegen oder ändern (Modelle, Einheiten, Werte, Dokumente);
- etwas prüfen, freigeben, veröffentlichen oder zurückziehen, auch keinen Import übernehmen oder freigeben, selbst wenn er ihn bereitgestellt hat;
- Mitglieder, API-Schlüssel oder Einstellungen der Organisation verwalten.

Das bleibt bei den Mitgliedern im Portal, damit das Vier-Augen-Prinzip und die Prüfungen vor der
Veröffentlichung ihre Bedeutung behalten. Was ein Schlüssel meldet, wird mit der Quelle `api` gespeichert, und das
Lebenszyklusereignis der Meldung nennt den Schlüssel im Feld `api_key`.

## Sitzung und Schlüssel zusammen

Eine Anfrage, die zugleich ein Sitzungs-Cookie des Portals und einen API-Schlüssel trägt, wird mit `401`
abgelehnt. Das passiert etwa, wenn Sie Anfragen in einem Browser ausprobieren, in dem Sie im Portal angemeldet
sind. Verwenden Sie einen Client ohne Cookies, zum Beispiel `curl` oder Ihren Servercode.

## 401 oder 403

| Status | `code`            | Ursache                                                                                                                                  | Was tun                                                                                                            |
| ------ | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `401`  | `unauthenticated` | Der Header fehlt oder ist fehlerhaft, der Schlüssel ist unbekannt oder widerrufen, oder die Anfrage trägt zusätzlich ein Sitzungs-Cookie | Header und Umgebungsvariable prüfen; bei einem widerrufenen Schlüssel einen Eigentümer um einen neuen bitten       |
| `403`  | `forbidden`       | Dem Schlüssel fehlt der Scope der Operation, oder die Aktion braucht mehr als Lesezugriff                                                | Einen Schlüssel mit passendem Scope verwenden; Aktionen über den Lesezugriff hinaus erledigen Mitglieder im Portal |
| `429`  | `rate_limited`    | Zu viele Versuche mit unbekannten oder widerrufenen Schlüsseln von Ihrer Client-Adresse                                                  | Anhalten und den Schlüssel korrigieren; siehe [Ratenlimits](https://passport.kabin.sh/docs/de/rate-limits)       |

> [!WARNING]
> Wiederholen Sie einen `401` nicht in einer Schleife. Anfragen mit unbekannten oder widerrufenen Schlüsseln
> sind auf 30 Versuche pro 15 Minuten und Client-Adresse begrenzt; danach erhalten weitere Versuche mit einem
> solchen Schlüssel `429` statt `401`, bis das Zeitfenster endet.

## Schlüssel rotieren

Schlüssel laufen nicht ab, also rotieren Sie sie selbst: in einem festen Rhythmus, sofort bei Verdacht auf ein
Leck und wenn jemand ausscheidet, der den Schlüssel kannte.

1. **Neuen Schlüssel erstellen**

   Ein Eigentümer erstellt unter **Einstellungen → API-Schlüssel** einen neuen Schlüssel mit denselben Scopes.

2. **Neuen Schlüssel einsetzen**

   Legen Sie das neue Geheimnis in Ihrem Secret-Speicher ab und starten oder konfigurieren Sie das nutzende
   System neu.

3. **Prüfen, dass der alte Schlüssel ruht**

   Im Portal ändert sich die Angabe „Zuletzt genutzt“ des alten Schlüssels nicht mehr. Sie wird höchstens
   einmal pro Minute aktualisiert.

4. **Alten Schlüssel widerrufen**

   Der Widerruf wirkt sofort und lässt sich nicht rückgängig machen.

## Schlüssel geheim halten

- Verwenden Sie Schlüssel nur in serverseitigen Systemen, nie in Browsercode, mobilen Apps oder öffentlichen
  Repositorys.
- Erstellen Sie für jedes System einen eigenen Schlüssel, damit Sie ihn einzeln widerrufen können.
- Geben Sie jedem Schlüssel nur die Scopes, die er braucht.
- Das Präfix `bp_live_` hilft Secret-Scannern, geleakte Schlüssel zu finden.

Mehr dazu unter [Sichere Schlüsselverwaltung](https://passport.kabin.sh/docs/de/guides/secure-key-handling) und
[Sicherheit](https://passport.kabin.sh/docs/de/general/security).
