Wie du Zugangstokens in deinem API-Dienst oder Backend validierst
Die Validierung von Zugangstokens 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 / API, einschließlich der Prüfung von Signatur, Aussteller (Issuer), Zielgruppe (Audience), Ablauf, Berechtigungen (Scopes) und Organisationskontext.
Bevor du beginnst
- Diese Anleitung setzt voraus, dass du mit den RBAC-Konzepten von Logto vertraut bist.
 - Wenn du API-Ressourcen schützt, wird vorausgesetzt, dass du die Anleitung Globale API-Ressourcen schützen durchgearbeitet hast.
 - Wenn du In-App-Features oder Workflows (nicht-API-Berechtigungen) schützt, wird vorausgesetzt, dass du die Anleitung Organisations-(Nicht-API-)Berechtigungen schützen durchgearbeitet hast.
 - Wenn du organisationsbezogene API-Ressourcen schützt, wird vorausgesetzt, dass du die Anleitung Organisationsbezogene API-Ressourcen schützen durchgearbeitet hast.
 
Schritt 1: Konstanten und Hilfsfunktionen initialisieren
Definiere die notwendigen Konstanten und Hilfsfunktionen in deinem Code, um die Extraktion und Validierung von Tokens zu handhaben. Eine gültige Anfrage muss einen Authorization-Header in der Form Bearer <Zugangstoken (Access token)> enthalten.
- Node.js
 - Python
 - Go
 - Java
 - .NET
 - PHP
 - Ruby
 - Rust
 
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);
}
JWKS_URI = 'https://your-tenant.logto.app/oidc/jwks'
ISSUER = 'https://your-tenant.logto.app/oidc'
class AuthInfo:
    def __init__(self, sub: str, client_id: str = None, organization_id: str = None,
                 scopes: list = None, audience: list = None):
        self.sub = sub
        self.client_id = client_id
        self.organization_id = organization_id
        self.scopes = scopes or []
        self.audience = audience or []
    def to_dict(self):
        return {
            'sub': self.sub,
            'client_id': self.client_id,
            'organization_id': self.organization_id,
            'scopes': self.scopes,
            'audience': self.audience
        }
class AuthorizationError(Exception):
    def __init__(self, message: str, status: int = 403):
        self.message = message
        self.status = status
        super().__init__(self.message)
def extract_bearer_token_from_headers(headers: dict) -> str:
    """
    Extrahiere das Bearer-Token aus den HTTP-Headern.
    Hinweis: FastAPI und Django REST Framework verfügen über eine eingebaute Token-Extraktion,
    daher ist diese Funktion hauptsächlich für Flask und andere Frameworks gedacht.
    """
    authorization = headers.get('authorization') or headers.get('Authorization')
    if not authorization:
        raise AuthorizationError('Authorization-Header fehlt', 401)
    if not authorization.startswith('Bearer '):
        raise AuthorizationError('Authorization-Header muss mit "Bearer " beginnen', 401)
    return authorization[7:]  # Entferne das Präfix 'Bearer '
package main
import (
    "fmt"
    "net/http"
    "strings"
)
const (
    JWKS_URI = "https://your-tenant.logto.app/oidc/jwks"
    ISSUER   = "https://your-tenant.logto.app/oidc"
)
type AuthorizationError struct {
    Message string
    Status  int
}
func (e *AuthorizationError) Error() string {
    return e.Message
}
func NewAuthorizationError(message string, status ...int) *AuthorizationError {
    statusCode := http.StatusForbidden // Standardmäßig 403 Verboten
    if len(status) > 0 {
        statusCode = status[0]
    }
    return &AuthorizationError{
        Message: message,
        Status:  statusCode,
    }
}
func extractBearerTokenFromHeaders(r *http.Request) (string, error) {
    const bearerPrefix = "Bearer "
    authorization := r.Header.Get("Authorization")
    if authorization == "" {
        return "", NewAuthorizationError("Authorization-Header fehlt", http.StatusUnauthorized)
    }
    if !strings.HasPrefix(authorization, bearerPrefix) {
        return "", NewAuthorizationError(fmt.Sprintf("Authorization-Header muss mit \"%s\" beginnen", bearerPrefix), http.StatusUnauthorized)
    }
    return strings.TrimPrefix(authorization, bearerPrefix), nil
}
public class AuthorizationException extends RuntimeException {
    private final int statusCode;
    public AuthorizationException(String message) {
        this(message, 403); // Standardmäßig 403 Verboten (Forbidden)
    }
    public AuthorizationException(String message, int statusCode) {
        super(message);
        this.statusCode = statusCode;
    }
    public int getStatusCode() {
        return statusCode;
    }
}
namespace YourApiNamespace
{
    public static class AuthConstants
    {
        public const string Issuer = "https://your-tenant.logto.app/oidc";
    }
}
namespace YourApiNamespace.Exceptions
{
    public class AuthorizationException : Exception
    {
        public int StatusCode { get; }
        public AuthorizationException(string message, int statusCode = 403) : base(message)
        {
            StatusCode = statusCode;
        }
    }
}
<?php
class AuthConstants
{
    public const JWKS_URI = 'https://your-tenant.logto.app/oidc/jwks';
    public const ISSUER = 'https://your-tenant.logto.app/oidc';
}
<?php
class AuthInfo
{
    public function __construct(
        public readonly string $sub,
        public readonly ?string $clientId = null,
        public readonly ?string $organizationId = null,
        public readonly array $scopes = [],
        public readonly array $audience = []
    ) {}
    public function toArray(): array
    {
        return [
            'sub' => $this->sub,
            'client_id' => $this->clientId,
            'organization_id' => $this->organizationId,
            'scopes' => $this->scopes,
            'audience' => $this->audience,
        ];
    }
}
<?php
class AuthorizationException extends Exception
{
    public function __construct(
        string $message,
        public readonly int $statusCode = 403
    ) {
        parent::__construct($message);
    }
}
<?php
trait AuthHelpers
{
    protected function extractBearerToken(array $headers): string
    {
        $authorization = $headers['authorization'][0] ?? $headers['Authorization'][0] ?? null;
        if (!$authorization) {
            throw new AuthorizationException('Autorisierungs-Header fehlt (Authorization header is missing)', 401);
        }
        if (!str_starts_with($authorization, 'Bearer ')) {
            throw new AuthorizationException('Autorisierungs-Header muss mit "Bearer " beginnen (Authorization header must start with "Bearer ")', 401);
        }
        return substr($authorization, 7); // Entfernt das Präfix 'Bearer ' (Remove 'Bearer ' prefix)
    }
}
module AuthConstants
  JWKS_URI = 'https://your-tenant.logto.app/oidc/jwks'
  ISSUER = 'https://your-tenant.logto.app/oidc'
end
class AuthInfo
  attr_accessor :sub, :client_id, :organization_id, :scopes, :audience
  def initialize(sub, client_id = nil, organization_id = nil, scopes = [], audience = [])
    @sub = sub
    @client_id = client_id
    @organization_id = organization_id
    @scopes = scopes
    @audience = audience
  end
  def to_h
    {
      sub: @sub,
      client_id: @client_id,
      organization_id: @organization_id,
      scopes: @scopes,
      audience: @audience
    }
  end
end
class AuthorizationError < StandardError
  attr_reader :status
  def initialize(message, status = 403)
    super(message)
    @status = status
  end
end
module AuthHelpers
  def extract_bearer_token(request)
    authorization = request.headers['Authorization']
    raise AuthorizationError.new('Authorization header fehlt', 401) unless authorization
    raise AuthorizationError.new('Authorization header muss mit "Bearer " beginnen', 401) unless authorization.start_with?('Bearer ')
    authorization[7..-1] # Entferne das Präfix 'Bearer '
  end
end
use serde::{Deserialize, Serialize};
use std::fmt;
pub const JWKS_URI: &str = "https://your-tenant.logto.app/oidc/jwks";
pub const ISSUER: &str = "https://your-tenant.logto.app/oidc";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthInfo {
    pub sub: String,
    pub client_id: Option<String>,
    pub organization_id: Option<String>,
    pub scopes: Vec<String>,
    pub audience: Vec<String>,
}
impl AuthInfo {
    pub fn new(
        sub: String,
        client_id: Option<String>,
        organization_id: Option<String>,
        scopes: Vec<String>,
        audience: Vec<String>,
    ) -> Self {
        Self {
            sub,
            client_id,
            organization_id,
            scopes,
            audience,
        }
    }
}
#[derive(Debug)]
pub struct AuthorizationError {
    pub message: String,
    pub status_code: u16,
}
impl AuthorizationError {
    pub fn new(message: impl Into<String>) -> Self {
        Self {
            message: message.into(),
            status_code: 403,
        }
    }
    pub fn with_status(message: impl Into<String>, status_code: u16) -> Self {
        Self {
            message: message.into(),
            status_code,
        }
    }
}
impl fmt::Display for AuthorizationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.message)
    }
}
impl std::error::Error for AuthorizationError {}
pub fn extract_bearer_token(authorization: Option<&str>) -> Result<&str, AuthorizationError> {
    let auth_header = authorization.ok_or_else(|| {
        AuthorizationError::with_status("Authorization-Header fehlt", 401)
    })?;
    if !auth_header.starts_with("Bearer ") {
        return Err(AuthorizationError::with_status(
            "Authorization-Header muss mit \"Bearer \" beginnen",
            401,
        ));
    }
    Ok(&auth_header[7..]) // Entferne das Präfix 'Bearer '
}
Schritt 2: Informationen über deinen 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 von JWT-Signaturen verwendet wird.
 - Aussteller (Issuer): Der erwartete Ausstellerwert (die OIDC-URL von Logto).
 
Zuerst finde den Endpunkt deines Logto-Tenants. Du findest ihn an verschiedenen Stellen:
- In der Logto-Konsole unter Einstellungen → Domains.
 - In den Anwendungseinstellungen, die du in Logto konfiguriert hast, unter Einstellungen → Endpoints & Credentials.
 
Abrufen vom OpenID Connect Discovery-Endpunkt
Diese Werte können vom OpenID Connect Discovery-Endpunkt von Logto abgerufen werden:
https://<your-logto-endpoint>/oidc/.well-known/openid-configuration
Hier ist ein Beispiel für eine Antwort (andere Felder wurden zur Übersichtlichkeit weggelassen):
{
  "jwks_uri": "https://your-tenant.logto.app/oidc/jwks",
  "issuer": "https://your-tenant.logto.app/oidc"
}
Im Code fest hinterlegen (nicht empfohlen)
Da Logto keine Anpassung der JWKS-URI oder des Ausstellers (Issuer) erlaubt, kannst du diese Werte fest in deinem Code hinterlegen. Dies wird jedoch für Produktionsanwendungen nicht empfohlen, da dies den Wartungsaufwand erhöhen kann, falls sich zukünftig Konfigurationen ändern.
- JWKS URI: 
https://<your-logto-endpoint>/oidc/jwks - Aussteller (Issuer): 
https://<your-logto-endpoint>/oidc 
Schritt 3: Das Token und die Berechtigungen validieren
Nach dem Extrahieren des Tokens und dem Abrufen der OIDC-Konfiguration überprüfe Folgendes:
- Signatur: JWT muss gültig und von Logto (über JWKS) signiert sein.
 - Aussteller (Issuer): Muss mit dem Aussteller deines Logto-Tenants übereinstimmen.
 - Zielgruppe (Audience): Muss mit dem in Logto registrierten Ressourcenindikator der API oder dem Organisationskontext (falls zutreffend) übereinstimmen.
 - Ablauf (Expiration): Token darf nicht abgelaufen sein.
 - Berechtigungen (Scopes): Token muss die erforderlichen Berechtigungen für deine API / Aktion enthalten. Berechtigungen sind durch Leerzeichen getrennte Zeichenfolgen im 
scope-Anspruch. - Organisationskontext: Wenn du API-Ressourcen auf Organisationsebene schützt, überprüfe den 
organization_id-Anspruch. 
Siehe JSON Web Token, um mehr über die Struktur und Ansprüche von JWT zu erfahren.
Was bei jedem Berechtigungsmodell zu prüfen ist
- Globale API-Ressourcen
 - Organisation (nicht-API) Berechtigungen
 - API-Ressourcen auf Organisationsebene
 
