Zum Hauptinhalt springen

Wie du Zugangstokens in deinem API-Service oder Backend validierst

Die Validierung von Zugangstokens (Access tokens) ist ein entscheidender Bestandteil der Durchsetzung der rollenbasierten Zugangskontrolle (RBAC) in Logto. Diese Anleitung führt dich durch die Überprüfung von von Logto ausgestellten JWTs in deinem Backend / deiner API, einschließlich der Prüfung von Signatur, Aussteller (Issuer), Zielgruppe (Audience), Ablauf, Berechtigungen (Scopes) und Organisationskontext.

Bevor du beginnst

Schritt 1: Konstanten und Hilfsfunktionen initialisieren

Definiere die notwendigen Konstanten und Hilfsfunktionen in deinem Code, um die Token-Extraktion und -Validierung zu handhaben. Eine gültige Anfrage muss einen Authorization-Header in der Form Bearer <access_token> enthalten.

auth-middleware.ts
import { IncomingHttpHeaders } from 'http';

const JWKS_URI = 'https://your-tenant.logto.app/oidc/jwks';
const ISSUER = 'https://your-tenant.logto.app/oidc';

export class AuthInfo {
constructor(
public sub: string,
public clientId?: string,
public organizationId?: string,
public scopes: string[] = [],
public audience: string[] = []
) {}
}

export class AuthorizationError extends Error {
name = 'AuthorizationError';
constructor(
message: string,
public status = 403
) {
super(message);
}
}

export function extractBearerTokenFromHeaders({ authorization }: IncomingHttpHeaders): string {
const bearerPrefix = 'Bearer ';

if (!authorization) {
throw new AuthorizationError('Authorization-Header fehlt', 401);
}

if (!authorization.startsWith(bearerPrefix)) {
throw new AuthorizationError(`Authorization-Header muss mit "${bearerPrefix}" beginnen`, 401);
}

return authorization.slice(bearerPrefix.length);
}

Schritt 2: Informationen zu deinem Logto-Tenant abrufen

Du benötigst die folgenden Werte, um von Logto ausgestellte Tokens zu validieren:

  • JSON Web Key Set (JWKS) URI: Die URL zu den öffentlichen Schlüsseln von Logto, die zur Überprüfung der JWT-Signaturen verwendet werden.
  • Aussteller (Issuer): Der erwartete Wert des Ausstellers (die OIDC-URL von Logto).

Finde zunächst den Endpunkt deines Logto-Tenants. Du findest ihn an verschiedenen Stellen:

  • In der Logto-Konsole unter EinstellungenDomains.
  • In den Anwendungseinstellungen, die du in Logto konfiguriert hast, unter EinstellungenEndpoints & Credentials.

Abruf vom OpenID Connect Discovery Endpoint

Diese Werte können vom OpenID Connect Discovery Endpoint von Logto abgerufen werden:

https://<your-logto-endpoint>/oidc/.well-known/openid-configuration

Hier ein Beispiel für eine Antwort (andere Felder zur Übersichtlichkeit ausgelassen):

{
"jwks_uri": "https://your-tenant.logto.app/oidc/jwks",
"issuer": "https://your-tenant.logto.app/oidc"
}

Da Logto keine Anpassung der JWKS-URI oder des Issuers erlaubt, kannst du diese Werte in deinem Code fest hinterlegen. Für Produktionsanwendungen wird dies jedoch nicht empfohlen, da dies den Wartungsaufwand erhöht, falls sich Konfigurationen in Zukunft ändern.

  • JWKS URI: https://<your-logto-endpoint>/oidc/jwks
  • Issuer: https://<your-logto-endpoint>/oidc

Schritt 3: Das Token und die Berechtigungen validieren

Nachdem du das Token extrahiert und die OIDC-Konfiguration abgerufen hast, prüfe Folgendes:

  • Signatur: Das JWT muss gültig und von Logto signiert sein (über JWKS).
  • Aussteller (Issuer): Muss mit dem Aussteller deines Logto-Tenants übereinstimmen.
  • Zielgruppe (Audience): Muss mit dem in Logto registrierten Ressourcenindikator der API oder dem Organisationskontext übereinstimmen, falls zutreffend.
  • Ablauf: Das Token darf nicht abgelaufen sein.
  • Berechtigungen (Scopes): Das Token muss die erforderlichen Berechtigungen (Scopes) für deine API / Aktion enthalten. Scopes sind durch Leerzeichen getrennte Zeichenfolgen im scope-Anspruch.
  • Organisationskontext: Wenn du API-Ressourcen auf Organisationsebene schützt, prüfe den Anspruch organization_id.

Siehe JSON Web Token, um mehr über die Struktur und Ansprüche von JWTs zu erfahren.

Was bei jedem Berechtigungsmodell zu prüfen ist

Die Ansprüche und Validierungsregeln unterscheiden sich je nach Berechtigungsmodell:

BerechtigungsmodellAudience-Anspruch (aud)Organisations-Anspruch (organization_id)Zu prüfende Berechtigungen (scope)
Globale API-RessourcenAPI-RessourcenindikatorNicht vorhandenAPI-Ressourcen-Berechtigungen
Organisation (Nicht-API) Berechtigungenurn:logto:organization:<id> (Organisationskontext im aud-Anspruch)Nicht vorhandenOrganisationsberechtigungen
API-Ressourcen auf OrganisationsebeneAPI-RessourcenindikatorOrganisations-ID (muss zur Anfrage passen)API-Ressourcen-Berechtigungen

