Documentation

n8n-nodes-oidc-login

Community-Node-Package für einen OIDC-Login-Flow, der von einem eingehenden Webhook/Web-Formular
ausgelöst wird
und dessen Access Token danach in nachfolgenden HTTP-Request-Nodes per eigenem
Credential-Typ genutzt werden kann.

Enthaltene Bausteine

Datei Zweck
credentials/OidcProviderApi.credentials.ts Statische Provider-Settings (Client ID/Secret, Authorization-/Token-URL). Wird vom Trigger-Node gelesen.
credentials/OidcTokenEncryptionKey.credentials.ts Gemeinsames Geheimnis zum symmetrischen Ver-/Entschlüsseln des Access Tokens. Wird vom Trigger-Node (verschlüsseln) gelesen.
nodes/OidcLoginTrigger/OidcLoginTrigger.node.ts Webhook-Trigger mit zwei Endpunkten: Login-Start (Redirect zum OIDC-Server bzw. Cookie-basiertes Auto-Login/Refresh) und Callback (Code-Exchange). Gibt Access/Refresh Token nur verschlüsselt aus, id_token bleibt Klartext.
credentials/OidcBearerApi.credentials.ts Credential mit Feldern Encrypted Access Token (Expression) und Decryption Key, wählbar in jedem HTTP Request Node. Entschlüsselt den Token erst innerhalb von authenticate() — der Klartext taucht nie als Item-Daten auf.
shared/tokenCrypto.ts AES-256-GCM Ver-/Entschlüsselung, von Trigger und OidcBearerApi gemeinsam genutzt.
shared/cookies.ts Cookie-Parsing/-Serialisierung (Set-Cookie-Header bauen, Cookie-Header lesen), vom Trigger genutzt.

Antwortverhalten (Response Mode)

Das Node-Feld "Response Mode" gilt für den Start-Webhook: sowohl für den Auto-Login/Refresh-
Kurzschluss (gültiger Access-/Refresh-Token-Cookie) als auch für den Request, auf den der Callback nach
erfolgreichem Login redirected (siehe "Ablauf", Schritt 4) — der Callback selbst antwortet nie mehr
direkt, siehe unten.

  • "Nach letztem Node" (Default): n8n lässt die Verbindung offen, bis der komplette Workflow
    durchgelaufen ist, und schickt dann automatisch die Ausgabe der letzten Node im Workflow als
    JSON-Antwort zurück. Baut man z. B. einen HTTP-Request-Node (Repos abrufen) + Set-Node
    (Formatierung) dahinter, landet exakt deren Output als JSON-Antwort beim aufrufenden Client.
  • "Sofort": n8n bestätigt jeden eingehenden Request sofort mit einer generischen Antwort, ohne auf
    das Ergebnis des Workflows zu warten. Der Workflow läuft im Hintergrund trotzdem vollständig durch.

Unabhängig vom gewählten Response Mode antworten folgende Fälle immer sofort und manuell (der Workflow
läuft dort gar nicht bzw. noch nicht weiter):

  • der reine Redirect zum OIDC-Server (HTTP 302, kein gültiger Token-Cookie vorhanden),
  • der Redirect (HTTP 302) vom Callback zurück auf den Start-Webhook nach erfolgreichem Token-Austausch,
  • Fehlerfälle beim Callback (ungültiger/abgelaufener state, Token-Austausch fehlgeschlagen — HTTP 400/502).

Warum drei getrennte Credential-Typen?

  • OidcProviderApi ist statisch (Client ID/Secret ändern sich nicht) → normale, fest gespeicherte Credential.
  • OidcTokenEncryptionKey und OidcBearerApi enthalten denselben Geheimwert (Ver- bzw. Entschlüsselung),
    sind aber bewusst zwei getrennte Credential-Typen, keine kombinierte Entity: n8n löst beim Laden
    einer Credential IMMER alle Felder auf, auch ungenutzte. OidcBearerApi enthält ein Expression-Feld
    (Encrypted Access Token, z. B. ={{ $('OIDC Login Trigger').item.json.encryptedAccessToken }}),
    das sich nur aus dem Ausführungskontext eines nachgelagerten Nodes auflösen lässt. Würde der Trigger
    dieselbe Credential referenzieren, versuchte n8n dieses Feld auch im Trigger selbst aufzulösen —
    zu diesem Zeitpunkt hat der Trigger (der ja gerade erst läuft) noch keine eigenen Ausführungsdaten,
    wodurch die Auflösung mit Referenced node doesn't exist fehlschlägt. Der Trigger nutzt daher die
    separate, expressionsfreie OidcTokenEncryptionKey-Credential.
  • OidcBearerApi ist dynamisch pro Endnutzer/Ausführung — n8n löst Expressions in Credential-Feldern
    innerhalb des Kontexts der jeweiligen Workflow-Ausführung auf. Damit bekommt jeder Website-Besucher
    in seiner eigenen Ausführung automatisch seinen eigenen, frisch erhaltenen Token — ohne dass Tokens
    zwischen Nutzern/Ausführungen vermischt werden.
  • Damit ihr den Geheimwert trotzdem nicht doppelt pflegen müsst: Beide Credentials (Encryption Key bei
    OidcTokenEncryptionKey, Decryption Key bei OidcBearerApi) auf dieselbe Expression setzen, z. B.
    ={{$env.OIDC_TOKEN_KEY}}.