- Audience-Anspruch (
aud): API-Ressourcenindikator - Organisations-Anspruch (
organization_id): Nicht vorhanden - Zu prüfende Berechtigungen (
scope): API-Ressourcen-Berechtigungen 
- Audience-Anspruch (
aud):urn:logto:organization:<id>(Organisationskontext ist imaud-Anspruch) - Organisations-Anspruch (
organization_id): Nicht vorhanden - Zu prüfende Berechtigungen (
scope): Organisationsberechtigungen 
- Audience-Anspruch (
aud): API-Ressourcenindikator - Organisations-Anspruch (
organization_id): Organisations-ID (muss mit Anfrage übereinstimmen) - Zu prüfende Berechtigungen (
scope): API-Ressourcen-Berechtigungen 
Für nicht-API-Organisationsberechtigungen wird der Organisationskontext durch den aud-Anspruch
dargestellt (z. B. urn:logto:organization:abc123). Der organization_id-Anspruch ist nur für
Tokens von API-Ressourcen auf Organisationsebene vorhanden.
Validiere immer sowohl Berechtigungen (Scopes) als auch Kontext (Audience, Organisation) für sichere Multi-Tenant-APIs.
Die Validierungslogik hinzufügen
- Node.js
 - Python
 - Go
 - Java
 - .NET
 - PHP
 - Ruby
 - Rust
 
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:
import { createRemoteJWKSet, jwtVerify, JWTPayload } from 'jose';
import { AuthInfo, AuthorizationError } from './auth-middleware.js';
const jwks = createRemoteJWKSet(new URL(JWKS_URI));
export async function validateJwt(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:
- Express.js
 - Fastify
 - Hapi.js
 - Koa.js
 - NestJS
 
import { Request, Response, NextFunction } from 'express';
import { validateJwt, createAuthInfo } from './jwt-validator.js';
// Erweiterung der Express Request-Schnittstelle 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 validateJwt(token);
    // Auth-Informationen 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 });
  }
}
import { FastifyRequest, FastifyReply } from 'fastify';
import { validateJwt, createAuthInfo } from './jwt-validator.js';
// Fastify Request-Interface erweitern, um auth einzuschließen
declare module 'fastify' {
  interface FastifyRequest {
    auth?: AuthInfo;
  }
}
export async function fastifyVerifyAccessToken(request: FastifyRequest, reply: FastifyReply) {
  try {
    const token = extractBearerTokenFromHeaders(request.headers);
    const payload = await validateJwt(token);
    // Auth-Informationen im Request für generische Nutzung speichern
    request.auth = createAuthInfo(payload);
  } catch (err: any) {
    reply.code(err.status ?? 401).send({ error: err.message });
  }
}
import { Request, ResponseToolkit } from '@hapi/hapi';
import { validateJwt, createAuthInfo } from './jwt-validator.js';
export async function hapiVerifyAccessToken(request: Request, h: ResponseToolkit) {
  try {
    const token = extractBearerTokenFromHeaders(request.headers);
    const payload = await validateJwt(token);
    // Auth-Informationen im request.app für generische Nutzung speichern
    request.app.auth = createAuthInfo(payload);
    return h.continue;
  } catch (err: any) {
    return h
      .response({ error: err.message })
      .code(err.status ?? 401)
      .takeover();
  }
}
import { Context, Next } from 'koa';
import { validateJwt, createAuthInfo } from './jwt-validator.js';
export async function koaVerifyAccessToken(ctx: Context, next: Next) {
  try {
    const token = extractBearerTokenFromHeaders(ctx.request.headers);
    const payload = await validateJwt(token);
    // Auth-Informationen im State für generische Nutzung speichern
    ctx.state.auth = createAuthInfo(payload);
    await next();
  } catch (err: any) {
    ctx.status = err.status ?? 401;
    ctx.body = { error: err.message };
  }
}
import {
  Injectable,
  CanActivate,
  ExecutionContext,
  UnauthorizedException,
  ForbiddenException,
} from '@nestjs/common';
import { validateJwt, createAuthInfo } from './jwt-validator.js';
@Injectable()
export class AccessTokenGuard implements CanActivate {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const req = context.switchToHttp().getRequest();
    try {
      const token = extractBearerTokenFromHeaders(req.headers);
      const payload = await validateJwt(token);
      // Auth-Informationen im Request für generische Nutzung speichern
      req.auth = createAuthInfo(payload);
      return true;
    } catch (err: any) {
      if (err.status === 401) throw new UnauthorizedException(err.message);
      throw new ForbiddenException(err.message);
    }
  }
}
Entsprechend deinem Berechtigungsmodell implementiere die passende Verifizierungslogik in jwt-validator.ts:
- Globale API-Ressourcen
 - Organisation (nicht-API) Berechtigungen
 - Organisationsbezogene API-Ressourcen
 
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)');
  }
}
function verifyPayload(payload: JWTPayload): void {
  // Überprüfe, ob der Audience-Anspruch dem Organisationsformat entspricht
  const audiences = Array.isArray(payload.aud) ? payload.aud : payload.aud ? [payload.aud] : [];
  const hasOrgAudience = audiences.some((aud) => aud.startsWith('urn:logto:organization:'));
  if (!hasOrgAudience) {
    throw new AuthorizationError('Ungültige Zielgruppe für Organisationsberechtigungen');
  }
  // Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (du musst dies ggf. aus dem Request-Kontext extrahieren)
  const expectedOrgId = 'your-organization-id'; // Aus dem Request-Kontext extrahieren
  const expectedAud = `urn:logto:organization:${expectedOrgId}`;
  if (!audiences.includes(expectedAud)) {
    throw new AuthorizationError('Organisations-ID stimmt nicht überein');
  }
  // Überprüfe erforderliche Organisationsberechtigungen
  const requiredScopes = ['invite:users', 'manage:settings']; // 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 Organisationsberechtigung (scope)');
  }
}
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 für organisationsbezogene API-Ressourcen');
  }
  // Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (du musst dies ggf. aus dem Request-Kontext extrahieren)
  const expectedOrgId = 'your-organization-id'; // Aus dem Request-Kontext extrahieren
  const orgId = payload.organization_id as string;
  if (expectedOrgId !== orgId) {
    throw new AuthorizationError('Organisations-ID stimmt nicht überein');
  }
  // Überprüfe erforderliche Berechtigungen für organisationsbezogene 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 organisationsbezogene API-Berechtigungen (scope)');
  }
}
Wir verwenden PyJWT, um JWTs zu validieren. Installiere es, falls du es noch nicht getan hast:
pip install pyjwt[crypto]
Füge zunächst diese gemeinsamen Hilfsfunktionen hinzu, um die JWT-Validierung zu handhaben:
import jwt
from jwt import PyJWKClient
from typing import Dict, Any
from auth_middleware import AuthInfo, AuthorizationError, JWKS_URI, ISSUER
jwks_client = PyJWKClient(JWKS_URI)
def validate_jwt(token: str) -> Dict[str, Any]:
    """JWT validieren und Payload zurückgeben"""
    try:
        signing_key = jwks_client.get_signing_key_from_jwt(token)
        payload = jwt.decode(
            token,
            signing_key.key,
            algorithms=['RS256'],
            issuer=ISSUER,
            options={'verify_aud': False}  # Die Zielgruppe wird manuell überprüft
        )
        verify_payload(payload)
        return payload
    except jwt.InvalidTokenError as e:
        raise AuthorizationError(f'Ungültiges Token: {str(e)}', 401)
    except Exception as e:
        raise AuthorizationError(f'Token-Validierung fehlgeschlagen: {str(e)}', 401)
def create_auth_info(payload: Dict[str, Any]) -> AuthInfo:
    """AuthInfo aus JWT-Payload erstellen"""
    scopes = payload.get('scope', '').split(' ') if payload.get('scope') else []
    audience = payload.get('aud', [])
    if isinstance(audience, str):
        audience = [audience]
    return AuthInfo(
        sub=payload.get('sub'),
        client_id=payload.get('client_id'),
        organization_id=payload.get('organization_id'),
        scopes=scopes,
        audience=audience
    )
def verify_payload(payload: Dict[str, Any]) -> None:
    """Payload basierend auf Berechtigungsmodell überprüfen"""
    # Implementiere hier deine Überprüfungslogik basierend auf dem Berechtigungsmodell
    # Dies wird im Abschnitt zu den Berechtigungsmodellen unten gezeigt
    pass
Implementiere anschließend die Middleware, um das Zugangstoken zu überprüfen:
- FastAPI
 - Flask
 - Django
 - Django REST Framework
 
from fastapi import HTTPException, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jwt_validator import validate_jwt, create_auth_info
security = HTTPBearer()
async def verify_access_token(credentials: HTTPAuthorizationCredentials = Depends(security)) -> AuthInfo:
    try:
        token = credentials.credentials
        payload = validate_jwt(token)
        return create_auth_info(payload)
    except AuthorizationError as e:
        raise HTTPException(status_code=e.status, detail=str(e))
from functools import wraps
from flask import request, jsonify, g
from jwt_validator import validate_jwt, create_auth_info
def verify_access_token(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        try:
            token = extract_bearer_token_from_headers(dict(request.headers))
            payload = validate_jwt(token)
            # Auth-Informationen im Flask-g-Objekt für generische Nutzung speichern
            g.auth = create_auth_info(payload)
            return f(*args, **kwargs)
        except AuthorizationError as e:
            return jsonify({'error': str(e)}), e.status
    return decorated_function
from django.http import JsonResponse
from jwt_validator import validate_jwt, create_auth_info
def require_access_token(view_func):
    def wrapper(request, *args, **kwargs):
        try:
            headers = {key.replace('HTTP_', '').replace('_', '-').lower(): value
                      for key, value in request.META.items() if key.startswith('HTTP_')}
            token = extract_bearer_token_from_headers(headers)
            payload = validate_jwt(token)
            # Auth-Informationen an die Anfrage anhängen, um sie allgemein zu verwenden
            request.auth = create_auth_info(payload)
            return view_func(request, *args, **kwargs)
        except AuthorizationError as e:
            return JsonResponse({'error': str(e)}, status=e.status)
    return wrapper
from rest_framework.authentication import TokenAuthentication
from rest_framework import exceptions
from jwt_validator import validate_jwt, create_auth_info
class AccessTokenAuthentication(TokenAuthentication):
    keyword = 'Bearer'  # Verwende 'Bearer' statt 'Token'
    def authenticate_credentials(self, key):
        """
        Authentifiziere das Token, indem es als JWT validiert wird.
        """
        try:
            payload = validate_jwt(key)
            auth_info = create_auth_info(payload)
            # Erstelle ein benutzerähnliches Objekt, das Auth-Informationen für generische Nutzung hält
            user = type('User', (), {
                'auth': auth_info,
                'is_authenticated': True,
                'is_anonymous': False,
                'is_active': True,
            })()
            return (user, key)
        except AuthorizationError as e:
            if e.status == 401:
                raise exceptions.AuthenticationFailed(str(e))
            else:  # 403
                raise exceptions.PermissionDenied(str(e))
Implementiere gemäß deinem Berechtigungsmodell die entsprechende Überprüfungslogik in jwt_validator.py:
- Globale API-Ressourcen
 - Organisation (nicht-API) Berechtigungen
 - Organisationsbezogene API-Ressourcen
 
def verify_payload(payload: Dict[str, Any]) -> None:
    """Payload für globale API-Ressourcen überprüfen"""
    # Überprüfe, ob der Audience-Claim mit deinem API-Ressourcenindikator übereinstimmt
    audiences = payload.get('aud', [])
    if isinstance(audiences, str):
        audiences = [audiences]
    if 'https://your-api-resource-indicator' not in audiences:
        raise AuthorizationError('Ungültige Zielgruppe')
    # Überprüfe erforderliche Berechtigungen für globale API-Ressourcen
    required_scopes = ['api:read', 'api:write']  # Ersetze durch deine tatsächlich benötigten Berechtigungen
    scopes = payload.get('scope', '').split(' ') if payload.get('scope') else []
    if not all(scope in scopes for scope in required_scopes):
        raise AuthorizationError('Unzureichende Berechtigung')
def verify_payload(payload: Dict[str, Any]) -> None:
    """Payload für Organisationsberechtigungen überprüfen"""
    # Überprüfe, ob der Audience-Claim dem Organisationsformat entspricht
    audiences = payload.get('aud', [])
    if isinstance(audiences, str):
        audiences = [audiences]
    has_org_audience = any(aud.startswith('urn:logto:organization:') for aud in audiences)
    if not has_org_audience:
        raise AuthorizationError('Ungültige Zielgruppe für Organisationsberechtigungen')
    # Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (ggf. aus dem Request-Kontext extrahieren)
    expected_org_id = 'your-organization-id'  # Aus dem Request-Kontext extrahieren
    expected_aud = f'urn:logto:organization:{expected_org_id}'
    if expected_aud not in audiences:
        raise AuthorizationError('Organisations-ID stimmt nicht überein')
    # Überprüfe erforderliche Organisationsberechtigungen
    required_scopes = ['invite:users', 'manage:settings']  # Ersetze durch deine tatsächlich benötigten Berechtigungen
    scopes = payload.get('scope', '').split(' ') if payload.get('scope') else []
    if not all(scope in scopes for scope in required_scopes):
        raise AuthorizationError('Unzureichende Organisationsberechtigung')
def verify_payload(payload: Dict[str, Any]) -> None:
    """Payload für organisationsbezogene API-Ressourcen überprüfen"""
    # Überprüfe, ob der Audience-Claim mit deinem API-Ressourcenindikator übereinstimmt
    audiences = payload.get('aud', [])
    if isinstance(audiences, str):
        audiences = [audiences]
    if 'https://your-api-resource-indicator' not in audiences:
        raise AuthorizationError('Ungültige Zielgruppe für organisationsbezogene API-Ressourcen')
    # Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (ggf. aus dem Request-Kontext extrahieren)
    expected_org_id = 'your-organization-id'  # Aus dem Request-Kontext extrahieren
    org_id = payload.get('organization_id')
    if expected_org_id != org_id:
        raise AuthorizationError('Organisations-ID stimmt nicht überein')
    # Überprüfe erforderliche Berechtigungen für organisationsbezogene API-Ressourcen
    required_scopes = ['api:read', 'api:write']  # Ersetze durch deine tatsächlich benötigten Berechtigungen
    scopes = payload.get('scope', '').split(' ') if payload.get('scope') else []
    if not all(scope in scopes for scope in required_scopes):
        raise AuthorizationError('Unzureichende organisationsbezogene API-Berechtigungen')
Wir verwenden github.com/lestrrat-go/jwx, um JWTs zu validieren. Installiere es, falls du es noch nicht getan hast:
go mod init your-project
go get github.com/lestrrat-go/jwx/v3
Füge zunächst diese gemeinsamen Komponenten zu deiner Datei auth_middleware.go hinzu:
import (
    "context"
    "strings"
    "time"
    "github.com/lestrrat-go/jwx/v3/jwk"
    "github.com/lestrrat-go/jwx/v3/jwt"
)
var jwkSet jwk.Set
func init() {
    // JWKS-Cache initialisieren
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    var err error
    jwkSet, err = jwk.Fetch(ctx, JWKS_URI)
    if err != nil {
        panic("Fehler beim Abrufen von JWKS: " + err.Error())
    }
}
// validateJWT validiert das JWT und gibt das geparste Token zurück
func validateJWT(tokenString string) (jwt.Token, error) {
    token, err := jwt.Parse([]byte(tokenString), jwt.WithKeySet(jwkSet))
    if err != nil {
        return nil, NewAuthorizationError("Ungültiges Token: "+err.Error(), http.StatusUnauthorized)
    }
    // Aussteller überprüfen
    if token.Issuer() != ISSUER {
        return nil, NewAuthorizationError("Ungültiger Aussteller", http.StatusUnauthorized)
    }
    if err := verifyPayload(token); err != nil {
        return nil, err
    }
    return token, nil
}
// Hilfsfunktionen zum Extrahieren von Token-Daten
func getStringClaim(token jwt.Token, key string) string {
    if val, ok := token.Get(key); ok {
        if str, ok := val.(string); ok {
            return str
        }
    }
    return ""
}
func getScopesFromToken(token jwt.Token) []string {
    if val, ok := token.Get("scope"); ok {
        if scope, ok := val.(string); ok && scope != "" {
            return strings.Split(scope, " ")
        }
    }
    return []string{}
}
func getAudienceFromToken(token jwt.Token) []string {
    return token.Audience()
}
Implementiere anschließend das Middleware, um das Zugangstoken zu überprüfen:
- Gin
 - Fiber
 - Echo
 - Chi
 
import "github.com/gin-gonic/gin"
func VerifyAccessToken() gin.HandlerFunc {
    return func(c *gin.Context) {
        tokenString, err := extractBearerTokenFromHeaders(c.Request)
        if err != nil {
            authErr := err.(*AuthorizationError)
            c.JSON(authErr.Status, gin.H{"error": authErr.Message})
            c.Abort()
            return
        }
        token, err := validateJWT(tokenString)
        if err != nil {
            authErr := err.(*AuthorizationError)
            c.JSON(authErr.Status, gin.H{"error": authErr.Message})
            c.Abort()
            return
        }
        // Token im Kontext für generische Nutzung speichern
        c.Set("auth", token)
        c.Next()
    }
}
import (
    "net/http"
    "github.com/gofiber/fiber/v2"
)
func VerifyAccessToken(c *fiber.Ctx) error {
    // Konvertiere die Fiber-Anfrage in eine http.Request für Kompatibilität
    req := &http.Request{
        Header: make(http.Header),
    }
    req.Header.Set("Authorization", c.Get("Authorization"))
    tokenString, err := extractBearerTokenFromHeaders(req)
    if err != nil {
        authErr := err.(*AuthorizationError)
        return c.Status(authErr.Status).JSON(fiber.Map{"error": authErr.Message})
    }
    token, err := validateJWT(tokenString)
    if err != nil {
        authErr := err.(*AuthorizationError)
        return c.Status(authErr.Status).JSON(fiber.Map{"error": authErr.Message})
    }
    // Speichere das Token in Locals für generische Nutzung
    c.Locals("auth", token)
    return c.Next()
}
import "github.com/labstack/echo/v4"
func VerifyAccessToken(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        tokenString, err := extractBearerTokenFromHeaders(c.Request())
        if err != nil {
            authErr := err.(*AuthorizationError)
            return c.JSON(authErr.Status, echo.Map{"error": authErr.Message})
        }
        token, err := validateJWT(tokenString)
        if err != nil {
            authErr := err.(*AuthorizationError)
            return c.JSON(authErr.Status, echo.Map{"error": authErr.Message})
        }
        // Token im Kontext für generische Nutzung speichern
        c.Set("auth", token)
        return next(c)
    }
}
import (
    "context"
    "encoding/json"
    "net/http"
)
type contextKey string
const AuthContextKey contextKey = "auth"
func VerifyAccessToken(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        tokenString, err := extractBearerTokenFromHeaders(r)
        if err != nil {
            authErr := err.(*AuthorizationError)
            w.Header().Set("Content-Type", "application/json")
            w.WriteHeader(authErr.Status)
            json.NewEncoder(w).Encode(map[string]string{"error": authErr.Message})
            return
        }
        token, err := validateJWT(tokenString)
        if err != nil {
            authErr := err.(*AuthorizationError)
            w.Header().Set("Content-Type", "application/json")
            w.WriteHeader(authErr.Status)
            json.NewEncoder(w).Encode(map[string]string{"error": authErr.Message})
            return
        }
        // Token im Kontext für generische Nutzung speichern
        ctx := context.WithValue(r.Context(), AuthContextKey, token)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}
