Agent Permissions mit OAuth und Entra ID: On-Behalf-Of

Agent Permissions mit Entra ID On-Behalf-Of-Flow: User-Identity durch die Tool-Use-Kette für sauberen Audit-Trail. Code-Beispiele, MCP-Integration, FAQs.

27 Juli. 2026 | Compliance

Inhaltsverzeichnis

Pexon Consulting | 11 Min. Lesezeit | 10.06.2026


TL;DR

Agent Permissions Entra ID ist die Architektur-Entscheidung, die 2026 zwischen „Production-tauglich“ und „Audit-Albtraum“ entscheidet: Statt mit einem Service-Account-Token gegen alle Backend-Systeme zu sprechen, läuft der Agent mit der Identity des aufrufenden Users über OAuth On-Behalf-Of-Flow. Pexon Consulting baut diese Architektur als Standard-Komponente in jedem produktiven Multi-Backend-Agent-Setup — mit Entra ID, Keycloak oder einem anderen IdP, sauberem Audit-Trail und feingranularer Permissions-Durchsetzung. Claude Managed Agents ergänzt diesen Identity-Layer auf Anwendungsebene mit einem eigenen Permission Policies für Enterprise-Agents-Modell aus always_allow und always_ask.

Aspekt Service-Account-Pattern On-Behalf-Of-Pattern
Identity im Backend „Agent-Service“ für alle Anfragen User-Email aus Token
Audit-Trail nur Agent-Service sichtbar echter User nachvollziehbar
Permissions-Durchsetzung im Agent-Code im Backend nativ
Compliance bei Audit schwach stark
Komplexität Setup niedrig mittel

Warum Service-Accounts in Production nicht reichen

Wir sehen das aktuell wöchentlich: Ein Team hat den ersten Agent gebaut, der mit dem CRM und ERP spricht. Der Agent hat einen Service-Account-Token, mit dem er gegen die Backend-APIs spricht. Funktioniert für den PoC. Drei Monate später kommt der Datenschutz-Audit und stellt fest: Im CRM-Audit-Log steht bei jedem Tool-Call der User „Agent-Service“ — niemand weiß, welcher echte Mitarbeiter die Anfrage gestellt hat.

Das ist nicht akademisch. DSGVO verlangt nachweisbar, wer wann welche personenbezogenen Daten verarbeitet hat. BaFin- und KRITIS-Vorgaben verlangen User-bezogene Audit-Trails. Selbst ohne regulatorischen Druck gibt es klare operative Probleme: Wenn ein Agent versehentlich falsche Daten ändert, kannst du nicht nachvollziehen, in welchem User-Kontext das passiert ist. Bei einem Pilotkunden hatten wir genau diesen Fall — ein Agent hat einen Kundenstamm-Eintrag falsch aktualisiert, im Audit-Log stand nur „Agent-Service“, die Recherche dauerte zwei Tage.

Die saubere Antwort heißt OAuth On-Behalf-Of-Flow: Der User authentifiziert sich beim Agent, der Agent tauscht den User-Token gegen einen Token für das Backend, der Backend sieht den echten User in seinen Logs. Identity wandert durch die Kette mit.


Wie der OBO-Flow konkret aussieht

Architektur in fünf Schritten.