Ablauf

  1. Endnutzer ruft https://<n8n>/webhook/oidc-login auf (z. B. Link/Redirect aus einem Formular).

  2. OidcLoginTrigger prüft zuerst zwei verschlüsselte Cookies aus einem vorherigen Login:

    • Gültiger oidc_access_token-Cookie vorhanden → kompletter OIDC-Roundtrip wird übersprungen,
      der Workflow läuft sofort mit diesem Token weiter (Schritt 5).
    • Kein gültiger Access-Token-Cookie, aber ein gültiger oidc_refresh_token-Cookie → es wird
      versucht, per grant_type=refresh_token einen neuen Access Token zu holen. Klappt das, laufen
      beide Cookies neu gesetzt weiter (Schritt 4/5); schlägt der Refresh fehl (z. B. widerrufen/abgelaufen),
      fällt der Trigger automatisch auf den normalen Login-Redirect zurück.
    • Keines von beidem → OidcLoginTrigger generiert state + PKCE code_verifier/code_challenge,
      merkt sich beides verschlüsselt in einem kurzlebigen <nodeId>_oidc_pending_login-Cookie (10 Min. TTL) und
      leitet (HTTP 302) zur authorizationUrl des OIDC-Servers weiter.

    Alle drei Cookie-Namen sind mit der Node-ID prefixt (<nodeId>_oidc_pending_login,
    <nodeId>_oidc_access_token, <nodeId>_oidc_refresh_token), damit mehrere OidcLoginTrigger-Instanzen
    auf demselben n8n-Host sich nicht gegenseitig die Cookies überschreiben oder auslesen.

  3. Nutzer meldet sich beim Auth-Server an. Dieser leitet zurück auf
    https://<n8n>/webhook/oidc-login/callback?code=...&state=....

  4. OidcLoginTrigger liest das oidc_pending_login-Cookie, verifiziert state, tauscht code gegen
    Access Token (POST an tokenUrl, inkl. code_verifier) und setzt (wie beim Refresh-Fall) zwei neue
    Cookies:

    • oidc_access_token, Lifetime = expires_in des Providers.
    • oidc_refresh_token (falls vorhanden), Lifetime = konfigurierbares Feld "Refresh Token Cookie
      Lifetime (Days)" am Node (Default 30 Tage).

    Beide Cookie-Inhalte sind AES-256-GCM-verschlüsselt mit dem Encryption Key aus der
    OidcTokenEncryptionKey-Credential.

    Statt direkt zu antworten, redirected OidcLoginTrigger anschließend (HTTP 302) auf die
    Login-Start-URL
    (.../oidc-login, ohne Query-String) — der frisch gesetzte
    oidc_access_token-Cookie greift dort sofort über den Kurzschluss-Pfad aus Schritt 2 ("gültiger
    Access-Token-Cookie vorhanden"). So verschwinden code/state aus der sichtbaren Adresszeile, ohne
    dass sie dafür zusätzlich zwischengespeichert werden müssen. Trade-off: Der Workflow-Output eines
    frischen Logins hat dadurch dieselbe schlankere Form wie bei einem wiederkehrenden Besucher (siehe
    Schritt 5) — id_token/expires_in/etc. aus der Token-Response werden nicht mehr an den Workflow
    weitergereicht.

  5. Der Workflow-Output enthält nur encryptedAccessToken (verschlüsselt) — bzw. bei einem erfolgreichen
    Refresh (Schritt 2, zweiter Fall) zusätzlich encryptedRefreshToken sowie id_token/expires_in/…
    im Klartext, da dieser Zweig direkt antwortet statt zu redirecten. Nachfolgende Nodes greifen auf den
    Token via $json.encryptedAccessToken zu; den Klartext liefert erst das OidcBearerApi-Credential,
    und zwar nur innerhalb des jeweiligen HTTP-Requests selbst.

Einrichtung mit Gitea als OIDC-Server

Gitea selbst als Auth-Server nutzt feste, nicht per Discovery konfigurierbare Endpunkte:

  • Authorization: https://<gitea>/login/oauth/authorize
  • Token: https://<gitea>/login/oauth/access_token
  • Userinfo (optional, für Profildaten): https://<gitea>/login/oauth/userinfo

Schritte in Gitea:

  1. Als der Nutzer, unter dem die App laufen soll (oder als Admin unter Site Administration →
    Applications
    für eine instanzweite OAuth2-App): Settings → Applications → "Create a new OAuth2 Application".
  2. Application Name vergeben, als Redirect URI exakt die Callback-URL des OidcLoginTrigger
    eintragen (z. B. https://<n8n>/webhook/oidc-login/callback) — muss zeichengenau übereinstimmen,
    sonst meldet Gitea redirect_uri_mismatch.
  3. Gitea zeigt Client ID und Client Secret an (Secret nur einmal sichtbar!) → in die
    OIDC Provider-Credential in n8n eintragen.
  4. Scope: openid profile email funktioniert bei Gitea für den OIDC-Login. Für Zugriff auf die
    Gitea-API selbst (z. B. Repos lesen) ggf. zusätzlich Gitea-spezifische Scopes wie read:repository
    ergänzen (Feld "Scope" im OidcLoginTrigger-Node), siehe Gitea-Doku zu "granular scopes".
  5. Optional: ein nachgeschalteter HTTP-Request-Node auf https://<gitea>/login/oauth/userinfo (GET,
    Credential = OIDC Bearer Token) liefert die Profildaten des eingeloggten Gitea-Nutzers — guter Test,
    ob der Flow funktioniert.

Einrichtung (allgemein)

npm install
npm run build
npm link
# im n8n Custom-Extensions-Ordner:
npm link n8n-nodes-oidc-login

n8n neu starten. Danach:

  1. Credential "OIDC Provider" anlegen: Authorization URL, Token URL, Client ID, (optional) Client Secret
    eures OIDC-Servers eintragen.
  2. Credential "OIDC Token Encryption Key" anlegen: ein zufälliges, langes Geheimnis eintragen —
    idealerweise als Expression ={{$env.OIDC_TOKEN_KEY}}, damit dieselbe Umgebungsvariable später auch
    im OidcBearerApi-Credential referenziert werden kann (siehe Schritt 5).
  3. Node "OIDC Login Trigger" in den Workflow ziehen, beide Credentials auswählen, Path setzen
    (z. B. oidc-login). n8n zeigt dir automatisch zwei Webhook-URLs an (Start + .../callback).
  4. Beim OIDC-Provider den Callback-Webhook (.../oidc-login/callback) als Redirect URI des Clients
    registrieren.
  5. Credential "OIDC Bearer Token" anlegen:
    • "Encrypted Access Token" auf Expression umschalten und ={{ $json.encryptedAccessToken }}
      eintragen (oder ={{ $('OIDC Login Trigger').item.json.encryptedAccessToken }}, falls dazwischen
      weitere Nodes liegen).
    • "Decryption Key" auf denselben Wert wie das OIDC Token Encryption Key-Credential setzen, also
      ebenfalls ={{$env.OIDC_TOKEN_KEY}}.
  6. Nachfolgenden HTTP Request Node hinzufügen → Authentication → "Predefined Credential Type" →
    "OIDC Bearer Token" auswählen. Der Authorization: Bearer <token>-Header wird automatisch gesetzt —
    der entschlüsselte Token existiert nur kurzzeitig innerhalb dieses Requests, nie als sichtbare
    Item-Daten.

Wichtige Einschränkungen / Hinweise

  • Ein frisch ausgehandelter Token gilt nur für die Ausführung, in der er erzeugt wurde (jeder
    Endnutzer meldet sich selbst an, keine Vermischung zwischen Nutzern). Wiederkehrende Besuche
    innerhalb der Cookie-Lifetime lösen aber keinen neuen vollständigen Login-Roundtrip aus, sondern
    nutzen Access- bzw. Refresh-Token-Cookie (siehe "Ablauf").
  • State-/PKCE-Zwischenspeicherung erfolgt über ein kurzlebiges, verschlüsseltes Cookie
    (<nodeId>_oidc_pending_login, 10 Min. TTL) statt Workflow-Static-Data — kein serverseitiger
    Cleanup nötig, der Browser verwirft das Cookie selbst nach Ablauf.
  • Sicherheit: PKCE (S256) wird immer verwendet; Client Secret ist optional (für "public" Clients).
    Stellt sicher, dass die n8n-Instanz per HTTPS erreichbar ist, da sensible Tokens durchlaufen (alle
    gesetzten Cookies sind HttpOnly/Secure). Der Encryption Key/Decryption Key sollte ein langes,
    zufälliges Geheimnis sein — wer ihn kennt, kann die verschlüsselten Cookies/Item-Felder entschlüsseln.
  • Der HTTP Request Node muss in derselben Workflow-Ausführung wie der Login-Callback bzw. das
    Auto-Login/Refresh laufen (direkt oder indirekt nachgeschaltet) — die Credential kann keinen Token
    aus einer anderen, bereits abgeschlossenen Ausführung "nachladen".

Discussion