Je nach deinem Berechtigungsmodell musst du möglicherweise unterschiedliche verifyPayload-Logik anwenden:
- Globale API-Ressourcen
 - Organisation (nicht-API) Berechtigungen
 - Organisationsbezogene API-Ressourcen
 
func verifyPayload(token jwt.Token) error {
    // Überprüfe, ob der Audience-Anspruch mit deinem API-Ressourcenindikator übereinstimmt
    if !hasAudience(token, "https://your-api-resource-indicator") {
        return NewAuthorizationError("Ungültige Zielgruppe")
    }
    // Überprüfe erforderliche Berechtigungen für globale API-Ressourcen
    requiredScopes := []string{"api:read", "api:write"} // Ersetze durch deine tatsächlichen erforderlichen Berechtigungen
    if !hasRequiredScopes(token, requiredScopes) {
        return NewAuthorizationError("Unzureichende Berechtigung")
    }
    return nil
}
func verifyPayload(token jwt.Token) error {
    // Überprüfe, ob der Audience-Anspruch dem Organisationsformat entspricht
    if !hasOrganizationAudience(token) {
        return NewAuthorizationError("Ungültige Zielgruppe für Organisationsberechtigungen")
    }
    // Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (du musst dies ggf. aus dem Request-Kontext extrahieren)
    expectedOrgID := "your-organization-id" // Aus dem Request-Kontext extrahieren
    if !hasMatchingOrganization(token, expectedOrgID) {
        return NewAuthorizationError("Organisation-ID stimmt nicht überein")
    }
    // Überprüfe erforderliche Organisationsberechtigungen
    requiredScopes := []string{"invite:users", "manage:settings"} // Ersetze durch deine tatsächlichen erforderlichen Berechtigungen
    if !hasRequiredScopes(token, requiredScopes) {
        return NewAuthorizationError("Unzureichende Organisationsberechtigung")
    }
    return nil
}
func verifyPayload(token jwt.Token) error {
    // Überprüfe, ob der Audience-Anspruch mit deinem API-Ressourcenindikator übereinstimmt
    if !hasAudience(token, "https://your-api-resource-indicator") {
        return NewAuthorizationError("Ungültige Zielgruppe für organisationsbezogene API-Ressourcen")
    }
    // Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (du musst dies ggf. aus dem Request-Kontext extrahieren)
    expectedOrgID := "your-organization-id" // Aus dem Request-Kontext extrahieren
    if !hasMatchingOrganizationID(token, expectedOrgID) {
        return NewAuthorizationError("Organisation-ID stimmt nicht überein")
    }
    // Überprüfe erforderliche Berechtigungen für organisationsbezogene API-Ressourcen
    requiredScopes := []string{"api:read", "api:write"} // Ersetze durch deine tatsächlichen erforderlichen Berechtigungen
    if !hasRequiredScopes(token, requiredScopes) {
        return NewAuthorizationError("Unzureichende organisationsbezogene API-Berechtigungen")
    }
    return nil
}
Füge diese Hilfsfunktionen zur Payload-Überprüfung hinzu:
// hasAudience prüft, ob das Token die angegebene Zielgruppe enthält
func hasAudience(token jwt.Token, expectedAud string) bool {
    audiences := token.Audience()
    for _, aud := range audiences {
        if aud == expectedAud {
            return true
        }
    }
    return false
}
// hasOrganizationAudience prüft, ob das Token eine Zielgruppe im Organisationsformat enthält
func hasOrganizationAudience(token jwt.Token) bool {
    audiences := token.Audience()
    for _, aud := range audiences {
        if strings.HasPrefix(aud, "urn:logto:organization:") {
            return true
        }
    }
    return false
}
// hasRequiredScopes prüft, ob das Token alle erforderlichen Berechtigungen enthält
func hasRequiredScopes(token jwt.Token, requiredScopes []string) bool {
    scopes := getScopesFromToken(token)
    for _, required := range requiredScopes {
        found := false
        for _, scope := range scopes {
            if scope == required {
                found = true
                break
            }
        }
        if !found {
            return false
        }
    }
    return true
}
// hasMatchingOrganization prüft, ob die Token-Zielgruppe mit der erwarteten Organisation übereinstimmt
func hasMatchingOrganization(token jwt.Token, expectedOrgID string) bool {
    expectedAud := fmt.Sprintf("urn:logto:organization:%s", expectedOrgID)
    return hasAudience(token, expectedAud)
}
// hasMatchingOrganizationID prüft, ob die organization_id des Tokens mit der erwarteten übereinstimmt
func hasMatchingOrganizationID(token jwt.Token, expectedOrgID string) bool {
    orgID := getStringClaim(token, "organization_id")
    return orgID == expectedOrgID
}
Wir verwenden je nach Framework unterschiedliche JWT-Bibliotheken. Installiere die erforderlichen Abhängigkeiten:
- Spring Boot
 - Quarkus
 - Micronaut
 - Vert.x Web
 