Schritt 1: User-Login. User loggt sich in der Chatbot-App ein, typisch über OIDC-Flow gegen Entra ID. Bekommt ein ID-Token plus Access-Token für die Chatbot-App (Audience: api://chatbot-app).

Schritt 2: User schickt Anfrage an Chatbot. Mit dem Access-Token im Authorization-Header. Die Chatbot-App validiert das Token gegen das JWKS-Endpoint des IdP.

Schritt 3: Agent will gegen das CRM-Backend sprechen. Statt einen eigenen Service-Token zu verwenden, ruft der Agent das Entra-Token-Endpoint mit dem User-Token und der Grant-Type on_behalf_of auf. Bekommt einen neuen Token mit Audience api://crm-api plus User-Identity-Claims.

Schritt 4: Agent ruft das CRM mit dem neuen Token. Das CRM validiert das Token, sieht den User in den Claims, prüft User-spezifische Permissions, gibt die Antwort zurück.

Schritt 5: Audit-Trail überall korrekt. Chatbot-App-Log zeigt User. CRM-Log zeigt User. Bei einem Vorfall ist nachvollziehbar, in welchem User-Kontext was passiert ist.

import msal
import httpx

confidential_client = msal.ConfidentialClientApplication(
    client_id="CHATBOT_APP_ID",
    client_credential="CHATBOT_APP_SECRET",
    authority=f"https://login.microsoftonline.com/{TENANT_ID}",
)

def call_crm_obo(user_token: str, crm_endpoint: str, payload: dict) -> dict:
    """Ruft das CRM On-Behalf-Of des Users."""
    obo_response = confidential_client.acquire_token_on_behalf_of(
        user_assertion=user_token,
        scopes=["api://crm-api/.default"],
    )
    
    if "access_token" not in obo_response:
        raise Exception(f"OBO-Flow fehlgeschlagen: {obo_response.get('error')}")
    
    crm_token = obo_response["access_token"]
    
    response = httpx.post(
        crm_endpoint,
        headers={"Authorization": f"Bearer {crm_token}"},
        json=payload,
    )
    return response.json()

App Roles und Delegated Permissions

In Entra ID gibt es zwei Permissions-Konzepte für dieses Setup.

Delegated Permissions sind User-getragene Berechtigungen. Der User loggt sich ein, gibt der Chatbot-App das Recht, in seinem Namen das CRM zu lesen. Bei jedem Tool-Call wird die Permission im Token mitgeschickt — scp: "Customer.Read Order.Read". Das CRM-Backend prüft die Permissions im Token und entscheidet, was der Agent für diesen User tun darf.

App Roles sind tieftragene Rollen, die in Entra ID definiert sind. Beispiel: „CustomerSupportAgent“ mit den Rollen Customer.Read und Order.Read. User werden den Rollen zugeordnet (per Group-Membership oder direkt). Im Token landen die Rollen als roles: ["CustomerSupportAgent"]. Das Backend prüft Rollen statt einzelne Scopes.

Pexon-Default: Delegated Permissions für die Tool-Use-Berechtigungen (was darf der Agent für den User tun), App Roles für die Funktions-Berechtigung (welche Tools darf der Agent dem User überhaupt anbieten). Damit ist klar getrennt: User-Berechtigung am Backend vs Feature-Verfügbarkeit am Frontend.


Pattern in einer MCP-Server-Architektur

Für MCP-Server bauen wir das so:

from fastmcp import FastMCP, Context
from fastmcp.auth import BearerTokenAuth
from fastmcp.exceptions import ToolError
import msal

auth = BearerTokenAuth(
    jwks_url=f"https://login.microsoftonline.com/{TENANT_ID}/discovery/v2.0/keys",
    issuer=f"https://login.microsoftonline.com/{TENANT_ID}/v2.0",
    audience="api://mcp-customer-server",
)

mcp = FastMCP("customer-server", auth=auth)

obo_client = msal.ConfidentialClientApplication(...)

@mcp.tool
def get_customer_details(customer_id: str, ctx: Context) -> dict:
    """Holt Kundendetails On-Behalf-Of des aufrufenden Users."""
    user_token = ctx.auth.token
    user_email = ctx.auth.claims.get("email")
    
    obo_response = obo_client.acquire_token_on_behalf_of(
        user_assertion=user_token,
        scopes=["api://crm-api/Customer.Read"],
    )
    
    if "access_token" not in obo_response:
        raise ToolError(f"User {user_email} hat keine Lese-Berechtigung im CRM.")
    
    crm_token = obo_response["access_token"]
    
    response = httpx.get(
        f"{CRM_URL}/customers/{customer_id}",
        headers={"Authorization": f"Bearer {crm_token}"},
    )
    return response.json()

Mehr zur FastMCP-Auth-Integration im FastMCP-Advanced-Blog.


Häufig gestellte Fragen

Funktioniert OBO auch außerhalb von Entra ID?

Ja. Keycloak unterstützt seit Version 22 den Token-Exchange-Flow nach RFC 8693, der funktional OBO entspricht. Okta hat ähnliche Patterns über Custom Authorization Server. Für offene OAuth2-IdPs ist der Token-Exchange-Standard die portierbare Antwort.

Was, wenn das Backend OBO nicht versteht?

Häufiger Fall bei Legacy-Systemen. Wir bauen dann ein Token-Translation-Pattern: Der Agent macht den OBO-Flow gegen einen Pexon-Token-Service, der den User-Token gegen einen Backend-spezifischen Format-Token tauscht (Basic-Auth-Credentials, API-Key mit User-Tagging, etc.). Audit-Trail wird im Token-Service plus im Backend separat geführt.

Wie behandle ich Token-Refresh in lange laufenden Agent-Sessions?

Bei mehrstufigen Agent-Workflows können Token-Lebenszyklen kritisch werden. Wir bauen typisch einen Token-Manager, der Token-Expiry vorhersieht und proaktiv refresht. Bei mehrstufigen Agent-Loops mit MCP-Calls über mehrere Minuten ist das Pflicht.

Was, wenn der User eine Permission nicht hat, die der Agent braucht?

Klare Fehlermeldung an den User. Im Tool-Handler werfen wir einen ToolError mit User-freundlicher Beschreibung. Der Agent gibt das dem User zurück: „Ich kann diese Aktion nicht durchführen, weil dir die Berechtigung X fehlt. Wende dich an deinen Vorgesetzten oder den IT-Help-Desk.“ Damit ist die Permission-Lage transparent statt im Hintergrund maskiert.

Wie viele Tokens sind in einem typischen Agent-Run im Spiel?

Bei einem Multi-Tool-Agent typisch 2-5 verschiedene Backend-Token im Spiel — CRM, ERP, Wissensbasis, ggf. externe APIs. Jeder Tool-Call macht einen eigenen OBO-Flow. Performance-Impact: typisch 50-200ms pro OBO-Aufruf, was bei Multi-Tool-Conversations spürbar wird. Wir cachen OBO-Tokens pro User und Audience für 10-30 Minuten, was den Overhead auf ein vernachlässigbares Niveau drückt.


Nächster Schritt

Wenn dein Agent-Setup heute mit Service-Accounts läuft und der nächste Audit naht: Wir machen einen 60-Minuten-Architektur-Call mit dir und idealerweise dem CISO oder IT-Sicherheits-Beauftragten. Du beschreibst die Backend-Landschaft und das IdP, wir skizzieren den OBO-Setup mit Festpreis-Sprint.


Verwandte Themen


Microsoft Fabric. Bilder Blogtemplate Whitepaper

Whitepaper Zero Trust K8s

  • Wie Sie mit Istio die NIS-2 Haftungsrisiken technisch eliminieren

Beratungsgespräch

Sichern Sie sich Ihre kostenfreie Erstberatung

Analyse Ihres individuellen Cloud- oder KI-Bedarfs
Erste Empfehlungen zu Umsetzungsstrategien
Transparente Einblicke in unsere Methoden, Technologien & Referenzen