Authentifizierung & Rollen

NodePilot verwaltet widerrufbare serverseitige Sessions. Eine erfolgreiche Anmeldung erzeugt ein JWT-Cookie und einen CSRF-Token. Rollen und Folder-RBAC bestimmen die verfügbaren Aktionen.

Session, JWT und BCrypt

  • Jeder Login erzeugt eine serverseitig gespeicherte, widerrufbare Session. Die absolute Lebensdauer beträgt standardmäßig acht Stunden; Refresh verlängert diese Grenze nicht.
  • Das JWT trägt eine opaque Session-ID, Security-Stamp und jti, aber keine Gruppenliste. Der Signatur-Key kommt aus Jwt:Key oder dem automatisch erzeugten jwt-secret.key.
  • Lokale Passwörter werden mit BCrypt gespeichert. Der Produktionsdefault Authentication:LocalLoginMode=BreakGlassOnly erlaubt nur explizit markierte Notfallkonten.
  • Alle Loginwege konvergieren auf JWT-Cookie (np_auth, httpOnly) + CSRF-Token. Logout, Deaktivierung, Tombstone oder eine Sicherheitsänderung widerrufen die Session serverseitig.
  • Externe Autorisierung muss spätestens alle 15 Minuten aktualisiert sein; veraltete Membership-Snapshots werden abgewiesen. Eine lokal durch einen Admin gesetzte Deaktivierung bleibt sticky und kann weder durch AD noch durch SCIM active=true aufgehoben werden.

Rollen

EndpointAdminOperatorViewer
GET /api/{workflows,executions,machines}✓✓✓
POST /api/workflows, PUT, `POST /{id}/duplicateexecute`✓✓
POST /api/machines, PUT✓✓✗
`GETPOSTPUT /api/credentials`✓
POST /api/executions/{id}/cancel✓✓✗
DELETE /{workflows,machines,credentials}/{id}✓✗✗
POST /api/trigger/{name}API-Key via X-Api-Key-Header

Initial-Admin: erster Login bei leerer DB (One-Shot-Token admin-setup.token).

Auth-Pfade

Die aktiven Pfade werden beim Prozessstart eingefroren und erzeugen dieselbe Session:

PfadEndpointDefaultUse Case
Lokales BCrypt-PasswortPOST /api/auth/loginBreakGlassOnlyNotfallzugang; optional Disabled oder Enabled
LDAP Simple Bind über LDAPSPOST /api/auth/loginausDomänen-Login mit Benutzername und Passwort
Windows Negotiate/KerberosPOST /api/auth/windowsausSSO von domänengebundenen Windows-Clients
OpenID ConnectGET /api/auth/oidcaus, release-gatedAuthorization Code + PKCE für Enterprise-IdPs

Details, sichere Defaults und Preview-Status: AD SSO Preview.

GET /api/auth/methods (anonym) ist das Discovery-Endpoint für die LoginPage:

{
  "local": true,
  "ldap": true,
  "windows": true,
  "windowsEndpoint": "/api/auth/windows",
  "oidc": true,
  "oidcEndpoint": "/api/auth/oidc",
  "oidcDisplayName": "Firmenkonto"
}

Deaktivierte Methoden melden false und null. local ist nur bei LocalLoginMode=Disabled falsch. Änderungen an Authentication:* benötigen einen Service-Neustart. Im Cluster ist die Sektion Config-as-Code; ein Admin-PUT liefert 409 CLUSTER_CONFIG_AS_CODE_REQUIRED.

Beispiele

Login setzt das httpOnly np_auth-Cookie und ein np_csrf-Cookie. Browser-Clients erhalten nur die Identität; die Response enthält bewusst keinen Token. CLI-Aufrufe und Skripte ohne Cookie-Jar fordern einen Bearer-Token mit dem Header X-Auth-Token-Response: true an.

Bei Cookie-Authentifizierung benötigen schreibende Aufrufe (POST/PUT/PATCH/DELETE) zusätzlich den Header X-CSRF-Token mit dem Wert des Cookies np_csrf; ohne ihn folgt 403 csrf_mismatch. Login und Windows-Login sind ausgenommen. Die Bash-Funktion unten liest den aktuellen Wert aus einer Cookie-Datei für genau diesen Server. Refresh schreibt die rotierenden Cookies mit -c cookie.jar zurück; der nächste Aufruf liest dadurch den neuen CSRF-Wert. Bearer-Aufrufe benötigen keinen CSRF-Header.

NP=http://localhost:5000

csrf_token() {
  awk '$6 == "np_csrf" {sub(/\r$/, "", $7); print $7}' cookie.jar
}

# Browser-Style: Token nur im Cookie, Body = Identität
curl -s -c cookie.jar -X POST "$NP/api/auth/login" \
  -H 'Content-Type: application/json' \
  -d '{ "username":"admin", "password":"s3cret-pass" }'
# 200 → {"userId":"...","username":"admin","role":"Admin"}   (kein Token-Feld)