Füge dies zu deiner pom.xml hinzu:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class JwtSecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/api/protected/**").authenticated()
                .anyRequest().permitAll()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt.decoder(jwtDecoder()))
            );
        return http.build();
    }
    @Bean
    public JwtDecoder jwtDecoder() {
        // Denke daran, diese Umgebungsvariablen in deiner Bereitstellung zu setzen
        String jwksUri = System.getenv("JWKS_URI");
        String issuer = System.getenv("JWT_ISSUER");
        return NimbusJwtDecoder.withJwkSetUri(jwksUri)
            .issuer(issuer)
            .build();
    }
}
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class JwtValidator {
    public void verifyPayload(Jwt jwt) {
        // Die Aussteller-Validierung (Issuer validation) wird automatisch vom Spring Security JWT Decoder übernommen
        // Implementiere hier deine zusätzliche Verifizierungslogik basierend auf dem Berechtigungsmodell
        // Verwende die untenstehenden Hilfsmethoden zur Anspruchsextraktion (Claim extraction)
        // Beispiel: throw new AuthorizationException("Unzureichende Berechtigungen");
        // Der Statuscode wird durch das Exception-Handling von Spring Security behandelt
    }
    // Hilfsmethoden für Spring Boot JWT
    private List<String> extractAudiences(Jwt jwt) {
        return jwt.getAudience();
    }
    private String extractScopes(Jwt jwt) {
        return jwt.getClaimAsString("scope");
    }
    private String extractOrganizationId(Jwt jwt) {
        return jwt.getClaimAsString("organization_id");
    }
}
Füge Folgendes zu deiner pom.xml hinzu:
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-jwt</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
# JWT-Konfiguration
mp.jwt.verify.publickey.location=${JWKS_URI:https://your-tenant.logto.app/oidc/jwks}
mp.jwt.verify.issuer=${JWT_ISSUER:https://your-tenant.logto.app/oidc}
import org.eclipse.microprofile.jwt.JsonWebToken;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.Provider;
import java.util ArrayList;
import java.util.List;
@Provider
@ApplicationScoped
public class JwtVerificationFilter implements ContainerRequestFilter {
    @Inject
    JsonWebToken jwt;
    @Override
    public void filter(ContainerRequestContext requestContext) {
        if (requestContext.getUriInfo().getPath().startsWith("/api/protected")) {
            try {
                verifyPayload(jwt);
                requestContext.setProperty("auth", jwt);
            } catch (AuthorizationException e) {
                requestContext.abortWith(
                    Response.status(e.getStatusCode())
                        .entity("{\"error\": \"" + e.getMessage() + "\"}")
                        .build()
                );
            } catch (Exception e) {
                requestContext.abortWith(
                    Response.status(401)
                        .entity("{\"error\": \"Ungültiges Token\"}")
                        .build()
                );
            }
        }
    }
    private void verifyPayload(JsonWebToken jwt) {
        // Die Aussteller (Issuer)-Validierung wird automatisch von der Quarkus JWT-Erweiterung übernommen
        // Implementiere hier deine zusätzliche Verifizierungslogik basierend auf dem Berechtigungsmodell
        // Verwende die untenstehenden Hilfsmethoden zur Anspruchsextraktion
    }
    // Hilfsmethoden für Quarkus JWT
    private List<String> extractAudiences(JsonWebToken jwt) {
        return new ArrayList<>(jwt.getAudience());
    }
    private String extractScopes(JsonWebToken jwt) {
        return jwt.getClaim("scope");
    }
    private String extractOrganizationId(JsonWebToken jwt) {
        return jwt.getClaim("organization_id");
    }
}
Füge dies zu deiner pom.xml hinzu:
<dependency>
    <groupId>io.micronaut.security</groupId>
    <artifactId>micronaut-security-jwt</artifactId>
</dependency>
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-http-server-netty</artifactId>
</dependency>
micronaut:
  security:
    authentication: bearer
    token:
      jwt:
        signatures:
          jwks:
            logto:
              url: ${JWKS_URI:https://your-tenant.logto.app/oidc/jwks}
        claims-validators:
          issuer: ${JWT_ISSUER:https://your-tenant.logto.app/oidc}
import io.micronaut.security.token.Claims;
import io.micronaut.security.token.validator.TokenValidator;
import jakarta.inject.Singleton;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
@Singleton
public class JwtClaimsValidator implements TokenValidator {
    @Override
    public Publisher<Boolean> validateToken(String token, Claims claims) {
        try {
            verifyPayload(claims);
            return Mono.just(true);
        } catch (AuthorizationException e) {
            // Micronaut wird den Statuscode entsprechend behandeln
            return Mono.just(false);
        }
    }
    private void verifyPayload(Claims claims) {
        // Die Aussteller (Issuer)-Validierung wird automatisch durch die Micronaut JWT-Konfiguration übernommen
        // Implementiere hier deine zusätzliche Verifizierungslogik basierend auf dem Berechtigungsmodell
        // Verwende die untenstehenden Hilfsmethoden zur Anspruchsextraktion
        // Beispiel: throw new AuthorizationException("Unzureichende Berechtigungen");
    }
    // Hilfsmethoden für Micronaut JWT
    @SuppressWarnings("unchecked")
    private List<String> extractAudiences(Claims claims) {
        Object aud = claims.get("aud");
        if (aud instanceof List) {
            return (List<String>) aud;
        } else if (aud instanceof String) {
            return Arrays.asList((String) aud);
        }
        return List.of();
    }
    private String extractScopes(Claims claims) {
        return (String) claims.get("scope");
    }
    private String extractOrganizationId(Claims claims) {
        return (String) claims.get("organization_id");
    }
}
Füge dies zu deiner pom.xml hinzu:
<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-web</artifactId>
</dependency>
<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-auth-jwt</artifactId>
</dependency>
<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-web-client</artifactId>
</dependency>
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.ext.auth.jwt.JWTAuthOptions;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.client.WebClient;
import java.util.List;
import java.util.ArrayList;
public class JwtAuthHandler implements Handler<RoutingContext> {
    private final JWTAuth jwtAuth;
    private final WebClient webClient;
    private final String expectedIssuer;
    private final String jwksUri;
    public JwtAuthHandler(Vertx vertx) {
        this.webClient = WebClient.create(vertx);
        this.jwtAuth = JWTAuth.create(vertx, new JWTAuthOptions());
        // Denke daran, diese Umgebungsvariablen in deiner Bereitstellung zu setzen
        this.expectedIssuer = System.getenv("JWT_ISSUER");
        this.jwksUri = System.getenv("JWKS_URI");
        // JWKS abrufen und JWT-Authentifizierung konfigurieren
        fetchJWKS().onSuccess(jwks -> {
            // JWKS konfigurieren (vereinfacht – du benötigst möglicherweise einen richtigen JWKS-Parser)
        });
    }
    @Override
    public void handle(RoutingContext context) {
        String authHeader = context.request().getHeader("Authorization");
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            context.response()
                .setStatusCode(401)
                .putHeader("Content-Type", "application/json")
                .end("{\"error\": \"Authorization header missing or invalid\"}");
            return;
        }
        String token = authHeader.substring(7);
        jwtAuth.authenticate(new JsonObject().put("jwt", token))
            .onSuccess(user -> {
                try {
                    JsonObject principal = user.principal();
                    verifyPayload(principal);
                    context.put("auth", principal);
                    context.next();
                } catch (AuthorizationException e) {
                    context.response()
                        .setStatusCode(e.getStatusCode())  // Verwende den Statuscode der Exception
                        .putHeader("Content-Type", "application/json")
                        .end("{\"error\": \"" + e.getMessage() + "\"}");
                } catch (Exception e) {
                    context.response()
                        .setStatusCode(401)
                        .putHeader("Content-Type", "application/json")
                        .end("{\"error\": \"Invalid token\"}");
                }
            })
            .onFailure(err -> {
                context.response()
                    .setStatusCode(401)
                    .putHeader("Content-Type", "application/json")
                    .end("{\"error\": \"Invalid token: " + err.getMessage() + "\"}");
            });
    }
    private Future<JsonObject> fetchJWKS() {
        return webClient.getAbs(this.jwksUri)
            .send()
            .map(response -> response.bodyAsJsonObject());
    }
    private void verifyPayload(JsonObject principal) {
        // Aussteller (Issuer) manuell für Vert.x überprüfen
        String issuer = principal.getString("iss");
        if (issuer == null || !expectedIssuer.equals(issuer)) {
            throw new AuthorizationException("Invalid issuer: " + issuer);
        }
        // Implementiere hier deine zusätzliche Überprüfungslogik basierend auf dem Berechtigungsmodell
        // Verwende die untenstehenden Hilfsmethoden für das Extrahieren von Ansprüchen (Claims)
    }
    // Hilfsmethoden für Vert.x JWT
    private List<String> extractAudiences(JsonObject principal) {
        JsonArray audiences = principal.getJsonArray("aud");
        if (audiences != null) {
            List<String> result = new ArrayList<>();
            for (Object aud : audiences) {
                result.add(aud.toString());
            }
            return result;
        }
        return List.of();
    }
    private String extractScopes(JsonObject principal) {
        return principal.getString("scope");
    }
    private String extractOrganizationId(JsonObject principal) {
        return principal.getString("organization_id");
    }
}
Implementiere entsprechend deinem Berechtigungsmodell die passende Prüf-Logik:
- Globale API-Ressourcen
 - Organisations-(Nicht-API)-Berechtigungen
 - Organisationsbezogene API-Ressourcen
 
// Überprüfe, ob der Audience-Anspruch mit deinem API-Ressourcenindikator übereinstimmt
List<String> audiences = extractAudiences(token); // Framework-spezifische Extraktion
if (!audiences.contains("https://your-api-resource-indicator")) {
    throw new AuthorizationException("Ungültige Zielgruppe");
}
// Überprüfe die erforderlichen Berechtigungen für globale API-Ressourcen
List<String> requiredScopes = Arrays.asList("api:read", "api:write"); // Ersetze durch deine tatsächlichen erforderlichen Berechtigungen
String scopes = extractScopes(token); // Framework-spezifische Extraktion
List<String> tokenScopes = scopes != null ? Arrays.asList(scopes.split(" ")) : List.of();
if (!tokenScopes.containsAll(requiredScopes)) {
    throw new AuthorizationException("Unzureichende Berechtigung");
}
// Überprüfe, ob der Audience-Anspruch dem Organisationsformat entspricht
List<String> audiences = extractAudiences(token); // Framework-spezifische Extraktion
boolean hasOrgAudience = audiences.stream()
    .anyMatch(aud -> aud.startsWith("urn:logto:organization:"));
if (!hasOrgAudience) {
    throw new AuthorizationException("Ungültige Zielgruppe für Organisationsberechtigungen");
}
// Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (du musst sie ggf. aus dem Request-Kontext extrahieren)
String expectedOrgId = "your-organization-id"; // Aus dem Request-Kontext extrahieren
String expectedAud = "urn:logto:organization:" + expectedOrgId;
if (!audiences.contains(expectedAud)) {
    throw new AuthorizationException("Organisations-ID stimmt nicht überein");
}
// Überprüfe die erforderlichen Organisationsberechtigungen
List<String> requiredScopes = Arrays.asList("invite:users", "manage:settings"); // Ersetze durch deine tatsächlichen erforderlichen Berechtigungen
String scopes = extractScopes(token); // Framework-spezifische Extraktion
List<String> tokenScopes = scopes != null ? Arrays.asList(scopes.split(" ")) : List.of();
if (!tokenScopes.containsAll(requiredScopes)) {
    throw new AuthorizationException("Unzureichende Organisationsberechtigung");
}
// Überprüfe, ob der Audience-Anspruch mit deinem API-Ressourcenindikator übereinstimmt
List<String> audiences = extractAudiences(token); // Framework-spezifische Extraktion
if (!audiences.contains("https://your-api-resource-indicator")) {
    throw new AuthorizationException("Ungültige Zielgruppe für organisationsbezogene API-Ressourcen");
}
// Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (du musst sie ggf. aus dem Request-Kontext extrahieren)
String expectedOrgId = "your-organization-id"; // Aus dem Request-Kontext extrahieren
String orgId = extractOrganizationId(token); // Framework-spezifische Extraktion
if (!expectedOrgId.equals(orgId)) {
    throw new AuthorizationException("Organisations-ID stimmt nicht überein");
}
// Überprüfe die erforderlichen Berechtigungen für organisationsbezogene API-Ressourcen
List<String> requiredScopes = Arrays.asList("api:read", "api:write"); // Ersetze durch deine tatsächlichen erforderlichen Berechtigungen
String scopes = extractScopes(token); // Framework-spezifische Extraktion
List<String> tokenScopes = scopes != null ? Arrays.asList(scopes.split(" ")) : List.of();
if (!tokenScopes.containsAll(requiredScopes)) {
    throw new AuthorizationException("Unzureichende organisationsbezogene API-Berechtigungen");
}
Die Hilfsmethoden zum Extrahieren von Ansprüchen sind framework-spezifisch. Siehe die Implementierungsdetails in den oben genannten framework-spezifischen Validierungsdateien.
Füge das erforderliche NuGet-Paket für JWT-Authentifizierung hinzu:
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
Erstelle einen Validierungsdienst, um die Tokenvalidierung zu übernehmen:
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using YourApiNamespace.Exceptions;
namespace YourApiNamespace.Services
{
    public interface IJwtValidationService
    {
        Task ValidateTokenAsync(TokenValidatedContext context);
    }
    public class JwtValidationService : IJwtValidationService
    {
        public async Task ValidateTokenAsync(TokenValidatedContext context)
        {
            var principal = context.Principal!;
            try
            {
                // Füge hier deine Validierungslogik basierend auf dem Berechtigungsmodell ein
                ValidatePayload(principal);
            }
            catch (AuthorizationException)
            {
                throw; // Autorisierungsfehler erneut auslösen
            }
            catch (Exception ex)
            {
                throw new AuthorizationException($"Tokenvalidierung fehlgeschlagen: {ex.Message}", 401);
            }
        }
        private void ValidatePayload(ClaimsPrincipal principal)
        {
            // Implementiere hier deine Überprüfungslogik basierend auf dem Berechtigungsmodell
            // Dies wird im Abschnitt zu den Berechtigungsmodellen unten gezeigt
        }
    }
}
Konfiguriere die JWT-Authentifizierung in deiner Program.cs:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using YourApiNamespace.Services;
using YourApiNamespace.Exceptions;
var builder = WebApplication.CreateBuilder(args);
// Füge Dienste zum Container hinzu
builder.Services.AddControllers();
builder.Services.AddScoped<IJwtValidationService, JwtValidationService>();
// Konfiguriere JWT-Authentifizierung
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = AuthConstants.Issuer;
        options.MetadataAddress = $"{AuthConstants.Issuer}/.well-known/openid-configuration";
        options.RequireHttpsMetadata = true;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = AuthConstants.Issuer,
            ValidateAudience = false, // Die Zielgruppe wird manuell basierend auf dem Berechtigungsmodell validiert
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ClockSkew = TimeSpan.FromMinutes(5)
        };
        options.Events = new JwtBearerEvents
        {
            OnTokenValidated = async context =>
            {
                var validationService = context.HttpContext.RequestServices
                    .GetRequiredService<IJwtValidationService>();
                await validationService.ValidateTokenAsync(context);
            },
            OnAuthenticationFailed = context =>
            {
                // Behandle JWT-Bibliotheksfehler als 401
                context.Response.StatusCode = 401;
                context.Response.ContentType = "application/json";
                context.Response.WriteAsync($"{{\"error\": \"Invalid token\"}}");
                context.HandleResponse();
                return Task.CompletedTask;
            }
        };
    });
builder.Services.AddAuthorization();
var app = builder.Build();
// Globale Fehlerbehandlung für Authentifizierungs- / Autorisierungsfehler
app.Use(async (context, next) =>
{
    try
    {
        await next();
    }
    catch (AuthorizationException ex)
    {
        context.Response.StatusCode = ex.StatusCode;
        context.Response.ContentType = "application/json";
        await context.Response.WriteAsync($"{{\"error\": \"{ex.Message}\"}}");
    }
});
// Konfiguriere die HTTP-Request-Pipeline
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Implementiere entsprechend deinem Berechtigungsmodell die passende Validierungslogik in JwtValidationService:
- Globale API-Ressourcen
 - Organisation (nicht-API) Berechtigungen
 - Organisationsbezogene API-Ressourcen
 
private void ValidatePayload(ClaimsPrincipal principal)
{
    // Überprüfe, ob der Audience-Claim mit deinem API-Ressourcenindikator übereinstimmt
    var audiences = principal.FindAll("aud").Select(c => c.Value).ToList();
    if (!audiences.Contains("https://your-api-resource-indicator"))
    {
        throw new AuthorizationException("Ungültige Zielgruppe (audience)");
    }
    // Überprüfe erforderliche Berechtigungen für globale API-Ressourcen
    var requiredScopes = new[] { "api:read", "api:write" }; // Ersetze durch deine tatsächlichen erforderlichen Berechtigungen
    var tokenScopes = principal.FindFirst("scope")?.Value?.Split(' ') ?? Array.Empty<string>();
    if (!requiredScopes.All(scope => tokenScopes.Contains(scope)))
    {
        throw new AuthorizationException("Unzureichende Berechtigung (scope)");
    }
}
private void ValidatePayload(ClaimsPrincipal principal)
{
    // Überprüfe, ob der Audience-Claim dem Organisationsformat entspricht
    var audiences = principal.FindAll("aud").Select(c => c.Value).ToList();
    var hasOrgAudience = audiences.Any(aud => aud.StartsWith("urn:logto:organization:"));
    if (!hasOrgAudience)
    {
        throw new AuthorizationException("Ungültige Zielgruppe für Organisationsberechtigungen");
    }
    // Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (ggf. aus dem Request-Kontext extrahieren)
    var expectedOrgId = "your-organization-id"; // Aus dem Request-Kontext extrahieren
    var expectedAud = $"urn:logto:organization:{expectedOrgId}";
    if (!audiences.Contains(expectedAud))
    {
        throw new AuthorizationException("Organisation-ID stimmt nicht überein");
    }
    // Überprüfe erforderliche Organisationsberechtigungen
    var requiredScopes = new[] { "invite:users", "manage:settings" }; // Ersetze durch deine tatsächlichen erforderlichen Berechtigungen
    var tokenScopes = principal.FindFirst("scope")?.Value?.Split(' ') ?? Array.Empty<string>();
    if (!requiredScopes.All(scope => tokenScopes.Contains(scope)))
    {
        throw new AuthorizationException("Unzureichende Organisationsberechtigung (scope)");
    }
}
private void ValidatePayload(ClaimsPrincipal principal)
{
    // Überprüfe, ob der Audience-Claim mit deinem API-Ressourcenindikator übereinstimmt
    var audiences = principal.FindAll("aud").Select(c => c.Value).ToList();
    if (!audiences.Contains("https://your-api-resource-indicator"))
    {
        throw new AuthorizationException("Ungültige Zielgruppe für organisationsbezogene API-Ressourcen");
    }
    // Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (ggf. aus dem Request-Kontext extrahieren)
    var expectedOrgId = "your-organization-id"; // Aus dem Request-Kontext extrahieren
    var orgId = principal.FindFirst("organization_id")?.Value;
    if (!expectedOrgId.Equals(orgId))
    {
        throw new AuthorizationException("Organisation-ID stimmt nicht überein");
    }
    // Überprüfe erforderliche Berechtigungen für organisationsbezogene API-Ressourcen
    var requiredScopes = new[] { "api:read", "api:write" }; // Ersetze durch deine tatsächlichen erforderlichen Berechtigungen
    var tokenScopes = principal.FindFirst("scope")?.Value?.Split(' ') ?? Array.Empty<string>();
    if (!requiredScopes.All(scope => tokenScopes.Contains(scope)))
    {
        throw new AuthorizationException("Unzureichende organisationsbezogene API-Berechtigungen (scope)");
    }
}
Wir verwenden firebase/php-jwt, um JWTs zu validieren. Installiere es mit Composer:
composer require firebase/php-jwt
Füge zunächst diese gemeinsamen Hilfsfunktionen hinzu, um die JWT-Validierung zu behandeln:
<?php
use Firebase\JWT\JWT;
use Firebase\JWT\JWK;
use Firebase\JWT\Key;
class JwtValidator
{
    use AuthHelpers;
    private static ?array $jwks = null;
    public static function fetchJwks(): array
    {
        if (self::$jwks === null) {
            $jwksData = file_get_contents(AuthConstants::JWKS_URI);
            if ($jwksData === false) {
                throw new AuthorizationException('Fehler beim Abrufen der JWKS', 401);
            }
            self::$jwks = json_decode($jwksData, true);
        }
        return self::$jwks;
    }
    public static function validateJwt(string $token): array
    {
        try {
            $jwks = self::fetchJwks();
            $keys = JWK::parseKeySet($jwks);
            $decoded = JWT::decode($token, $keys);
            $payload = (array) $decoded;
            // Aussteller (Issuer) überprüfen
            if (($payload['iss'] ?? '') !== AuthConstants::ISSUER) {
                throw new AuthorizationException('Ungültiger Aussteller', 401);
            }
            self::verifyPayload($payload);
            return $payload;
        } catch (AuthorizationException $e) {
            throw $e;
        } catch (Exception $e) {
            throw new AuthorizationException('Ungültiges Token: ' . $e->getMessage(), 401);
        }
    }
    public static function createAuthInfo(array $payload): AuthInfo
    {
        $scopes = !empty($payload['scope']) ? explode(' ', $payload['scope']) : [];
        $audience = $payload['aud'] ?? [];
        if (is_string($audience)) {
            $audience = [$audience];
        }
        return new AuthInfo(
            sub: $payload['sub'],
            clientId: $payload['client_id'] ?? null,
            organizationId: $payload['organization_id'] ?? null,
            scopes: $scopes,
            audience: $audience
        );
    }
    private static function verifyPayload(array $payload): void
    {
        // Implementiere hier deine Überprüfungslogik basierend auf dem Berechtigungsmodell
        // Dies wird im Abschnitt zu den Berechtigungsmodellen unten gezeigt
    }
}
Implementiere dann das Middleware, um das Zugangstoken zu überprüfen:
- Laravel
 - Symfony
 - Slim
 
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class VerifyAccessToken
{
    use AuthHelpers;
    public function handle(Request $request, Closure $next): Response
    {
        try {
            $token = $this->extractBearerToken($request->headers->all());
            $payload = JwtValidator::validateJwt($token);
            // Auth-Informationen in den Request-Attributen für generische Nutzung speichern
            $request->attributes->set('auth', JwtValidator::createAuthInfo($payload));
            return $next($request);
        } catch (AuthorizationException $e) {
            return response()->json(['error' => $e->getMessage()], $e->statusCode);
        }
    }
}
Registriere die Middleware in app/Http/Kernel.php:
protected $middlewareAliases = [
    // ... andere Middleware
    'auth.token' => \App\Http\Middleware\VerifyAccessToken::class,
];
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
class JwtAuthenticator extends AbstractAuthenticator
{
    use AuthHelpers;
    public function supports(Request $request): ?bool
    {
        return $request->headers->has('authorization');
    }
    public function authenticate(Request $request): Passport
    {
        try {
            $token = $this->extractBearerToken($request->headers->all());
            $payload = JwtValidator::validateJwt($token);
            $authInfo = JwtValidator::createAuthInfo($payload);
            // Auth-Informationen in den Request-Attributen für generische Nutzung speichern
            $request->attributes->set('auth', $authInfo);
            return new SelfValidatingPassport(new UserBadge($payload['sub']));
        } catch (AuthorizationException $e) {
            throw new AuthenticationException($e->getMessage());
        }
    }
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        return null; // Weiter zum Controller
    }
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
    {
        return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_UNAUTHORIZED);
    }
}
Konfiguriere die Sicherheit in config/packages/security.yaml:
security:
  firewalls:
    api:
      pattern: ^/api/protected
      stateless: true
      custom_authenticators:
        - App\Security\JwtAuthenticator
<?php
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Psr7\Response;
class JwtMiddleware implements MiddlewareInterface
{
    use AuthHelpers;
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        try {
            $headers = $request->getHeaders();
            $token = $this->extractBearerToken($headers);
            $payload = JwtValidator::validateJwt($token);
            // Authentifizierungsinformationen in den Request-Attributen für generische Nutzung speichern
            $request = $request->withAttribute('auth', JwtValidator::createAuthInfo($payload));
            return $handler->handle($request);
        } catch (AuthorizationException $e) {
            $response = new Response();
            $response->getBody()->write(json_encode(['error' => $e->getMessage()]));
            return $response
                ->withHeader('Content-Type', 'application/json')
                ->withStatus($e->statusCode);
        }
    }
}
Entsprechend deinem Berechtigungsmodell implementiere die passende Überprüfungslogik in JwtValidator:
- Globale API-Ressourcen
 - Organisations-(Nicht-API)-Berechtigungen
 - Organisationsbezogene API-Ressourcen
 
private static function verifyPayload(array $payload): void
{
    // Überprüfe, ob der Audience-Anspruch (audience claim) mit deinem API-Ressourcenindikator übereinstimmt
    $audiences = $payload['aud'] ?? [];
    if (is_string($audiences)) {
        $audiences = [$audiences];
    }
    if (!in_array('https://your-api-resource-indicator', $audiences)) {
        throw new AuthorizationException('Ungültige Zielgruppe');
    }
    // Überprüfe erforderliche Berechtigungen (Scopes) für globale API-Ressourcen
    $requiredScopes = ['api:read', 'api:write']; // Ersetze dies durch deine tatsächlich erforderlichen Berechtigungen
    $scopes = !empty($payload['scope']) ? explode(' ', $payload['scope']) : [];
    foreach ($requiredScopes as $scope) {
        if (!in_array($scope, $scopes)) {
            throw new AuthorizationException('Unzureichende Berechtigung');
        }
    }
}
private static function verifyPayload(array $payload): void
{
    // Überprüfe, ob der Audience-Anspruch dem Organisationsformat entspricht
    $audiences = $payload['aud'] ?? [];
    if (is_string($audiences)) {
        $audiences = [$audiences];
    }
    $hasOrgAudience = false;
    foreach ($audiences as $aud) {
        if (str_starts_with($aud, 'urn:logto:organization:')) {
            $hasOrgAudience = true;
            break;
        }
    }
    if (!$hasOrgAudience) {
        throw new AuthorizationException('Ungültige Zielgruppe für Organisationsberechtigungen');
    }
    // Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (dies musst du ggf. aus dem Request-Kontext extrahieren)
    $expectedOrgId = 'your-organization-id'; // Aus dem Request-Kontext extrahieren
    $expectedAud = "urn:logto:organization:{$expectedOrgId}";
    if (!in_array($expectedAud, $audiences)) {
        throw new AuthorizationException('Organisations-ID stimmt nicht überein');
    }
    // Überprüfe erforderliche Organisations-Berechtigungen (Scopes)
    $requiredScopes = ['invite:users', 'manage:settings']; // Ersetze dies durch deine tatsächlich erforderlichen Berechtigungen
    $scopes = !empty($payload['scope']) ? explode(' ', $payload['scope']) : [];
    foreach ($requiredScopes as $scope) {
        if (!in_array($scope, $scopes)) {
            throw new AuthorizationException('Unzureichende Organisationsberechtigung');
        }
    }
}
private static function verifyPayload(array $payload): void
{
    // Überprüfe, ob der Audience-Anspruch (audience claim) mit deinem API-Ressourcenindikator übereinstimmt
    $audiences = $payload['aud'] ?? [];
    if (is_string($audiences)) {
        $audiences = [$audiences];
    }
    if (!in_array('https://your-api-resource-indicator', $audiences)) {
        throw new AuthorizationException('Ungültige Zielgruppe für organisationsbezogene API-Ressourcen');
    }
    // Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (dies musst du ggf. aus dem Request-Kontext extrahieren)
    $expectedOrgId = 'your-organization-id'; // Aus dem Request-Kontext extrahieren
    $orgId = $payload['organization_id'] ?? null;
    if ($expectedOrgId !== $orgId) {
        throw new AuthorizationException('Organisations-ID stimmt nicht überein');
    }
    // Überprüfe erforderliche Berechtigungen (Scopes) für organisationsbezogene API-Ressourcen
    $requiredScopes = ['api:read', 'api:write']; // Ersetze dies durch deine tatsächlich erforderlichen Berechtigungen
    $scopes = !empty($payload['scope']) ? explode(' ', $payload['scope']) : [];
    foreach ($requiredScopes as $scope) {
        if (!in_array($scope, $scopes)) {
            throw new AuthorizationException('Unzureichende Berechtigungen für organisationsbezogene API-Ressourcen');
        }
    }
}
Wir verwenden das jwt Gem, um JWTs zu validieren. Füge es zu deiner Gemfile hinzu:
gem 'jwt'
# net-http ist seit Ruby 2.7 Teil der Ruby-Standardbibliothek, muss nicht explizit hinzugefügt werden
Führe dann aus:
bundle install
Füge zunächst diese gemeinsamen Hilfsfunktionen hinzu, um JWKS und Token-Validierung zu behandeln:
require 'jwt'
require 'net/http'
require 'json'
class JwtValidator
  include AuthHelpers
  def self.fetch_jwks
    @jwks ||= begin
      uri = URI(AuthConstants::JWKS_URI)
      response = Net::HTTP.get_response(uri)
      raise AuthorizationError.new('Failed to fetch JWKS', 401) unless response.is_a?(Net::HTTPSuccess)
      jwks_data = JSON.parse(response.body)
      JWT::JWK::Set.new(jwks_data)
    end
  end
  def self.validate_jwt(token)
    jwks = fetch_jwks
    # Die JWT-Bibliothek erkennt den Algorithmus automatisch aus dem JWKS
    decoded_token = JWT.decode(token, nil, true, {
      iss: AuthConstants::ISSUER,
      verify_iss: true,
      verify_aud: false, # Die Zielgruppe wird manuell anhand des Berechtigungsmodells überprüft
      jwks: jwks
    })[0]
    verify_payload(decoded_token)
    decoded_token
  end
  def self.create_auth_info(payload)
    scopes = payload['scope']&.split(' ') || []
    audience = payload['aud'] || []
    AuthInfo.new(
      payload['sub'],
      payload['client_id'],
      payload['organization_id'],
      scopes,
      audience
    )
  end
  def self.verify_payload(payload)
    # Implementiere hier deine Überprüfungslogik basierend auf dem Berechtigungsmodell
    # Dies wird im Abschnitt zu den Berechtigungsmodellen unten gezeigt
  end
end
Implementiere anschließend die Middleware, um das Zugangstoken zu überprüfen:
- Ruby on Rails
 - Sinatra
 - Grape
 
module JwtAuthentication
  extend ActiveSupport::Concern
  include AuthHelpers
  included do
    before_action :verify_access_token, only: [:protected_action] # Füge spezifische Aktionen hinzu
  end
  private
  def verify_access_token
    begin
      token = extract_bearer_token(request)
      decoded_token = JwtValidator.validate_jwt(token)
      # Auth-Informationen für generische Nutzung speichern
      @auth = JwtValidator.create_auth_info(decoded_token)
    rescue AuthorizationError => e
      render json: { error: e.message }, status: e.status
    rescue JWT::DecodeError, JWT::VerificationError, JWT::ExpiredSignature => e
      render json: { error: 'Ungültiges Token' }, status: 401
    end
  end
end
class AuthMiddleware
  include AuthHelpers
  def initialize(app)
    @app = app
  end
  def call(env)
    request = Rack::Request.new(env)
    # Schütze nur bestimmte Routen
    if request.path.start_with?('/api/protected')
      begin
        token = extract_bearer_token(request)
        decoded_token = JwtValidator.validate_jwt(token)
        # Auth-Informationen im env für generische Nutzung speichern
        env['auth'] = JwtValidator.create_auth_info(decoded_token)
      rescue AuthorizationError => e
        return [e.status, { 'Content-Type' => 'application/json' }, [{ error: e.message }.to_json]]
      rescue JWT::DecodeError, JWT::VerificationError, JWT::ExpiredSignature => e
        return [401, { 'Content-Type' => 'application/json' }, [{ error: 'Ungültiges Token' }.to_json]]
      end
    end
    @app.call(env)
  end
end
module GrapeAuthHelpers
  include AuthHelpers
  def authenticate_user!
    begin
      token = extract_bearer_token(request)
      decoded_token = JwtValidator.validate_jwt(token)
      # Auth-Informationen für die allgemeine Verwendung speichern
      @auth = JwtValidator.create_auth_info(decoded_token)
    rescue AuthorizationError => e
      error!({ error: e.message }, e.status)
    rescue JWT::DecodeError, JWT::VerificationError, JWT::ExpiredSignature => e
      error!({ error: 'Ungültiges Token' }, 401)
    end
  end
  def auth
    @auth
  end
end
Entsprechend deinem Berechtigungsmodell implementiere die passende Überprüfungslogik in JwtValidator:
- Globale API-Ressourcen
 - Organisations-(Nicht-API)-Berechtigungen
 - Organisationsbezogene API-Ressourcen
 
def self.verify_payload(payload)
  # Überprüfe, ob der Audience-Claim mit deinem API-Ressourcenindikator übereinstimmt
  audiences = payload['aud'] || []
  unless audiences.include?('https://your-api-resource-indicator')
    raise AuthorizationError.new('Invalid audience')
  end
  # Überprüfe erforderliche Berechtigungen für globale API-Ressourcen
  required_scopes = ['api:read', 'api:write'] # Ersetze durch deine tatsächlich erforderlichen Berechtigungen
  token_scopes = payload['scope']&.split(' ') || []
  unless required_scopes.all? { |scope| token_scopes.include?(scope) }
    raise AuthorizationError.new('Insufficient scope')
  end
end
def self.verify_payload(payload)
  # Überprüfe, ob der Audience-Claim dem Organisationsformat entspricht
  audiences = payload['aud'] || []
  has_org_audience = audiences.any? { |aud| aud.start_with?('urn:logto:organization:') }
  unless has_org_audience
    raise AuthorizationError.new('Invalid audience for organization permissions')
  end
  # Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (dies musst du ggf. aus dem Request-Kontext extrahieren)
  expected_org_id = 'your-organization-id' # Aus dem Request-Kontext extrahieren
  expected_aud = "urn:logto:organization:#{expected_org_id}"
  unless audiences.include?(expected_aud)
    raise AuthorizationError.new('Organization ID mismatch')
  end
  # Überprüfe erforderliche Organisationsberechtigungen
  required_scopes = ['invite:users', 'manage:settings'] # Ersetze durch deine tatsächlich erforderlichen Berechtigungen
  token_scopes = payload['scope']&.split(' ') || []
  unless required_scopes.all? { |scope| token_scopes.include?(scope) }
    raise AuthorizationError.new('Insufficient organization scope')
  end
end
def self.verify_payload(payload)
  # Überprüfe, ob der Audience-Claim mit deinem API-Ressourcenindikator übereinstimmt
  audiences = payload['aud'] || []
  unless audiences.include?('https://your-api-resource-indicator')
    raise AuthorizationError.new('Invalid audience for organization-level API resources')
  end
  # Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (dies musst du ggf. aus dem Request-Kontext extrahieren)
  expected_org_id = 'your-organization-id' # Aus dem Request-Kontext extrahieren
  org_id = payload['organization_id']
  unless expected_org_id == org_id
    raise AuthorizationError.new('Organization ID mismatch')
  end
  # Überprüfe erforderliche Berechtigungen für organisationsbezogene API-Ressourcen
  required_scopes = ['api:read', 'api:write'] # Ersetze durch deine tatsächlich erforderlichen Berechtigungen
  token_scopes = payload['scope']&.split(' ') || []
  unless required_scopes.all? { |scope| token_scopes.include?(scope) }
    raise AuthorizationError.new('Insufficient organization-level API scopes')
  end
end
Wir verwenden jsonwebtoken, um JWTs zu validieren. Füge die benötigten Abhängigkeiten zu deiner Cargo.toml hinzu:
[dependencies]
jsonwebtoken = "9.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1.0", features = ["full"] }
Füge zunächst diese gemeinsamen Hilfsfunktionen hinzu, um die JWT-Validierung zu behandeln:
use crate::{AuthInfo, AuthorizationError, ISSUER, JWKS_URI};
use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation};
use serde_json::Value;
use std::collections::HashMap;
pub struct JwtValidator {
    jwks: HashMap<String, DecodingKey>,
}
impl JwtValidator {
    pub async fn new() -> Result<Self, AuthorizationError> {
        let jwks = Self::fetch_jwks().await?;
        Ok(Self { jwks })
    }
    async fn fetch_jwks() -> Result<HashMap<String, DecodingKey>, AuthorizationError> {
        let response = reqwest::get(JWKS_URI).await.map_err(|e| {
            AuthorizationError::with_status(format!("Failed to fetch JWKS: {}", e), 401)
        })?;
        let jwks: Value = response.json().await.map_err(|e| {
            AuthorizationError::with_status(format!("Failed to parse JWKS: {}", e), 401)
        })?;
        let mut keys = HashMap::new();
        if let Some(keys_array) = jwks["keys"].as_array() {
            for key in keys_array {
                if let (Some(kid), Some(kty), Some(n), Some(e)) = (
                    key["kid"].as_str(),
                    key["kty"].as_str(),
                    key["n"].as_str(),
                    key["e"].as_str(),
                ) {
                    if kty == "RSA" {
                        if let Ok(decoding_key) = DecodingKey::from_rsa_components(n, e) {
                            keys.insert(kid.to_string(), decoding_key);
                        }
                    }
                }
            }
        }
        if keys.is_empty() {
            return Err(AuthorizationError::with_status("No valid keys found in JWKS", 401));
        }
        Ok(keys)
    }
    pub fn validate_jwt(&self, token: &str) -> Result<AuthInfo, AuthorizationError> {
        let header = decode_header(token).map_err(|e| {
            AuthorizationError::with_status(format!("Invalid token header: {}", e), 401)
        })?;
        let kid = header.kid.ok_or_else(|| {
            AuthorizationError::with_status("Token missing kid claim", 401)
        })?;
        let key = self.jwks.get(&kid).ok_or_else(|| {
            AuthorizationError::with_status("Unknown key ID", 401)
        })?;
        let mut validation = Validation::new(Algorithm::RS256);
        validation.set_issuer(&[ISSUER]);
        validation.validate_aud = false; // Wir überprüfen die Zielgruppe manuell
        let token_data = decode::<Value>(token, key, &validation).map_err(|e| {
            AuthorizationError::with_status(format!("Invalid token: {}", e), 401)
        })?;
        let claims = token_data.claims;
        self.verify_payload(&claims)?;
        Ok(self.create_auth_info(claims))
    }
    fn verify_payload(&self, claims: &Value) -> Result<(), AuthorizationError> {
        // Implementiere hier deine Verifizierungslogik basierend auf dem Berechtigungsmodell
        // Dies wird im Abschnitt zu den Berechtigungsmodellen unten gezeigt
        Ok(())
    }
    fn create_auth_info(&self, claims: Value) -> AuthInfo {
        let scopes = claims["scope"]
            .as_str()
            .map(|s| s.split(' ').map(|s| s.to_string()).collect())
            .unwrap_or_default();
        let audience = match &claims["aud"] {
            Value::Array(arr) => arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect(),
            Value::String(s) => vec![s.clone()],
            _ => vec![],
        };
        AuthInfo::new(
            claims["sub"].as_str().unwrap_or_default().to_string(),
            claims["client_id"].as_str().map(|s| s.to_string()),
            claims["organization_id"].as_str().map(|s| s.to_string()),
            scopes,
            audience,
        )
    }
}
Implementiere anschließend das Middleware, um das Zugangstoken zu überprüfen:
- Axum
 - Actix Web
 - Rocket
 
use crate::{AuthInfo, AuthorizationError, extract_bearer_token};
use crate::jwt_validator::JwtValidator;
use axum::{
    extract::Request,
    http::{HeaderMap, StatusCode},
    middleware::Next,
    response::{IntoResponse, Response},
    Extension, Json,
};
use serde_json::json;
use std::sync::Arc;
// JWT-Middleware zur Überprüfung und Speicherung von Authentifizierungsinformationen
pub async fn jwt_middleware(
    Extension(validator): Extension<Arc<JwtValidator>>,
    headers: HeaderMap,
    mut request: Request,
    next: Next,
) -> Result<Response, AuthorizationError> {
    let authorization = headers
        .get("authorization")
        .and_then(|h| h.to_str().ok());
    let token = extract_bearer_token(authorization)?;
    let auth_info = validator.validate_jwt(token)?;
    // Authentifizierungsinformationen in den Request-Extensions für generische Nutzung speichern
    request.extensions_mut().insert(auth_info);
    Ok(next.run(request).await)
}
// Fehlerbehandlung für Autorisierungsfehler
impl IntoResponse for AuthorizationError {
    fn into_response(self) -> Response {
        let status = StatusCode::from_u16(self.status_code).unwrap_or(StatusCode::FORBIDDEN);
        (status, Json(json!({ "error": self.message }))).into_response()
    }
}
use crate::{AuthInfo, AuthorizationError, extract_bearer_token};
use crate::jwt_validator::JwtValidator;
use actix_web::{
    dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
    web, Error, HttpMessage, HttpResponse,
};
use futures::future::{ok, Ready};
use std::sync::Arc;
pub struct JwtMiddleware {
    validator: Arc<JwtValidator>,
}
impl JwtMiddleware {
    pub fn new(validator: Arc<JwtValidator>) -> Self {
        Self { validator }
    }
}
impl<S, B> Transform<S, ServiceRequest> for JwtMiddleware
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = JwtMiddlewareService<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;
    fn new_transform(&self, service: S) -> Self::Future {
        ok(JwtMiddlewareService {
            service,
            validator: self.validator.clone(),
        })
    }
}
pub struct JwtMiddlewareService<S> {
    service: S,
    validator: Arc<JwtValidator>,
}
impl<S, B> Service<ServiceRequest> for JwtMiddlewareService<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = futures::future::LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
    forward_ready!(service);
    fn call(&self, req: ServiceRequest) -> Self::Future {
        let validator = self.validator.clone();
        Box::pin(async move {
            let authorization = req
                .headers()
                .get("authorization")
                .and_then(|h| h.to_str().ok());
            match extract_bearer_token(authorization)
                .and_then(|token| validator.validate_jwt(token))
            {
                Ok(auth_info) => {
                    // Auth-Informationen in den Request-Extensions für generische Nutzung speichern
                    req.extensions_mut().insert(auth_info);
                    let fut = self.service.call(req);
                    fut.await
                }
                Err(e) => {
                    let response = HttpResponse::build(
                        actix_web::http::StatusCode::from_u16(e.status_code)
                            .unwrap_or(actix_web::http::StatusCode::FORBIDDEN),
                    )
                    .json(serde_json::json!({ "error": e.message }));
                    Ok(req.into_response(response))
                }
            }
        })
    }
}
use crate::{AuthInfo, AuthorizationError, extract_bearer_token};
use crate::jwt_validator::JwtValidator;
use rocket::{
    http::Status,
    outcome::Outcome,
    request::{self, FromRequest, Request},
    State,
};
#[rocket::async_trait]
impl<'r> FromRequest<'r> for AuthInfo {
    type Error = AuthorizationError;
    async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
        let validator = match req.guard::<&State<JwtValidator>>().await {
            Outcome::Success(validator) => validator,
            Outcome::Failure((status, _)) => {
                return Outcome::Failure((
                    status,
                    AuthorizationError::with_status("JWT validator not found", 500),
                ))
            }
            Outcome::Forward(()) => {
                return Outcome::Forward(())
            }
        };
        let authorization = req.headers().get_one("authorization");
        match extract_bearer_token(authorization)
            .and_then(|token| validator.validate_jwt(token))
        {
            Ok(auth_info) => Outcome::Success(auth_info),
            Err(e) => {
                let status = Status::from_code(e.status_code).unwrap_or(Status::Forbidden);
                Outcome::Failure((status, e))
            }
        }
    }
}
Entsprechend deinem Berechtigungsmodell implementiere die passende Verifizierungslogik in JwtValidator:
- Globale API-Ressourcen
 - Organisation (nicht-API) Berechtigungen
 - Organisationsbezogene API-Ressourcen
 
fn verify_payload(&self, claims: &Value) -> Result<(), AuthorizationError> {
    // Überprüfe, ob der Audience-Claim mit deinem API-Ressourcenindikator übereinstimmt
    let audiences = match &claims["aud"] {
        Value::Array(arr) => arr.iter().filter_map(|v| v.as_str()).collect::<Vec<_>>(),
        Value::String(s) => vec![s.as_str()],
        _ => vec![],
    };
    if !audiences.contains(&"https://your-api-resource-indicator") {
        return Err(AuthorizationError::new("Invalid audience"));
    }
    // Überprüfe erforderliche Berechtigungen für globale API-Ressourcen
    let required_scopes = vec!["api:read", "api:write"]; // Ersetze durch deine tatsächlich benötigten Berechtigungen
    let scopes = claims["scope"]
        .as_str()
        .map(|s| s.split(' ').collect::<Vec<_>>())
        .unwrap_or_default();
    for required_scope in &required_scopes {
        if !scopes.contains(required_scope) {
            return Err(AuthorizationError::new("Insufficient scope"));
        }
    }
    Ok(())
}
fn verify_payload(&self, claims: &Value) -> Result<(), AuthorizationError> {
    // Überprüfe, ob der Audience-Claim dem Organisationsformat entspricht
    let audiences = match &claims["aud"] {
        Value::Array(arr) => arr.iter().filter_map(|v| v.as_str()).collect::<Vec<_>>(),
        Value::String(s) => vec![s.as_str()],
        _ => vec![],
    };
    let has_org_audience = audiences.iter().any(|aud| aud.starts_with("urn:logto:organization:"));
    if !has_org_audience {
        return Err(AuthorizationError::new("Invalid audience for organization permissions"));
    }
    // Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (du musst dies ggf. aus dem Request-Kontext extrahieren)
    let expected_org_id = "your-organization-id"; // Aus dem Request-Kontext extrahieren
    let expected_aud = format!("urn:logto:organization:{}", expected_org_id);
    if !audiences.contains(&expected_aud.as_str()) {
        return Err(AuthorizationError::new("Organization ID mismatch"));
    }
    // Überprüfe erforderliche Organisationsberechtigungen
    let required_scopes = vec!["invite:users", "manage:settings"]; // Ersetze durch deine tatsächlich benötigten Berechtigungen
    let scopes = claims["scope"]
        .as_str()
        .map(|s| s.split(' ').collect::<Vec<_>>())
        .unwrap_or_default();
    for required_scope in &required_scopes {
        if !scopes.contains(required_scope) {
            return Err(AuthorizationError::new("Insufficient organization scope"));
        }
    }
    Ok(())
}
fn verify_payload(&self, claims: &Value) -> Result<(), AuthorizationError> {
    // Überprüfe, ob der Audience-Claim mit deinem API-Ressourcenindikator übereinstimmt
    let audiences = match &claims["aud"] {
        Value::Array(arr) => arr.iter().filter_map(|v| v.as_str()).collect::<Vec<_>>(),
        Value::String(s) => vec![s.as_str()],
        _ => vec![],
    };
    if !audiences.contains(&"https://your-api-resource-indicator") {
        return Err(AuthorizationError::new("Invalid audience for organization-level API resources"));
    }
    // Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (du musst dies ggf. aus dem Request-Kontext extrahieren)
    let expected_org_id = "your-organization-id"; // Aus dem Request-Kontext extrahieren
    let org_id = claims["organization_id"].as_str().unwrap_or_default();
    if expected_org_id != org_id {
        return Err(AuthorizationError::new("Organization ID mismatch"));
    }
    // Überprüfe erforderliche Berechtigungen für organisationsbezogene API-Ressourcen
    let required_scopes = vec!["api:read", "api:write"]; // Ersetze durch deine tatsächlich benötigten Berechtigungen
    let scopes = claims["scope"]
        .as_str()
        .map(|s| s.split(' ').collect::<Vec<_>>())
        .unwrap_or_default();
    for required_scope in &required_scopes {
        if !scopes.contains(required_scope) {
            return Err(AuthorizationError::new("Insufficient organization-level API scopes"));
        }
    }
    Ok(())
}
Schritt 4: Middleware auf deine API anwenden
Wende die Middleware auf deine geschützten API-Routen an.
- Node.js
 - Python
 - Go
 - Java
 - .NET
 - PHP
 - Ruby
 - Rust
 
- Express.js
 - Koa.js
 - Fastify
 - Hapi.js
 - NestJS
 
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);
import Koa from 'koa';
import Router from '@koa/router';
import { koaVerifyAccessToken } from './auth-middleware.js';
const app = new Koa();
const router = new Router();
router.get('/api/protected', koaVerifyAccessToken, (ctx) => {
  // Greife direkt über ctx.state.auth auf Authentifizierungsinformationen zu
  ctx.body = { auth: ctx.state.auth };
});
router.get('/api/protected/detailed', koaVerifyAccessToken, (ctx) => {
  // Deine geschützte Endpunkt-Logik
  ctx.body = {
    auth: ctx.state.auth,
    message: 'Geschützte Daten erfolgreich abgerufen',
  };
});
app.use(router.routes());
app.listen(3000);
import Fastify from 'fastify';
import { fastifyVerifyAccessToken } from './auth-middleware.js';
const fastify = Fastify();
server.get('/api/protected', { preHandler: fastifyVerifyAccessToken }, (request, reply) => {
  // Greife direkt über request.auth auf Authentifizierungsinformationen zu
  reply.send({ auth: request.auth });
});
server.get(
  '/api/protected/detailed',
  { preHandler: fastifyVerifyAccessToken },
  (request, reply) => {
    // Deine geschützte Endpunkt-Logik
    reply.send({
      auth: request.auth,
      message: 'Geschützte Daten erfolgreich abgerufen',
    });
  }
);
fastify.listen({ port: 3000 });
import Hapi from '@hapi/hapi';
import { hapiVerifyAccessToken } from './auth-middleware.js';
const server = Hapi.server({ port: 3000 });
server.route({
  method: 'GET',
  path: '/api/protected',
  options: {
    pre: [{ method: hapiVerifyAccessToken }],
    handler: (request, h) => {
      // Greife auf Authentifizierungsinformationen über request.app.auth zu
      return { auth: request.app.auth };
    },
  },
});
server.route({
  method: 'GET',
  path: '/api/protected/detailed',
  options: {
    pre: [{ method: hapiVerifyAccessToken }],
    handler: (request, h) => {
      // Deine geschützte Endpunkt-Logik
      return {
        auth: request.app.auth,
        message: 'Geschützte Daten erfolgreich abgerufen',
      };
    },
  },
});
await server.start();
import { Controller, Get, UseGuards, Req } from '@nestjs/common';
import { AccessTokenGuard } from './access-token.guard.js';
@Controller('api')
export class ProtectedController {
  @Get('protected')
  @UseGuards(AccessTokenGuard)
  getProtected(@Req() req: any) {
    // Greife auf Authentifizierungsinformationen über req.auth zu
    return { auth: req.auth };
  }
  @Get('protected/detailed')
  @UseGuards(AccessTokenGuard)
  getDetailedProtected(@Req() req: any) {
    // Deine geschützte Endpunkt-Logik
    return {
      auth: req.auth,
      message: 'Geschützte Daten erfolgreich abgerufen',
    };
  }
}
- FastAPI
 - Flask
 - Django
 - Django REST Framework
 
from fastapi import FastAPI, Depends
from auth_middleware import verify_access_token, AuthInfo
app = FastAPI()
@app.get("/api/protected")
async def protected_endpoint(auth: AuthInfo = Depends(verify_access_token)):
    # Greife direkt über den auth-Parameter auf Authentifizierungsinformationen zu
    return {"auth": auth.to_dict()}
from flask import Flask, g, jsonify
from auth_middleware import verify_access_token
app = Flask(__name__)
@app.route('/api/protected', methods=['GET'])
@verify_access_token
def protected_endpoint():
    # Zugriff auf Authentifizierungsinformationen aus g.auth
    return jsonify({"auth": g.auth.to_dict()})
from django.http import JsonResponse
from auth_middleware import require_access_token
@require_access_token
def protected_view(request):
    # Zugriff auf Authentifizierungsinformationen über request.auth
    return JsonResponse({"auth": request.auth.to_dict()})
from django.urls import path
from . import views
urlpatterns = [
    path('api/protected/', views.protected_view, name='protected'),
]
from rest_framework.decorators import api_view, authentication_classes
from rest_framework.response import Response
from auth_middleware import AccessTokenAuthentication
@api_view(['GET'])
@authentication_classes([AccessTokenAuthentication])
def protected_view(request):
    # Zugriff auf Authentifizierungsinformationen über request.user.auth
    return Response({"auth": request.user.auth.to_dict()})
Oder mit klassenbasierten Views:
from rest_framework.views import APIView
from rest_framework.response import Response
from auth_middleware import AccessTokenAuthentication
class ProtectedView(APIView):
    authentication_classes = [AccessTokenAuthentication]
    def get(self, request):
        # Zugriff auf Authentifizierungsinformationen über request.user.auth
        return Response({"auth": request.user.auth.to_dict()})
from django.urls import path
from . import views
urlpatterns = [
    path('api/protected/', views.protected_view, name='protected'),
    # Oder für klassenbasierte Views:
    # path('api/protected/', views.ProtectedView.as_view(), name='protected'),
]
- Gin
 - Echo
 - Fiber
 - Chi
 
package main
import (
    "net/http"
    "github.com/gin-gonic/gin"
    "github.com/lestrrat-go/jwx/v3/jwt"
)
func main() {
    r := gin.Default()
    // Middleware auf geschützte Routen anwenden
    r.GET("/api/protected", VerifyAccessToken(), func(c *gin.Context) {
        // Zugangstoken (Access token) Informationen direkt aus dem Kontext abrufen
        tokenInterface, exists := c.Get("auth")
        if !exists {
            c.JSON(http.StatusInternalServerError, gin.H{"error": "Token nicht gefunden"})
            return
        }
        token := tokenInterface.(jwt.Token)
        c.JSON(http.StatusOK, gin.H{
            "sub":             token.Subject(),
            "client_id":       getStringClaim(token, "client_id"),
            "organization_id": getStringClaim(token, "organization_id"),
            "scopes":          getScopesFromToken(token),
            "audience":        getAudienceFromToken(token),
        })
    })
    r.Run(":8080")
}
package main
import (
    "net/http"
    "github.com/labstack/echo/v4"
    "github.com/lestrrat-go/jwx/v3/jwt"
)
func main() {
    e := echo.New()
    // Middleware auf geschützte Routen anwenden
    e.GET("/api/protected", func(c echo.Context) error {
        // Zugangstoken-Informationen direkt aus dem Kontext abrufen
        tokenInterface := c.Get("auth")
        if tokenInterface == nil {
            return c.JSON(http.StatusInternalServerError, echo.Map{"error": "Token nicht gefunden"})
        }
        token := tokenInterface.(jwt.Token)
        return c.JSON(http.StatusOK, echo.Map{
            "sub":             token.Subject(),
            "client_id":       getStringClaim(token, "client_id"),
            "organization_id": getStringClaim(token, "organization_id"),
            "scopes":          getScopesFromToken(token),
            "audience":        getAudienceFromToken(token),
        })
    }, VerifyAccessToken)
    e.Start(":8080")
}
Oder mit Routengruppen:
package main
import (
    "github.com/labstack/echo/v4"
    "github.com/lestrrat-go/jwx/v3/jwt"
)
func main() {
    e := echo.New()
    // Geschützte Routengruppe erstellen
    api := e.Group("/api", VerifyAccessToken)
    api.GET("/protected", func(c echo.Context) error {
        // Zugangstoken-Informationen direkt aus dem Kontext abrufen
        token := c.Get("auth").(jwt.Token)
        return c.JSON(200, echo.Map{
            "sub":             token.Subject(),
            "client_id":       getStringClaim(token, "client_id"),
            "organization_id": getStringClaim(token, "organization_id"),
            "scopes":          getScopesFromToken(token),
            "audience":        getAudienceFromToken(token),
            "message":         "Geschützte Daten erfolgreich abgerufen",
        })
    })
    e.Start(":8080")
}
package main
import (
    "github.com/gofiber/fiber/v2"
    "github.com/lestrrat-go/jwx/v3/jwt"
)
func main() {
    app := fiber.New()
    // Middleware auf geschützte Routen anwenden
    app.Get("/api/protected", VerifyAccessToken, func(c *fiber.Ctx) error {
        // Zugangstoken-Informationen direkt aus locals abrufen
        tokenInterface := c.Locals("auth")
        if tokenInterface == nil {
            return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Token nicht gefunden"})
        }
        token := tokenInterface.(jwt.Token)
        return c.JSON(fiber.Map{
            "sub":             token.Subject(),
            "client_id":       getStringClaim(token, "client_id"),
            "organization_id": getStringClaim(token, "organization_id"),
            "scopes":          getScopesFromToken(token),
            "audience":        getAudienceFromToken(token),
        })
    })
    app.Listen(":8080")
}
Oder mit Routengruppen:
package main
import (
    "github.com/gofiber/fiber/v2"
    "github.com/lestrrat-go/jwx/v3/jwt"
)
func main() {
    app := fiber.New()
    // Geschützte Routengruppe erstellen
    api := app.Group("/api", VerifyAccessToken)
    api.Get("/protected", func(c *fiber.Ctx) error {
        // Zugangstoken-Informationen direkt aus locals abrufen
        token := c.Locals("auth").(jwt.Token)
        return c.JSON(fiber.Map{
            "sub":             token.Subject(),
            "client_id":       getStringClaim(token, "client_id"),
            "organization_id": getStringClaim(token, "organization_id"),
            "scopes":          getScopesFromToken(token),
            "audience":        getAudienceFromToken(token),
            "message":         "Geschützte Daten erfolgreich abgerufen",
        })
    })
    app.Listen(":8080")
}
package main
import (
    "encoding/json"
    "net/http"
    "github.com/go-chi/chi/v5"
    "github.com/lestrrat-go/jwx/v3/jwt"
)
func main() {
    r := chi.NewRouter()
    // Middleware auf geschützte Routen anwenden
    r.With(VerifyAccessToken).Get("/api/protected", func(w http.ResponseWriter, r *http.Request) {
        // Zugangstoken-Informationen direkt aus dem Kontext
        tokenInterface := r.Context().Value(AuthContextKey)
        if tokenInterface == nil {
            w.Header().Set("Content-Type", "application/json")
            w.WriteHeader(http.StatusInternalServerError)
            json.NewEncoder(w).Encode(map[string]string{"error": "Token nicht gefunden"})
            return
        }
        token := tokenInterface.(jwt.Token)
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]interface{}{
            "sub":             token.Subject(),
            "client_id":       getStringClaim(token, "client_id"),
            "organization_id": getStringClaim(token, "organization_id"),
            "scopes":          getScopesFromToken(token),
            "audience":        getAudienceFromToken(token),
        })
    })
    http.ListenAndServe(":8080", r)
}
Oder mit Routengruppen:
package main
import (
    "encoding/json"
    "net/http"
    "github.com/go-chi/chi/v5"
    "github.com/lestrrat-go/jwx/v3/jwt"
)
func main() {
    r := chi.NewRouter()
    // Geschützte Routengruppe erstellen
    r.Route("/api", func(r chi.Router) {
        r.Use(VerifyAccessToken)
        r.Get("/protected", func(w http.ResponseWriter, r *http.Request) {
            // Zugangstoken-Informationen direkt aus dem Kontext
            token := r.Context().Value(AuthContextKey).(jwt.Token)
            w.Header().Set("Content-Type", "application/json")
            json.NewEncoder(w).Encode(map[string]interface{}{
                "sub":             token.Subject(),
                "client_id":       getStringClaim(token, "client_id"),
                "organization_id": getStringClaim(token, "organization_id"),
                "scopes":          getScopesFromToken(token),
                "audience":        getAudienceFromToken(token),
                "message":         "Geschützte Daten erfolgreich abgerufen",
            })
        })
    })
    http.ListenAndServe(":8080", r)
}
- Spring Boot
 - Quarkus
 - Micronaut
 - Vert.x Web
 
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@RestController
public class ProtectedController {
    @GetMapping("/api/protected")
    public Map<String, Object> protectedEndpoint(@AuthenticationPrincipal Jwt jwt) {
        // Zugangstoken-Informationen direkt aus dem JWT abrufen
        String scopes = jwt.getClaimAsString("scope");
        List<String> scopeList = scopes != null ? Arrays.asList(scopes.split(" ")) : List.of();
        return Map.of(
            "sub", jwt.getSubject(),
            "client_id", jwt.getClaimAsString("client_id"),
            "organization_id", jwt.getClaimAsString("organization_id"),
            "scopes", scopeList,
            "audience", jwt.getAudience()
        );
    }
}
import org.eclipse.microprofile.jwt.JsonWebToken;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.container.ContainerRequestContext;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@Path("/api")
public class ProtectedResource {
    @Inject
    JsonWebToken jwt;
    @GET
    @Path("/protected")
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, Object> protectedEndpoint(@Context ContainerRequestContext requestContext) {
        // Zugriff auf das JWT direkt über Injection oder Kontext
        JsonWebToken token = (JsonWebToken) requestContext.getProperty("auth");
        if (token == null) {
            token = jwt; // Rückgriff auf injiziertes JWT
        }
        String scopes = token.getClaim("scope");
        List<String> scopeList = scopes != null ? Arrays.asList(scopes.split(" ")) : List.of();
        return Map.of(
            "sub", token.getSubject(),
            "client_id", token.<String>getClaim("client_id"),
            "organization_id", token.<String>getClaim("organization_id"),
            "scopes", scopeList,
            "audience", token.getAudience()
        );
    }
}
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.authentication.Authentication;
import io.micronaut.security.rules.SecurityRule;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@Controller("/api")
@Secured(SecurityRule.IS_AUTHENTICATED)
public class ProtectedController {
    @Get("/protected")
    public Map<String, Object> protectedEndpoint(Authentication authentication) {
        // Zugangstoken-Informationen direkt aus Authentication abrufen
        String scopes = (String) authentication.getAttributes().get("scope");
        List<String> scopeList = scopes != null ? Arrays.asList(scopes.split(" ")) : List.of();
        return Map.of(
            "sub", authentication.getName(),
            "client_id", authentication.getAttributes().get("client_id"),
            "organization_id", authentication.getAttributes().get("organization_id"),
            "scopes", scopeList,
            "audience", authentication.getAttributes().get("aud")
        );
    }
}
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
public class MainVerticle extends AbstractVerticle {
    @Override
    public void start(Promise<Void> startPromise) throws Exception {
        Router router = Router.router(vertx);
        // Middleware auf geschützte Routen anwenden
        router.route("/api/protected*").handler(new JwtAuthHandler(vertx));
        router.get("/api/protected").handler(this::protectedEndpoint);
        vertx.createHttpServer()
            .requestHandler(router)
            .listen(8080, result -> {
                if (result.succeeded()) {
                    startPromise.complete();
                } else {
                    startPromise.fail(result.cause());
                }
            });
    }
    private void protectedEndpoint(RoutingContext context) {
        // Zugriff auf das JWT-Prinzipal direkt aus dem Kontext
        JsonObject principal = context.get("auth");
        if (principal == null) {
            context.response()
                .setStatusCode(500)
                .putHeader("Content-Type", "application/json")
                .end("{\"error\": \"JWT principal not found\"}");
            return;
        }
        String scopes = principal.getString("scope");
        JsonObject response = new JsonObject()
            .put("sub", principal.getString("sub"))
            .put("client_id", principal.getString("client_id"))
            .put("organization_id", principal.getString("organization_id"))
            .put("scopes", scopes != null ? scopes.split(" ") : new String[0])
            .put("audience", principal.getJsonArray("aud"));
        context.response()
            .putHeader("Content-Type", "application/json")
            .end(response.encode());
    }
}
Wir haben bereits die Authentifizierungs- und Autorisierungsmiddleware in den vorherigen Abschnitten eingerichtet. Jetzt können wir einen geschützten Controller erstellen, der Zugangstokens validiert und Ansprüche (Claims) aus authentifizierten Anfragen extrahiert.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace YourApiNamespace.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    [Authorize] // Authentifizierung für alle Aktionen in diesem Controller erforderlich
    public class ProtectedController : ControllerBase
    {
        [HttpGet]
        public IActionResult GetProtectedData()
        {
            // Informationen aus dem Zugangstoken direkt aus den User-Ansprüchen (Claims) abrufen
            var sub = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? User.FindFirst("sub")?.Value;
            var clientId = User.FindFirst("client_id")?.Value;
            var organizationId = User.FindFirst("organization_id")?.Value;
            var scopes = User.FindFirst("scope")?.Value?.Split(' ') ?? Array.Empty<string>();
            var audience = User.FindAll("aud").Select(c => c.Value).ToArray();
            return Ok(new {
                sub,
                client_id = clientId,
                organization_id = organizationId,
                scopes,
                audience
            });
        }
        [HttpGet("claims")]
        public IActionResult GetAllClaims()
        {
            // Gibt alle Ansprüche (Claims) zur Fehleranalyse / Überprüfung zurück
            var claims = User.Claims.Select(c => new { c.Type, c.Value }).ToList();
            return Ok(new { claims });
        }
    }
}
- Laravel
 - Symfony
 - Slim
 
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::middleware('auth.token')->group(function () {
    Route::get('/api/protected', function (Request $request) {
        // Zugriff auf Authentifizierungsinformationen aus den Request-Attributen
        $auth = $request->attributes->get('auth');
        return ['auth' => $auth->toArray()];
    });
});
Oder mit Controllern:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ProtectedController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth.token');
    }
    public function index(Request $request)
    {
        // Zugriff auf Authentifizierungsinformationen aus den Request-Attributen
        $auth = $request->attributes->get('auth');
        return ['auth' => $auth->toArray()];
    }
    public function show(Request $request)
    {
        // Deine Logik für den geschützten Endpunkt
        $auth = $request->attributes->get('auth');
        return [
            'auth' => $auth->toArray(),
            'message' => 'Geschützte Daten wurden erfolgreich abgerufen'
        ];
    }
}
<?php
namespace App\Controller\Api;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/api/protected')]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class ProtectedController extends AbstractController
{
    #[Route('', methods: ['GET'])]
    public function index(Request $request): JsonResponse
    {
        // Zugriff auf Authentifizierungsinformationen aus den Request-Attributen
        $auth = $request->attributes->get('auth');
        return $this->json(['auth' => $auth->toArray()]);
    }
}
<?php
namespace App\Controllers;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class ProtectedController
{
    public function index(Request $request, Response $response): Response
    {
        // Zugriff auf Authentifizierungsinformationen aus den Request-Attributen
        $auth = $request->getAttribute('auth');
        $response->getBody()->write(json_encode(['auth' => $auth->toArray()]));
        return $response->withHeader('Content-Type', 'application/json');
    }
    public function detailed(Request $request, Response $response): Response
    {
        // Deine Logik für den geschützten Endpunkt
        $auth = $request->getAttribute('auth');
        $data = [
            'auth' => $auth->toArray(),
            'message' => 'Geschützte Daten wurden erfolgreich abgerufen'
        ];
        $response->getBody()->write(json_encode($data));
        return $response->withHeader('Content-Type', 'application/json');
    }
}
- Ruby on Rails
 - Sinatra
 - Grape
 
class ApplicationController < ActionController::API # Für API-only-Apps
# class ApplicationController < ActionController::Base # Für vollständige Rails-Apps
  include JwtAuthentication
end
class Api::ProtectedController < ApplicationController
  before_action :verify_access_token
  def index
    # Zugriff auf Auth-Informationen über @auth
    render json: { auth: @auth.to_h }
  end
end
Rails.application.routes.draw do
  namespace :api do
    resources :protected, only: [:index]
  end
end
require 'sinatra'
require 'json'
require_relative 'auth_middleware'
require_relative 'auth_constants'
require_relative 'auth_info'
require_relative 'authorization_error'
require_relative 'auth_helpers'
require_relative 'jwt_validator'
# Middleware anwenden
use AuthMiddleware
get '/api/protected' do
  content_type :json
  # Authentifizierungsinformationen aus env abrufen
  auth = env['auth']
  { auth: auth.to_h }.to_json
end
# Öffentlicher Endpunkt (nicht durch Middleware geschützt)
get '/' do
  content_type :json
  { message: "Öffentlicher Endpunkt" }.to_json
end
require 'grape'
require_relative 'auth_helpers'
require_relative 'auth_constants'
require_relative 'auth_info'
require_relative 'authorization_error'
require_relative 'jwt_validator'
class API < Grape::API
  format :json
  helpers GrapeAuthHelpers
  namespace :api do
    namespace :protected do
      before do
        authenticate_user!
      end
      get do
        # Zugriff auf Authentifizierungsinformationen aus dem Auth-Helper
        { auth: auth.to_h }
      end
    end
  end
  # Öffentlicher Endpunkt (nicht geschützt)
  get :public do
    { message: "Öffentlicher Endpunkt" }
  end
end
require_relative 'api'
run API
- Axum
 - Actix Web
 - Rocket
 
use axum::{
    extract::Extension,
    http::StatusCode,
    middleware,
    response::Json,
    routing::get,
    Router,
};
use serde_json::{json, Value};
use std::sync::Arc;
use tower_http::cors::CorsLayer;
mod lib;
mod jwt_validator;
mod middleware as jwt_middleware;
use lib::AuthInfo;
use jwt_validator::JwtValidator;
#[tokio::main]
async fn main() {
    let validator = Arc::new(JwtValidator::new().await.expect("Initialisierung des JWT-Validators fehlgeschlagen"));
    let app = Router::new()
        .route("/api/protected", get(protected_handler))
        .layer(middleware::from_fn(jwt_middleware::jwt_middleware))
        .layer(Extension(validator))
        .layer(CorsLayer::permissive());
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
async fn protected_handler(Extension(auth): Extension<AuthInfo>) -> Json<Value> {
    // Greife direkt über Extension auf Auth-Informationen zu
    Json(json!({ "auth": auth }))
}
use actix_web::{middleware::Logger, web, App, HttpRequest, HttpServer, Result};
use serde_json::{json, Value};
use std::sync::Arc;
mod lib;
mod jwt_validator;
mod middleware as jwt_middleware;
use lib::AuthInfo;
use jwt_validator::JwtValidator;
use jwt_middleware::JwtMiddleware;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let validator = Arc::new(JwtValidator::new().await.expect("Fehler beim Initialisieren des JWT-Validators"));
    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(validator.clone()))
            .wrap(Logger::default())
            .service(
                web::scope("/api/protected")
                    .wrap(JwtMiddleware::new(validator.clone()))
                    .route("", web::get().to(protected_handler))
            )
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}
async fn protected_handler(req: HttpRequest) -> Result<web::Json<Value>> {
    // Zugriff auf Authentifizierungsinformationen aus den Erweiterungen der Anfrage
    let auth = req.extensions().get::<AuthInfo>().unwrap();
    Ok(web::Json(json!({ "auth": auth })))
}
use rocket::{get, launch, routes, serde::json::Json};
use serde_json::{json, Value};
mod lib;
mod jwt_validator;
mod guards;
use lib::AuthInfo;
use jwt_validator::JwtValidator;
#[get("/api/protected")]
fn protected_handler(auth: AuthInfo) -> Json<Value> {
    // Greife direkt über den Request Guard auf Authentifizierungsinformationen zu
    Json(json!({ "auth": auth }))
}
#[launch]
async fn rocket() -> _ {
    let validator = JwtValidator::new().await.expect("Initialisierung des JWT-Validators fehlgeschlagen");
    rocket::build()
        .manage(validator)
        .mount("/", routes![protected_handler])
}
Schritt 5: Deine Implementierung testen
Zugangstokens erhalten
Von deiner Client-Anwendung: Wenn du eine Client-Integration eingerichtet hast, kann deine App Tokens automatisch erhalten. Extrahiere das Zugangstoken und verwende es in API-Anfragen.
Zum Testen mit curl / Postman:
- 
Benutzertokens: Verwende die Entwicklertools deiner Client-App, um das Zugangstoken aus dem localStorage oder dem Netzwerk-Tab zu kopieren.
 - 
Maschine-zu-Maschine-Tokens: Verwende den Client-Credentials-Flow. Hier ein nicht-normatives Beispiel mit curl:
curl -X POST https://your-tenant.logto.app/oidc/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=your-m2m-client-id" \
-d "client_secret=your-m2m-client-secret" \
-d "resource=https://your-api-resource-indicator" \
-d "scope=api:read api:write"Möglicherweise musst du die Parameter
resourceundscopeentsprechend deiner API-Ressource und Berechtigungen anpassen; einorganization_id-Parameter kann ebenfalls erforderlich sein, wenn deine API organisationsgebunden ist. 
Möchtest du den Inhalt des Tokens inspizieren? Verwende unseren JWT Decoder, um deine JWTs zu dekodieren und zu überprüfen.
Geschützte Endpunkte testen
Gültige Token-Anfrage
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
  http://localhost:3000/api/protected
Erwartete Antwort:
{
  "auth": {
    "sub": "user123",
    "clientId": "app456",
    "organizationId": "org789",
    "scopes": ["api:read", "api:write"],
    "audience": ["https://your-api-resource-indicator"]
  }
}
Fehlendes Token
curl http://localhost:3000/api/protected
Erwartete Antwort (401):
{
  "error": "Authorization header is missing"
}
Ungültiges Token
curl -H "Authorization: Bearer invalid-token" \
  http://localhost:3000/api/protected
Erwartete Antwort (401):
{
  "error": "Invalid token"
}
Berechtigungsmodell-spezifisches Testen
- Globale API-Ressourcen
 - Organisations-(Nicht-API)-Berechtigungen
 - Organisationsgebundene API-Ressourcen
 
Testszenarien für APIs, die mit globalen Berechtigungen geschützt sind:
- Gültige Berechtigungen: Teste mit Tokens, die deine erforderlichen API-Berechtigungen enthalten (z. B. 
api:read,api:write) - Fehlende Berechtigungen: Erwarte 403 Verboten, wenn das Token die erforderlichen Berechtigungen nicht enthält
 - Falsche Zielgruppe: Erwarte 403 Verboten, wenn die Zielgruppe nicht mit der API-Ressource übereinstimmt
 
# Token mit fehlenden Berechtigungen - erwarte 403
curl -H "Authorization: Bearer token-without-required-scopes" \
  http://localhost:3000/api/protected
Testszenarien für organisationsspezifische Zugangskontrolle:
- Gültiges Organisationstoken: Teste mit Tokens, die den korrekten Organisationskontext enthalten (Organisations-ID und Berechtigungen)
 - Fehlende Berechtigungen: Erwarte 403 Verboten, wenn der Benutzer keine Berechtigungen für die angeforderte Aktion hat
 - Falsche Organisation: Erwarte 403 Verboten, wenn die Zielgruppe nicht mit dem Organisationskontext übereinstimmt (
urn:logto:organization:<organization_id>) 
# Token für falsche Organisation - erwarte 403
curl -H "Authorization: Bearer token-for-different-organization" \
  http://localhost:3000/api/protected
Testszenarien, die API-Ressourcen-Validierung mit Organisationskontext kombinieren:
- Gültige Organisation + API-Berechtigungen: Teste mit Tokens, die sowohl den Organisationskontext als auch die erforderlichen API-Berechtigungen enthalten
 - Fehlende API-Berechtigungen: Erwarte 403 Verboten, wenn das Organisationstoken die erforderlichen API-Berechtigungen nicht enthält
 - Falsche Organisation: Erwarte 403 Verboten, wenn auf die API mit einem Token aus einer anderen Organisation zugegriffen wird
 - Falsche Zielgruppe: Erwarte 403 Verboten, wenn die Zielgruppe nicht mit der organisationsgebundenen API-Ressource übereinstimmt
 
# Organisationstoken ohne API-Berechtigungen - erwarte 403
curl -H "Authorization: Bearer organization-token-without-api-scopes" \
  http://localhost:3000/api/protected
Verwandte Ressourcen
Token-Ansprüche anpassen JSON Web Token (JWT)OpenID Connect Discovery
RFC 8707: Ressourcenindikatoren