
# Eine Express-API absichern

Diese Schnellstartanleitung sichert eine Express-5-API mit Zugriffstoken von KeenAuth ab. Sie fügen die `expressAuth`-Middleware des Node.js-SDKs hinzu, verlangen einen Scope für eine Route und testen die API offline mit dem Test-Kit des SDKs. Express ist keine Abhängigkeit des SDKs; die Middleware funktioniert auch mit Express 4.

Die API hat drei Endpunkte:

| Endpunkt | Erfordert |
|---|---|
| `GET /api/public` | nichts |
| `GET /api/private` | ein gültiges Zugriffstoken für Ihre API |
| `GET /api/messages` | ein gültiges Zugriffstoken mit dem `messages:read`-Scope |

## Vor dem Start

- Eine KeenAuth-Umgebung. Ihr Aussteller sieht aus wie `https://your-environment.eu.keenauth.app`.
- Eine API, die in der Umgebung registriert ist ([API anlegen](/docs/api/management/apis/create)). Ihr `identifier`, zum Beispiel `https://api.example.com`, ist die Zielgruppe (Audience) der Token, die Ihre API akzeptiert.
- Der `messages:read`-Scope für diese API, gewährt der Anwendung, die sie aufruft.
- Node.js 22 oder neuer.

## 1. SDK installieren

```sh
npm install @keenauth/node express@5.2.1
```

Das SDK ist noch nicht veröffentlicht. Bis dahin verlinkt das Beispiel auf den SDK-Quellcode in seinem `package.json`.

## 2. Konfigurieren

Die API liest ihre Konfiguration ausschließlich aus Umgebungsvariablen. Legen Sie sie in einer `.env`-Datei ab, die `npm start` mit `node --env-file-if-exists=.env` lädt, und committen Sie diese Datei niemals.