# Skript-Style: Token in der Body (für Authorization: Bearer <token>)
curl -s -c cookie.jar -X POST "$NP/api/auth/login" \
  -H 'Content-Type: application/json' \
  -H 'X-Auth-Token-Response: true' \
  -d '{ "username":"admin", "password":"s3cret-pass" }'
# 200 → {"token":"eyJ...","userId":"...","username":"admin","role":"Admin","expiresAt":"2026-08-15T18:30:00Z"}

# Folgende Aufrufe authentifizieren (Cookie oder Bearer)
curl -s -b cookie.jar "$NP/api/auth/me"
# {"id":"...","username":"admin","role":"Admin"}
curl -s -H "Authorization: Bearer $TOKEN" "$NP/api/workflows"

Bootstrap (leere DB, erster Login): 401 {"code":"SETUP_TOKEN_REQUIRED","message":"Admin bootstrap required. Send the X-Setup-Token header ..."} — die Web-Oberfläche reagiert auf diesen Code und blendet ihr Setup-Token-Feld ein. Per API: Setup-Token aus admin-setup.token:

curl -s -c cookie.jar -X POST "$NP/api/auth/login" \
  -H 'Content-Type: application/json' \
  -H 'X-Setup-Token: '"$(cat admin-setup.token)" \
  -H 'X-Auth-Token-Response: true' \
  -d '{ "username":"admin", "password":"new-admin-pass-1" }'

LDAP läuft über denselben POST /api/auth/login und ist nur mit LDAPS, Service-Bind und Gruppen-Allowlist startfähig. Windows-SSO ist ein separater Negotiate-Endpoint: NodePilot übernimmt die primäre SID aus Kerberos, lädt aber bei jedem Login den autoritativen Account- und Gruppen-Snapshot per Service-Bind über LDAPS. PAC-Gruppen werden nicht für die Autorisierung vertraut. OIDC startet als Browser-Navigation und verwendet keinen Passwort-POST an NodePilot:

# Windows Negotiate/Kerberos (curl muss SPNEGO können; Windows-Clients nutzen den Browser/HTTP-Client)
curl -s --negotiate -u : -c cookie.jar -X POST "$NP/api/auth/windows"
# 200 → {"userId":"...","username":"DOMAIN\\user","role":"Operator"}   (401/503 wenn nicht konfiguriert)

# OIDC im Browser öffnen; der Provider leitet auf /api/auth/oidc/callback zurück
# Beim IdP als Redirect URI registrieren: $NP/signin-oidc
# /api/auth/oidc/callback ist nur die interne Landing-URL nach der Handler-Validierung.
# GET $NP/api/auth/oidc

# Refresh + Logout
curl -s -b cookie.jar -c cookie.jar -H "X-CSRF-Token: $(csrf_token)" -X POST "$NP/api/auth/refresh"   # neues JWT im Cookie (Body nur Identität; Token im Body nur bei Authorization: Bearer), gleiche absolute Session-Grenze
curl -s -b cookie.jar -c cookie.jar -H "X-CSRF-Token: $(csrf_token)" -X POST "$NP/api/auth/logout" -i                                   # 204 No Content

OIDC-Gruppenclaims werden nur mit vorhandenem, höchstens 15 Minuten altem iat akzeptiert. Bei Group-Overage werden ausschließlich frische, authority-scoped SCIM-Memberships verwendet. SCIM-User-externalId muss exakt und case-sensitive dem OIDC-sub entsprechen; User-Updates erneuern keine Gruppen-Freshness. Ein vollständiger Membership-Snapshot oder Heartbeat mindestens alle 15 Minuten und HA-Failover mit gemeinsamem, zertifikatgeschütztem Data-Protection-Keyring gehören zum Release-Gate. SAML ist out of scope.

LoginRequest hat kein RememberMe-Feld — nur Username + Password. 401 bei falschen Credentials: {"message":"Invalid credentials"}.

SignalR-Auth

Der httpOnly np_auth-Cookie wird beim WebSocket-Upgrade automatisch mitgeschickt (nur /hubs/); kein ?access_token=-Querystring.

Security-Headers (Non-Dev)

HSTS, CSP, X-Frame-Options=DENY, nosniff, Referrer-Policy.

Rate-Limiting

Für die Auth-Endpoints gilt per-IP im Sliding-Window: login 50/Min, refresh 20/Min.

Die vollständige Tabelle über alle Bereiche steht — als einzige Quelle — unter Hardening-Flags.

External Trigger

Gated via X-Api-Key. Bevorzugt werden SHA-256-gehashte Schlüssel unter ExternalTrigger:Keys:<id> mit einer GUID-basierten AllowedWorkflowIds-Liste. Zusätzlich muss der Workflow einen aktiven manualTrigger enthalten. Der Legacy-Key ExternalTrigger:ApiKey ist ohne eigene AllowedWorkflowIds-Liste wirkungslos. Die höchstprior deklarierte Keys-Map ist der vollständige Snapshot ({} widerruft niedrigere Keys); Allowlisten sind ebenfalls atomar ([] = deny-all). Optionale Idempotency-Key-Header gelten 24 Stunden und sind zusätzlich an den authentifizierten Key-Principal gebunden; gespeichert wird nur ein domain-separierter Digest.

Alle Kapitel