n8n authentifiziert sich gegen SAP BTP — OAuth gegen einen CAP-Service
Artikel 13 · Serie: Einstieg in n8n
Artikel 10 hat die SAP-Anbindung über einen einzelnen APIKey-Header gezeigt und den echten Token-Flow ausdrücklich vertagt. Diesen Schritt holt das Reihen-Finale nach: n8n authentifiziert sich mit echtem OAuth gegen einen Service auf SAP BTP, mit dem Client-Credentials-Flow gegen den xsuaa-Token-Endpunkt. Die Auth-Mechanik ist real und 1:1 übertragbar auf ein produktives S/4-OData-Ziel, nur URL und Pfade unterscheiden sich.
Eines noch vorweg, weil es leicht falsch ankommt: Ich binde hier kein produktives S/4 an, sondern einen eigenen kleinen Service auf der BTP. Real ist die Auth- und Deployment-Schicht: xsuaa, Client Credentials, JWT, Service Key, Cloud-Foundry-Deploy. Das Zielsystem ist ein Stellvertreter. On-Prem-Szenarien, Cloud Connector und Principal Propagation bleiben außen vor.
Der Code zu diesem Artikel liegt auf Codeberg, Tag v1.0: codeberg.org/rotecodefraktion/n8n-einstieg.
Cloud-Auth läuft über OAuth, nicht über Header-Keys
In SAP-Cloud-Welten ist OAuth der Standard-Auth-Pfad. Statt eines statischen Schlüssels tauscht der Aufrufer eine Client-Identität gegen ein kurzlebiges Token und ruft damit den Service. n8n holt das Token beim xsuaa-Token-Endpunkt und legt es als Bearer an den eigentlichen Request.
Von den OAuth-Flows nutze ich Client Credentials, weil n8n als Backend-Service spricht: kein Benutzer im Spiel, kein interaktiver Login. n8n tauscht Client ID und Secret gegen ein Token. Der Authorization-Code-Flow wäre für Browser- und User-Kontexte gedacht, SAML Bearer für On-Prem-Principal-Propagation. Beide gehören nicht in dieses Szenario.
Der CAP-Service als Stellvertreter für S/4
Als Zielsystem habe ich bewusst keinen FastAPI-Service genommen, sondern einen mit dem SAP Cloud Application Programming Model gebaut. CAP ist das native Entwicklungsmodell für Business-Services auf der BTP: Man beschreibt einen Service in CDS, exponiert ihn als OData und sichert ihn über xsuaa. Genau die drei Eigenschaften, die auch ein produktiver S/4-OData-Endpunkt mitbringt.
Der Service macht eine Sache: Er erzeugt auf Anfrage eine zufällige, 10-stellige Fall-ID. In CDS ist das ein Service mit einer unbound Action:
service CaseService @(requires: 'authenticated-user') {
type CaseResult { caseId : String(10); }
action generateCaseId() returns CaseResult;
}
Die Annotation @(requires: 'authenticated-user') ist der Riegel: Ohne gültiges Token kommt kein Aufruf durch. Die Logik dahinter sind drei Zeilen JavaScript, die eine Zufalls-ID erzeugen und zurückgeben. Lokal läuft der Service mit gemockter Auth, auf der BTP mit echtem xsuaa, gesteuert allein über die cds.requires.auth-Konfiguration.
BTP-Trial als Spielwiese
Für den echten Lauf reicht ein kostenloser BTP-Trial mit aktiviertem Cloud Foundry. Das Konzept ist immer dasselbe: ein Subaccount, darin ein Space, darin eine xsuaa-Instanz, an die der deployte Service gebunden wird. Aus dieser Bindung entsteht der Service Key, der die Credentials für n8n liefert.

Die konkreten Klick-Pfade in der BTP-Konsole ändern sich alle paar Monate. Deshalb erkläre ich hier das Konzept und verweise für die genauen Schritte auf docs/sap-auth-deployment.md im Repo. Der Deploy selbst ist knapp: xsuaa-Instanz anlegen, cds build, cf push. Eine Eigenheit des Trials, die man kennen sollte: Apps und der DevSpace werden nach Inaktivität automatisch gestoppt, Nachteulen, die am nächsten Morgen weiterarbeiten wollen, müssen die Services evtl. wieder neu starten. Ein cf app case-service zeigt den Status, ein cf start weckt sie wieder. Wer einen 404 vom Cloud-Foundry-Router bekommt, prüft als Erstes, ob die App überhaupt läuft.
xsuaa-Binding und Service Key
Ein xsuaa-Binding liefert einen Service Key mit mehreren Feldern. Drei davon gehören ins n8n-Credential:

