Gehostetes Login (Universal Login)
Das Problem: Wo sollte das Login-Formular wohnen?
Jede Anwendung, die Benutzer authentifiziert, muss entscheiden, wo das Login-Formular wohnt. Es gibt drei grundlegende Ansätze, und die Wahl hat tiefgreifende Sicherheitsauswirkungen.
Eingebettetes Login (der riskante Ansatz)
Die Anwendung rendert ihr eigenes Login-Formular und sammelt Anmeldedaten direkt:
Die Anmeldedaten werden an einem anderen Ursprung eingegeben — einem, der vollständig vom Identity Provider kontrolliert wird. Das JavaScript deiner Anwendung kann die Formularfelder nicht lesen, Tastenanschläge nicht abfangen oder auf die Anmeldedaten nicht zugreifen. Die Same-Origin-Richtlinie des Browsers erzwingt diese Grenze automatisch.
| Risiko | Eingebettetes Login | Gehostetes Login |
|---|---|---|
| XSS stiehlt Anmeldedaten | Ja — dein JS kann das Passwortfeld lesen | Nein — anderer Ursprung, dein JS kann nicht darauf zugreifen |
| Drittanbieter-Skript liest Anmeldedaten | Ja — jedes Skript in deinem Bundle läuft im selben Ursprung | Nein — Drittanbieter-Skripte in deinem Ursprung können die Auth-Seite nicht erreichen |
| Anmeldedaten-Phishing über DOM-Manipulation | Ja — ein Angreifer kann dein Login-Formular modifizieren | Nein — die Login-Seite befindet sich in der Domain des IdP |
| Passwort-Manager-Autofill-Bereich | Deine Domain | Die Domain des IdP (konsistent über alle Apps) |
| MFA-Durchsetzung | Du musst es implementieren | Der IdP übernimmt es transparent |
| Social-Login-Integration | Du musst jeden Provider integrieren | Der IdP zeigt alle konfigurierten Provider an |
| Compliance-Audit-Bereich | Deine gesamte Anwendung | Nur die Login-Seiten des IdP |
Gehostetes Login ist der Ansatz, der vom OAuth 2.0 Security Best Current Practice (RFC 6819, draft-ietf-oauth-security-topics) empfohlen wird. Auth0 nennt es “Universal Login”, Okta nennt es “Okta-hosted Sign-In”, und Auris nennt es “Hosted Login”. Das Sicherheitsprinzip ist dasselbe: Lass deine Anwendung niemals rohe Anmeldedaten berühren.
Funktionsweise: Der vollständige Flow
Auris Hosted Login implementiert den OAuth2-Autorisierungscode-Flow mit PKCE (RFC 7636). Hier ist die vollständige Sequenz, Schritt für Schritt.
Sequenzdiagramm
PKCE S256: Warum es wichtig ist
PKCE (Proof Key for Code Exchange) verhindert Autorisierungscode-Abfangangriffe. Der Mechanismus ist einfach, aber effektiv.
Code Verifier und Code Challenge
Bevor der Flow eingeleitet wird, generiert der Client zwei Werte:
- Code Verifier: Ein kryptografisch zufälliger String (43-128 Zeichen)
- Code Challenge:
BASE64URL(SHA256(code_verifier))
Der Client sendet den Code Challenge (den Hash) an den Autorisierungs-Endpunkt. Beim Austausch des Codes gegen Tokens sendet der Client den rohen Code Verifier. Auris berechnet SHA256(code_verifier) neu und überprüft, ob er mit dem gespeicherten Code Challenge übereinstimmt.
code_verifier: "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
│
SHA-256 + Base64URL
│
▼
code_challenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"Warum dies Abfangen stoppt
Wenn ein Angreifer den Autorisierungscode in der Weiterleitungs-URL abfängt (über eine bösartige App, die im gleichen URI-Schema registriert ist, einen Proxy oder den Browser-Verlauf), können sie ihn nicht austauschen. Um den Code auszutauschen, benötigen sie den code_verifier — der nie durch die Browser-URL übertragen wurde. Er wurde im Speicher des Clients gespeichert und nur direkt über HTTPS an den Token-Endpunkt gesendet.
SHA-256 ist eine Einwegfunktion: Ausgehend vom Code Challenge kann der Angreifer ihn nicht umkehren, um den Code Verifier zu erhalten.
Auris erzwingt PKCE S256 bei allen Autorisierungscode-Flows. Die plain-Challenge-Methode wird abgelehnt. Es gibt keine Konfiguration zum Deaktivieren von PKCE — es ist immer erforderlich.
Sitzungsverwaltung
Auris verwendet zwei serverseitige Modelle zur Verwaltung des gehosteten Login-Flows:
OAuthSession
Wird in Schritt 3 erstellt, wenn der Benutzer am Autorisierungs-Endpunkt ankommt. Wird in der Datenbank gespeichert und durch ein httpOnly-Cookie identifiziert.
| Feld | Beschreibung |
|---|---|
sessionToken | Eindeutiger Bezeichner (als httpOnly, Secure, SameSite=Lax-Cookie gesetzt) |
clientId | Die Anwendung, die Authentifizierung anfordert |
redirectUri | Wohin nach der Authentifizierung weitergeleitet werden soll |
codeChallenge | PKCE-Code-Challenge (S256) |
codeChallengeMethod | Immer S256 |
state | CSRF-State-Wert vom Client |
scope | Angeforderte OAuth-Scopes |
TTL | 30 Minuten (Sitzung läuft ab, wenn der Benutzer den Login nicht abschließt) |
Die OAuthSession verfolgt den Fortschritt des Benutzers durch die gehosteten Login-Seiten. Sie bleibt über die Login-Seite, die 2FA-Seite (falls ausgelöst) und alle Social-Login-Weiterleitungen hinweg bestehen. Wenn der Benutzer den Flow abbricht, läuft die Sitzung ab und wird durch einen Cron-Job bereinigt.
AuthorizationCode
Wird in Schritt 9 nach erfolgreicher Authentifizierung erstellt.
| Feld | Beschreibung |
|---|---|
code | Eindeutiger Autorisierungscode |
clientId | Muss mit der client_id der Token-Anfrage übereinstimmen |
redirectUri | Muss genau mit der redirect_uri der Token-Anfrage übereinstimmen |
codeChallenge | Für PKCE-Verifizierung beim Token-Austausch gespeichert |
codeChallengeMethod | S256 |
userId | Der authentifizierte Benutzer |
TTL | 5 Minuten |
usedAt | Wird beim ersten Gebrauch atomar gesetzt (Einmaligkeits-Durchsetzung) |
Der Autorisierungscode wird über eine atomare Prisma-Transaktion verbraucht: Der Code wird als verwendet markiert und in derselben Datenbankoperation gelöscht. Wenn zwei Anfragen versuchen, denselben Code gleichzeitig zu verwenden, gelingt nur eine — die andere erhält einen Fehler.
// Atomare Einmaligkeits-Durchsetzung (vereinfacht)
const code = await prisma.authorizationCode.update({
where: {
code: authCode,
usedAt: null, // Gelingt nur, wenn noch nicht verwendet
},
data: {
usedAt: new Date(),
},
})
// Wenn der Code bereits verwendet wurde, wirft Prisma (Datensatz nicht mit usedAt: null gefunden)Tenant-Branding
Die gehosteten Auris-Login-Seiten passen sich automatisch an die Branding-Konfiguration des Tenants an. Tenants konfigurieren Branding in der Auris-Konsole unter Einstellungen > Branding.
| Einstellung | Auswirkung auf das gehostete Login |
|---|---|
brandingCompanyName | Wird als Seitentitel und in der Kopfzeile angezeigt |
brandingLogoUrl | Ersetzt das Standard-Auris-Logo auf der Login-Seite |
brandingBackgroundColor | Legt die Seitenhintergrundfarbe fest (mit Verlauf) |
brandingFaviconUrl | Setzt das Favicon des Browser-Tabs |
Die gehosteten Login-Seiten lesen Branding aus dem HostedAuthContext, der aus der Kette clientId der OAuthSession → Anwendung → Tenant aufgelöst wird:
Das Branding wird rein mit CSS-Custom-Properties angewendet, sodass die gehosteten Seiten unabhängig vom Farbschema des Tenants korrekt gerendert werden. Die Konsole enthält eine Live-Vorschau, damit Administratoren die Auswirkungen vor der Veröffentlichung sehen können.
Die Sicherheitspipeline
Jeder Authentifizierungsversuch über das gehostete Login durchläuft die vollständige Auris-Sicherheitspipeline. Keine Schicht kann umgangen werden — sie werden sequenziell ausgeführt, und jede Schicht kann die Anfrage blockieren.
Jede Schicht ist unabhängig pro Tenant konfigurierbar. Ein kleines Startup hat möglicherweise nur Keycloak-Auth aktiviert, während ein Enterprise-Tenant alle neun Schichten aktiv haben könnte.
Alle Sicherheitsschichten umschließen ihre Logik in try/catch-Blöcken, um zu verhindern, dass der Ausfall einer einzelnen Schicht den gesamten Login-Flow zum Absturz bringt. Der Standard-Fehlermodus jeder Schicht ist jedoch, die Anfrage zu blockieren (fail-secure). Ein Absturz in der CAPTCHA-Verifizierung beispielsweise blockiert den Login, anstatt stillschweigend durchzulassen.
Social Login auf gehosteten Seiten
Auris unterstützt 9 Social-Login-Provider auf den gehosteten Login-Seiten:
| Provider | Protokoll | Symbol |
|---|---|---|
| OIDC | Google „G” | |
| GitHub | OAuth 2.0 | Octocat |
| Microsoft | OIDC | Microsoft-Logo |
| Apple | OIDC | Apple-Logo |
| OAuth 2.0 | Facebook „f” | |
| Discord | OAuth 2.0 | Discord-Logo |
| OAuth 2.0 | LinkedIn „in” | |
| Twitter/X | OAuth 2.0 | X-Logo |
| Slack | OAuth 2.0 | Slack-Raute |
Wenn ein Tenant Social-Provider in der Konsole aktiviert, zeigt die gehostete Login-Seite automatisch die entsprechenden Schaltflächen an. In der Client-Anwendung sind keine Änderungen erforderlich — Social Login wird vollständig innerhalb der gehosteten Seite behandelt.
SSO-Erkennung über E-Mail-Domain
Für Tenants mit konfiguriertem Enterprise SSO (SAML 2.0 / OIDC-Federation) kann die gehostete Login-Seite SSO-Domains automatisch erkennen. Wenn der Benutzer seine E-Mail-Adresse eingibt:
- Die Seite ruft
GET /api/auth/sso/[email protected]auf - Wenn die Domain
company.comverifiziert und mit einer SSO-Verbindung verknüpft ist, zeigt die Seite eine Schaltfläche „Mit SSO fortfahren” an - Ein Klick darauf leitet durch den SSO-Flow (Keycloak IdP-Brokering) anstelle der Passwort-Authentifizierung weiter
Dies bietet eine nahtlose Erfahrung: Benutzer von SSO-fähigen Organisationen werden automatisch zu ihrem Unternehmens-Identity-Provider weitergeleitet.
Vergleich: Login-Ansätze
| Dimension | Gehostetes Login (Auris) | Eingebettetes Login | Benutzerdefinierte Login-Seite |
|---|---|---|---|
| Sicherheitsgrenze | Same-Origin-Richtlinie des Browsers trennt Anmeldedaten vom App-Code | Anmeldedaten sind für alle Skripte im App-Ursprung zugänglich | Abhängig von der Implementierung |
| XSS-Auswirkung | XSS in deiner App kann keine Anmeldedaten stehlen | XSS in deiner App kann das Passwortfeld direkt lesen | XSS in deiner App kann Anmeldedaten möglicherweise erreichen |
| Anpassung | Logo, Farben, Favicon, Hintergrund (Tenant-Branding) | Vollständige Kontrolle über jeden Pixel | Vollständige Kontrolle, aber du pflegst den Code |
| MFA-Handling | Transparent — Auris übernimmt TOTP, SMS, WebAuthn, adaptives MFA | Du musst MFA-UI und Verifizierungslogik implementieren | Du musst implementieren oder delegieren |
| Social Login | Automatisch — in Konsole aktivieren, Schaltflächen erscheinen | Du musst jeden OAuth-Provider integrieren | Du musst jeden Provider integrieren |
| SSO / SAML | Automatisch — IdP-Brokering über Keycloak | Nicht ohne serverseitige Integration verfügbar | Komplexe serverseitige Implementierung |
| Wartung | Null — Auris aktualisiert die Login-Seite | Du musst deine Auth-UI aktuell halten | Du musst die Seite pflegen |
| Compliance | Anmeldedaten berühren deinen Ursprung nie (sauberer Audit-Trail) | Deine Anwendung ist im Umfang der Anmeldedaten-Verarbeitung | Abhängig von der Architektur |
| Implementierungsaufwand | Ein SDK-Aufruf (loginWithRedirect()) | Formular erstellen, API aufrufen, Fehler behandeln, Tokens speichern, MFA implementieren | Alles von Grund auf erstellen |
| Passwort-Manager-UX | Konsistent über alle Apps mit demselben Auris-Tenant | Anderes Autofill-Ziel pro Anwendung | Anderes Autofill-Ziel pro App |
Code-Beispiel: Verwendung des Auris SDK
Der empfohlene Weg zur Integration des gehosteten Logins ist über das Auris SDK. Das SDK übernimmt PKCE-Generierung, State-Verwaltung, Weiterleitungsbehandlung und Token-Speicherung automatisch.
JavaScript SDK (@auris/js)
import { AurisClient } from '@auris/js'
const auris = new AurisClient({
domain: 'your-tenant.auris.example.com',
clientId: 'app_abc123',
redirectUri: 'https://app.ihredomain.com/callback',
autoRefresh: true,
})
// Login einleiten — leitet zur gehosteten Login-Seite weiter
// Intern: generiert code_verifier, berechnet S256-Challenge,
// generiert State, speichert beides in sessionStorage, leitet weiter
await auris.loginWithRedirect()
// Auf der Callback-Seite (/callback) — Code gegen Tokens austauschen
// Intern: validiert State, extrahiert Code aus URL,
// sendet Code + code_verifier an Token-Endpunkt, speichert Tokens
const result = await auris.handleRedirectCallback()
console.log(result.user)
// {
// id: 'usr_abc123',
// email: '[email protected]',
// firstName: 'Jane',
// lastName: 'Doe',
// roles: ['user'],
// }React SDK (@auris/react)
import { AurisProvider, useAuris, AuthGuard } from '@auris/react'
// Deine App mit AurisProvider umhüllen
function App() {
return (
<AurisProvider
domain="your-tenant.auris.example.com"
clientId="app_abc123"
redirectUri="https://app.ihredomain.com/callback"
>
<Router />
</AurisProvider>
)
}
// Den Hook in einer beliebigen Komponente verwenden
function LoginButton() {
const { loginWithRedirect, isAuthenticated, user, logout } = useAuris()
if (isAuthenticated) {
return (
<div>
<span>Willkommen, {user.firstName}</span>
<button onClick={() => logout()}>Abmelden</button>
</div>
)
}
return <button onClick={() => loginWithRedirect()}>Anmelden</button>
}
// Routen mit AuthGuard schützen
function ProtectedPage() {
return (
<AuthGuard>
<Dashboard />
</AuthGuard>
)
}Next.js SDK (@auris/nextjs)
// middleware.ts — Routen am Edge schützen
import { aurisMiddleware } from '@auris/nextjs'
export default aurisMiddleware({
protectedPaths: ['/dashboard', '/settings', '/admin'],
publicPaths: ['/', '/about', '/pricing'],
loginPath: '/auth/login',
})
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}// Server Component — Auth auf dem Server prüfen
import { getSession } from '@auris/nextjs/server'
export default async function DashboardPage() {
const session = await getSession()
if (!session) {
redirect('/auth/login')
}
return <h1>Willkommen, {session.user.firstName}</h1>
}Weiterleitungs-URI-Validierung
Auris erzwingt eine strikte Weiterleitungs-URI-Validierung, um den Diebstahl von Autorisierungscodes über Open-Redirect-Angriffe zu verhindern.
Regeln
| Regel | Beschreibung |
|---|---|
| Exakte Übereinstimmung | Die redirect_uri in der Token-Anfrage muss genau mit der an den Autorisierungs-Endpunkt gesendeten übereinstimmen |
| Vorregistriert | Die redirect_uri muss im redirectUris-Array der Anwendung in der Auris-Konsole registriert sein |
| Keine Wildcards | Wildcard-Weiterleitungs-URIs (z. B. https://*.example.com/callback) werden nicht unterstützt |
| Keine Fragmente | URIs mit #fragment-Komponenten werden abgelehnt |
| HTTPS erforderlich | HTTP-Weiterleitungs-URIs werden in der Produktion abgelehnt (localhost ist für die Entwicklung erlaubt) |
Wenn die redirect_uri nicht mit einem registrierten Wert übereinstimmt, gibt Auris eine Fehlerseite direkt zurück (es leitet nicht zur nicht übereinstimmenden URI weiter, da dies den Zweck zunichte machen würde).
Warum exakte Übereinstimmung?
Partielle Übereinstimmung (z. B. Präfix-Übereinstimmung wie https://app.example.com/) schafft eine Schwachstelle: Ein Angreifer könnte https://app.example.com/evil-page registrieren und Autorisierungscodes dorthin leiten lassen. Exakte Übereinstimmung eliminiert diese Angriffskategorie vollständig.
Füge bei der Registrierung von Weiterleitungs-URIs in der Auris-Konsole den vollständigen Pfad einschließlich aller nachfolgenden Komponenten ein. https://app.example.com/callback und https://app.example.com/callback/ werden als verschiedene URIs behandelt. Registriere genau die URI, die deine Anwendung verwenden wird.
Bereinigung und Ablauf
Auris führt einen Cron-Job (/api/oauth/cron/cleanup/) zur Bereinigung abgelaufener Sitzungen und Codes aus:
| Ressource | TTL | Bereinigungsverhalten |
|---|---|---|
| OAuthSession | 30 Minuten | Gelöscht, wenn nicht innerhalb der TTL abgeschlossen |
| AuthorizationCode | 5 Minuten | Gelöscht, wenn nicht innerhalb der TTL ausgetauscht |
| Verwendete AuthorizationCodes | Sofort | Beim Verbrauch atomar gelöscht |
Der Cron läuft periodisch (konfiguriert in vercel.json) und stellt sicher, dass die Datenbank keine veralteten Sitzungen aus abgebrochenen Login-Flows ansammelt.
Verwandte Konzepte
- PKCE-Flow — Detaillierte Anleitung zum PKCE-Mechanismus, der vom gehosteten Login verwendet wird
- OAuth 2.0 & OIDC — Das Autorisierungs-Framework, das das gehostete Login implementiert
- Sitzungen & Token-Rotation — Wie Tokens nach dem gehosteten Login verwaltet werden
- Adaptives MFA & Risikobewertung — Risikobasiertes Step-up-MFA während des Login-Flows
- Actions & Sandbox-Ausführung — Benutzerdefinierte Logik-Hooks, die während des gehosteten Logins ausgeführt werden
- Tokens erklärt — Die Zugriffs-, Refresh- und ID-Tokens, die nach dem Login zurückgegeben werden