Für Nicht-API-Organisationsberechtigungen wird der Organisationskontext durch den aud-Anspruch dargestellt (z. B. urn:logto:organization:abc123). Der Anspruch organization_id ist nur bei Tokens für API-Ressourcen auf Organisationsebene vorhanden.

tipp:

Validiere immer sowohl die Berechtigungen (Scopes) als auch den Kontext (Audience, Organisation) für sichere Multi-Tenant-APIs.

Die Validierungslogik hinzufügen

Wir verwenden jose in diesem Beispiel, um das JWT zu validieren. Installiere es, falls du es noch nicht getan hast:

npm install jose

Oder verwende deinen bevorzugten Paketmanager (z. B. pnpm oder yarn).

Füge zunächst diese gemeinsamen Hilfsfunktionen hinzu, um die JWT-Validierung zu handhaben:

jwt-validator.ts
import { createRemoteJWKSet, jwtVerify, JWTPayload } from 'jose';
import { AuthInfo, AuthorizationError } from './auth-middleware.js';

const jwks = createRemoteJWKSet(new URL(JWKS_URI));

export async function validateJwtToken(token: string): Promise<JWTPayload> {
const { payload } = await jwtVerify(token, jwks, {
issuer: ISSUER,
});

verifyPayload(payload);
return payload;
}

export function createAuthInfo(payload: JWTPayload): AuthInfo {
const scopes = (payload.scope as string)?.split(' ') ?? [];
const audience = Array.isArray(payload.aud) ? payload.aud : payload.aud ? [payload.aud] : [];

return new AuthInfo(
payload.sub!,
payload.client_id as string,
payload.organization_id as string,
scopes,
audience
);
}

function verifyPayload(payload: JWTPayload): void {
// Implementiere hier deine Verifizierungslogik basierend auf dem Berechtigungsmodell
// Dies wird im Abschnitt zu den Berechtigungsmodellen unten gezeigt
}

Implementiere dann das Middleware, um das Zugangstoken zu überprüfen:

auth-middleware.ts
import { Request, Response, NextFunction } from 'express';
import { extractBearerTokenFromHeaders, AuthorizationError } from './auth-middleware.js';
import { validateJwtToken, createAuthInfo } from './jwt-validator.js';

// Erweitere das Express Request-Interface um auth
declare global {
namespace Express {
interface Request {
auth?: AuthInfo;
}
}
}

export async function verifyAccessToken(req: Request, res: Response, next: NextFunction) {
try {
const token = extractBearerTokenFromHeaders(req.headers);
const payload = await validateJwtToken(token);

// Auth-Info im Request für generische Nutzung speichern
req.auth = createAuthInfo(payload);

next();
} catch (err: any) {
return res.status(err.status ?? 401).json({ error: err.message });
}
}

Implementiere gemäß deinem Berechtigungsmodell die entsprechende Verifizierungslogik in jwt-validator.ts:

jwt-validator.ts
function verifyPayload(payload: JWTPayload): void {
// Überprüfe, ob der Audience-Anspruch mit deinem API-Ressourcenindikator übereinstimmt
const audiences = Array.isArray(payload.aud) ? payload.aud : payload.aud ? [payload.aud] : [];
if (!audiences.includes('https://your-api-resource-indicator')) {
throw new AuthorizationError('Ungültige Zielgruppe (audience)');
}

// Überprüfe erforderliche Berechtigungen für globale API-Ressourcen
const requiredScopes = ['api:read', 'api:write']; // Ersetze durch deine tatsächlich erforderlichen Berechtigungen
const scopes = (payload.scope as string)?.split(' ') ?? [];
if (!requiredScopes.every((scope) => scopes.includes(scope))) {
throw new AuthorizationError('Unzureichende Berechtigung (scope)');
}
}

Schritt 4: Middleware auf deine API anwenden

Wende die Middleware auf deine geschützten API-Routen an.

app.ts
import express from 'express';
import { verifyAccessToken } from './auth-middleware.js';

const app = express();

app.get('/api/protected', verifyAccessToken, (req, res) => {
// Greife direkt über req.auth auf Authentifizierungsinformationen zu
res.json({ auth: req.auth });
});

app.get('/api/protected/detailed', verifyAccessToken, (req, res) => {
// Deine geschützte Endpunkt-Logik
res.json({
auth: req.auth,
message: 'Geschützte Daten erfolgreich abgerufen',
});
});

app.listen(3000);

Schritt 5: Deine Implementierung testen

Teste deine API mit gültigen und ungültigen Tokens, um sicherzustellen:

  • Gültige Tokens werden akzeptiert und gewähren Zugriff.
  • Gib 401 Unauthorized für ungültige / fehlende Tokens zurück. Gib 403 Forbidden für gültige Tokens zurück, denen die erforderlichen Berechtigungen oder der Kontext fehlen.
Token-Ansprüche anpassen JSON Web Token (JWT)

OpenID Connect Discovery

RFC 8707: Ressourcenindikatoren