urlist die Basis für den Token-Endpunkt (<url>/oauth/token) und den Schlüssel-Endpunkt (<url>/token_keys).clientidist die Client-Identität.clientsecretist das Geheimnis, das ausschließlich ins n8n-Credential gehört und nie ins Repository. Die.gitignoreschließt*-service-key.jsonund.envexplizit aus. Das ist der häufigste BTP-Anfänger-Fehler, und er ist mit einer Zeile vermeidbar.
Alles andere im Service Key, etwa verificationkey oder xsappname, ist Metadaten und gehört nicht ins Credential. Den verificationkey braucht man nicht, wenn der Service die Signatur dynamisch über den JWKS-Endpunkt prüft, was der CAP-Service tut.
Die OAuth2-Credential entsteht am HTTP-Node
Bisher habe ich meine Credentials separat angelegt, beim generischen OAuth ist das anders. Die generische OAuth2-Anmeldung findet sich nicht als eigener Eintrag in der Credentials-Liste, sondern wird aus dem HTTP-Request-Node heraus angelegt: Authentication auf Generic Credential Type, dann Generic Auth Type auf OAuth2, dann Create New Credential. Wer in der Credentials-Übersicht nach „OAuth2 API" sucht, sucht vergeblich.
Für Client Credentials genügen vier Felder:
Grant Type: Client Credentials
Access Token URL: <url>/oauth/token
Client ID: <clientid>
Client Secret: <clientsecret>
Scope: (leer)
Authentication: Header
Den Scope lasse ich leer, weil der Service nur einen authentifizierten Aufrufer verlangt, keinen bestimmten Scope. Bei „Client Credentials" zeigt n8n übrigens keinen Token-Test beim Speichern an, anders als beim Authorization-Code-Flow mit seinem Connect-Button. Das Token wird erst beim ersten echten Aufruf geholt. Kein sichtbares Ergebnis beim Speichern ist also normal, kein Fehler.
Der Workflow holt das Token und die Fall-ID
Der Workflow v1.0-sap-oauth-lookup macht drei Schritte. Ein Webhook nimmt das klassifizierte Ticket an. Ein HTTP-Request-Node ruft mit der OAuth2-Credential die CAP-Action auf, ein POST auf /odata/v4/case/generateCaseId. Ein Code-Node hängt die zurückgegebene caseId ans Ticket und gibt es weiter.

Das Token-Caching erledigt n8n selbst: Es speichert das Bearer-Token für seine Lifetime und holt automatisch ein neues, wenn es abläuft. Der Token-Tausch passiert also nicht bei jedem Aufruf. Im Testlauf kam das angereicherte Ticket sauber zurück:
{"id":"TKT-OAUTH-1","subject":"Test OAuth gegen BTP",
"category":"sap-basis","caseId":"5195126423","caseIdSource":"btp-cap"}
Das ist der Beweis der ganzen Stufe: n8n hat sich gegen echtes xsuaa authentifiziert, den CAP-Service auf BTP aufgerufen und das Ergebnis verarbeitet.
Lokal ohne BTP: CAP mit gemockter Auth
Der CAP-Service läuft lokal mit cds serve und gemockter Auth, ohne jeden BTP-Zugang. Das eignet sich für die Entwicklung der Service-Logik: ein anonymer Aufruf prallt mit 401 ab, ein authentifizierter Mock-User bekommt seine Fall-ID. Was der lokale Modus nicht abbildet, ist der echte OAuth-Flow, denn dafür braucht es einen echten xsuaa-Token-Endpunkt. Die Auth-Mechanik beweist man gegen den Trial, die Service-Logik lokal.
Snapshot mit Datum
Diese Anleitung ist ein Snapshot mit Datum. BTP ist ein Cloud-Service ohne Versionsnummer, Konsole und Klick-Pfade verändern sich. Die Versionsstände zum Schreibzeitpunkt stehen in docs/sap-auth-snapshot.md im Repo. Läuft der Trial ab, bleibt die Reihe gültig: Ein frischer Trial und die Schritte aus der Deployment-Doku stellen den Live-Beweis wieder her.
Wer weiterginge, käme als Nächstes zu Principal Propagation für On-Prem-S/4 und zum SAML-Bearer-Flow für User-Kontext-Szenarien. Beides ist eine eigene Geschichte und liegt außerhalb dieser Reihe.
Die ganze Pipeline in einem Workflow
Über die Reihe hinweg ist die Pipeline modular gewachsen, viele Stufen liegen als eigene Subworkflows vor. Für diese eine Übersicht habe ich sie in einen einzigen Workflow aufgelöst und jeden Subworkflow-Aufruf durch seine Schritte ersetzt. Das Gesamtbild zeigt die ganze Strecke von links nach rechts: Der Webhook nimmt das Ticket an, eine Prüfung fängt leere Payloads ab, dann verteilt ein Round-Robin auf zwei AI-Backends mit Failover, Anthropic zuerst, OpenAI als Reserve, und dem regelbasierten Klassifikator als letztem Auffangnetz. Danach folgen die Post-Classifier-Pipeline mit Spracherkennung und Routing, die Anreicherung und die SAP-Brücke mit dem Business-Partner-Lookup, bis zur Antwort.

Der OAuth-Schritt aus diesem Artikel kommt als weiterer Lookup hinzu, oben steht er als eigener Workflow. So fügt sich das Finale in eine Strecke ein, die vom eingehenden Ticket bis zur SAP-gestützten Anreicherung reicht.
Reihen-Abschluss: dreizehn Artikel
Damit schließt sich der Bogen. In dreizehn Artikeln ist aus einem ersten Container eine produktionsnahe Pipeline geworden: Self-Hosting mit Docker, Postgres und Caddy, ein synthetischer Testdatensatz, regelbasierte und AI-gestützte Klassifikation über zwei Backends mit Round-Robin und Failover, Error Handling und Observability, die SAP-Brücke über OData, der Queue-Mode für den Produktivbetrieb, die Einordnung gegenüber Cloud Integration und Edge Integration Cell aus Artikel 12, und zum Schluss die OAuth-Anbindung gegen BTP. Der Code liegt offen auf Codeberg, ein Tag pro Artikel, das Finale unter v1.0.
Der CAP-Service in diesem Artikel war nur ein Stellvertreter, aber er hat einen eigenen Aufgabenraum aufgemacht. CAP als natives SAP-Entwicklungsmodell verdient eine eigene Reihe, „Einstieg in CAP", in der der Service nicht Mittel zum Zweck ist, sondern das Thema: Datenmodell in CDS, OData-Services, Business-Logik, Deployment auf BTP. Das ist kein Versprechen mit Datum, sondern der naheliegende nächste Schritt. Bis dahin steht hier eine Pipeline, die man nachbauen, verstehen und an die eigene SAP-Landschaft anpassen kann.