| Variable | Erforderlich | Bedeutung |
|---|---|---|
| `KEENAUTH_ISSUER` | ja | Der Aussteller Ihrer Umgebung |
| `KEENAUTH_AUDIENCE` | ja | Der Bezeichner Ihrer API |
| `PUBLIC_BASE_URL` | ja | Die URL, die Clients aufrufen (siehe [Hinter einem Proxy](#hinter-einem-proxy)) |
| `KEENAUTH_WEBHOOK_SECRET` | empfohlen | Das `idp_whsec_`-Signaturgeheimnis des Webhook-Endpunkts, der `token.revoked` zustellt (zwei, durch Komma getrennt, während Sie es rotieren). Ohne dieses erfährt die API nie von widerrufenen Token |
| `PORT` | nein | Listen-Port, Standard `8080` |

## 3. Routen absichern

`createApp` erstellt den Schutz und die Routen. `expressAuth(protector)` verlangt ein gültiges Token; mit `{ scopes }` verlangt es zusätzlich Scopes. Der Handler liest die geprüften Claims aus `req.auth`.

```js
export function createApp({ verifier, publicBaseUrl, revocation, webhookSecrets = [] }) {
  const protector = createProtector(verifier, {
    publicBaseUrl,
    revocation,
    // The error never contains the token or claim values; never log the Authorization or DPoP header.
    onError: (err) => console.warn(`rejected: ${err.message}`),
  });

  const app = express();
  app.disable("x-powered-by");

  // No token needed.
  app.get("/api/public", (req, res) => {
    res.json({ message: "Hello from a public endpoint." });
  });

  // Any valid access token for this API.
  app.get("/api/private", expressAuth(protector), (req, res) => {
    res.json({ message: "Hello from a private endpoint.", sub: req.auth.subject });
  });

  // A valid access token that also carries the messages:read scope.
  app.get("/api/messages", expressAuth(protector, { scopes: ["messages:read"] }), (req, res) => {
    res.json({ messages: ["Hello from a scoped endpoint."] });
  });

  // token.revoked deliveries from KeenAuth; the handler reads the raw body itself, so no JSON parser runs before it.
  if (webhookSecrets.length > 0) {
    app.post(WEBHOOK_PATH, ...createWebhookHandler({ secrets: webhookSecrets, revocation }));
  } else {
    console.warn("KEENAUTH_WEBHOOK_SECRET is not set: token.revoked events are not received, so revoked tokens stay valid until they expire");
  }

  return app;
}
```

`server.js` erstellt aus der Konfiguration den echten Verifier. Der Verifier lädt die Signaturschlüssel von Ihrer Umgebung, sobald er das erste Token sieht.

```js
const issuer = process.env.KEENAUTH_ISSUER;
const audience = process.env.KEENAUTH_AUDIENCE;
// The URL clients call: DPoP proofs are checked against it, never against the Host header.
const publicBaseUrl = process.env.PUBLIC_BASE_URL;
if (!issuer || !audience || !publicBaseUrl) {
  console.error("KEENAUTH_ISSUER, KEENAUTH_AUDIENCE and PUBLIC_BASE_URL are required");
  process.exit(1);
}

// createVerifier refuses an issuer under the reserved .invalid domain, so a test token can never pass here.
const verifier = createVerifier({ issuer, audience });
const revocation = createMemoryRevocationCache();
// The signing secret (idp_whsec_...) of the webhook endpoint that delivers token.revoked; comma separated while rotating.
const webhookSecrets = (process.env.KEENAUTH_WEBHOOK_SECRET ?? "").split(",").map((s) => s.trim()).filter(Boolean);
const app = createApp({ verifier, publicBaseUrl, revocation, webhookSecrets });

const port = Number(process.env.PORT || 8080);
app.listen(port, () => console.log(`listening on port ${port}`));
```

## 4. Ausführen und API aufrufen

```sh
npm start
```

Für einen ersten Aufruf holen Sie ein Token mit dem Client-Credentials-Grant einer Machine-to-Machine-Anwendung, die den `messages:read`-Scope auf Ihrer API hat:

```sh
ACCESS_TOKEN=$(curl -s -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=client_credentials \
  -d resource=https://api.example.com \
  -d scope=messages:read \
  https://your-environment.eu.keenauth.app/oauth2/token | jq -r .access_token)

curl -i http://localhost:8080/api/messages -H "Authorization: Bearer $ACCESS_TOKEN"
```

## 5. Ohne Netzwerk testen

`@keenauth/node/testing` erzeugt Token, die mit einem öffentlichen Testschlüssel signiert sind, und `createTestVerifier` akzeptiert nur diese Token. Die Tests übergeben diesen Verifier an `createApp` und rufen die App auf einem lokalen Port auf, daher benötigen sie weder Netzwerk noch ein KeenAuth-Konto. Siehe [Offline-Test-Kit](/docs/sdk/testing).

```js
test("scope check: 403 insufficient_scope without messages:read, 200 with it", async () => {
  await expectProblem(await get("/api/messages", bearer(mintAccessToken(AUDIENCE))), 403, "insufficient_scope", /scope="messages:read"/);
  const ok = await get("/api/messages", bearer(mintAccessToken(AUDIENCE, { claims: { scope: "profile messages:read" } })));
  assert.equal(ok.status, 200);
});
```

```sh
npm test
```

Die Tests des Beispiels prüfen, dass ein gültiges Token akzeptiert wird und dass ein fehlendes, abgelaufenes oder für eine andere Zielgruppe ausgestelltes Token, ein fehlender Scope, ein Token in der URL-Abfrage und ein widerrufenes Token mit den unten stehenden Statuscodes abgelehnt werden. Sie senden außerdem ein DPoP-gebundenes Token mit einem Nachweis für `PUBLIC_BASE_URL` und prüfen, dass `server. The tests also send signed `token.revoked` deliveries: a valid one revokes the token, a forged, tampered or stale one gets `400` and revokes nothing, and a repeated one has one effect.js` mit dem Test-Aussteller den Start verweigert.

## Antworten

| Anfrage | Status | `code` im Body |
|---|---|---|
| Kein `Authorization`-Header | `401` | `missing_token` |
| Abgelaufenes Token, Token für eine andere API, ungültige Signatur, widerrufenes Token | `401` | `invalid_token` |
| Token ohne den erforderlichen Scope | `403` | `insufficient_scope` |
| Token in der URL-Abfrage (`?access_token=`) | `400` | `invalid_request` |

Der Body ist `application/problem+json`, und der `WWW-Authenticate`-Header enthält die Standard-Challenge. Die vollständige Tabelle steht unter [API mit Middleware absichern](/docs/sdk/protect-api#antworten).

## Hinter einem Proxy

Wenn Clients DPoP verwenden, prüft die Middleware, dass jeder Nachweis für die URL erstellt wurde, die der Client aufgerufen hat. Setzen Sie `PUBLIC_BASE_URL` auf die externe URL, die Clients aufrufen, zum Beispiel `https://api.example.com`, auch hinter einem Proxy oder Load Balancer. Die Variable ist erforderlich und muss `https` verwenden (`http` nur für `localhost`, `127.0.0.1` oder `[::1]`); ohne sie startet die Anwendung nicht. Die Middleware vertraut `X-Forwarded-*`-Headern niemals, unabhängig von der Einstellung `trust proxy` in Express.

## Widerrufene Token

Ein Zugriffstoken bleibt gültig, bis es abläuft. Daher teilt KeenAuth Ihrer API mit, wenn Token widerrufen werden. Das Beispiel übergibt dem Schutz einen In-Memory-Widerrufscache und stellt unter `POST /webhooks/keenauth` (`webhook.js`) einen `token.revoked`-Webhook bereit, der diesen Cache füttert.

1. Legen Sie im Dashboard einen Webhook-Endpunkt mit der URL `https://<your host>/webhooks/keenauth` an, der `token.revoked` abonniert, und exportieren Sie sein Signaturgeheimnis als `KEENAUTH_WEBHOOK_SECRET`.
2. Der Handler prüft die Signatur mit dem SDK auf dem rohen Body und antwortet mit `400`, wenn sie ungültig ist oder der Zeitstempel mehr als fünf Minuten abweicht. Er ordnet den Selektor des Ereignisses (`access_token`, `session`, `user`, `client`, `org`, `org_user`, `client_user`) einer Widerrufsmarkierung zu und antwortet mit `204`; die Middleware lehnt danach jedes passende Token ab, das vor `not_before` ausgestellt wurde.
3. KeenAuth liefert mindestens einmal aus, daher wendet der Handler jedes `webhook-id` genau einmal an. Kann die Markierung nicht gespeichert werden, antwortet er mit `503`, und KeenAuth liefert das Ereignis erneut. Andere Ereignistypen und Selektoren, auf die kein Zugriffstoken passen kann (`refresh_family`, `api`), ignoriert er.

Der Handler protokolliert die Ereignis-ID, den Selektortyp und den Grund, nie den Body, eine Signatur oder das Geheimnis. Jeder Prozess hat seinen eigenen Cache; bei mehreren Instanzen legen Sie die Markierungen in einen gemeinsamen Speicher hinter derselben `revoke`-Methode. Siehe [Widerrufscache](/docs/sdk/protect-api#widerrufs-cache-tokenrevoked).

## Nächste Schritte

- [API mit Middleware absichern](/docs/sdk/protect-api): Fastify, Step-up-Anforderungen, DPoP und alle Optionen.
- [Token prüfen](/docs/sdk/token-verification): der Verifier unter der Middleware.
- [Offline-Test-Kit](/docs/sdk/testing).
