Integrieren Sie die Ottili ONE-Authentifizierung in Ihre Anwendung mit OAuth 2.0 und OpenID Connect (OIDC).
Übersicht
"Mit Ottili anmelden" ermöglicht Ihren Benutzern, sich mit ihren bestehenden Ottili ONE-Konten zu authentifizieren. Dies bietet:
- Single Sign-On (SSO)*: Benutzer müssen keine neuen Konten erstellen
- Verifizierte Identitäten*: E-Mail-Adressen werden von Ottili ONE verifiziert
- Sichere Authentifizierung*: OAuth 2.0 mit PKCE und OIDC
- Benutzereinwilligung*: Benutzer kontrollieren, welche Daten sie teilen
Wie es funktioniert
"Mit Ottili anmelden" verwendet den OAuth 2.0 Authorization Code Flow mit PKCE (Proof Key for Code Exchange) und OpenID Connect:
1. Benutzer klickt auf "Mit Ottili anmelden"* in Ihrer App
2. Ihre App leitet um* zum Ottili ONE-Autorisierungsendpunkt
3. Benutzer authentifiziert sich* bei Ottili ONE (falls nicht bereits angemeldet)
4. Benutzer stimmt zu*, sein Profil mit Ihrer App zu teilen
5. Ottili ONE leitet zurück* zu Ihrer App mit einem Autorisierungscode
6. Ihre App tauscht* den Code gegen Access- und ID-Tokens
7. Ihre App verwendet* die Tokens zur Authentifizierung des Benutzers
Voraussetzungen
Bevor Sie beginnen, benötigen Sie:
1. Ottili ONE-Konto*: Erstellen Sie eines unter [ottili.one](https://ottili.one)
2. Registrierte Anwendung*: Registrieren Sie Ihre App in der Ottili ONE Console
3. Client ID und Secret*: Während der Registrierung erhalten
4. Redirect URI*: Wohin Ottili ONE Benutzer nach der Authentifizierung sendet
Schritt 1: Ihre Anwendung registrieren
1. Melden Sie sich bei der [Ottili ONE Console](https://console.ottili.one) an
2. Navigieren Sie zu Einstellungen → OAuth-Anwendungen*
3. Klicken Sie auf "Neue Anwendung registrieren"*
4. Füllen Sie die Anwendungsdetails aus:
- Anwendungsname*: Name Ihrer App (wird Benutzern angezeigt)
- Anwendungs-URL*: Homepage Ihrer App
- Redirect URIs*: Wohin Benutzer nach der Authentifizierung gesendet werden (z.B. https://ihreapp.de/auth/callback)
- Scopes*: Welche Daten Sie benötigen (z.B. openid, profile, email)
5. Klicken Sie auf "Registrieren"*
6. Speichern Sie Ihre Client ID und Client Secret* (das Secret wird nur einmal angezeigt!)
Schritt 2: Den OAuth-Flow implementieren
Autorisierungsanfrage
Leiten Sie Benutzer zum Autorisierungsendpunkt um:
GET https://auth.ottili.one/oauth/authorize
?response_type=code
&client_id=IHRE_CLIENT_ID
&redirect_uri=IHRE_REDIRECT_URI
&scope=openid profile email
&state=ZUFÄLLIGER_STATE_WERT
&code_challenge=CODE_CHALLENGE
&code_challenge_method=S256Parameter:*
response_type: Musscodeseinclient_id: Client ID Ihrer Anwendungredirect_uri: Muss mit einer Ihrer registrierten Redirect URIs übereinstimmenscope: Leerzeichen-getrennte Liste von Scopes (openidist für OIDC erforderlich)state: Zufälliger Wert zur Verhinderung von CSRF-Angriffencode_challenge: PKCE-Challenge (siehe unten)code_challenge_method: MussS256sein
PKCE (Proof Key for Code Exchange)
PKCE verhindert Authorization Code Interception Attacks:
1. Code Verifier generieren*: Zufällige Zeichenfolge (43-128 Zeichen)
2. Code Challenge erstellen*: SHA256-Hash des Verifiers, base64url-kodiert
3. Challenge senden* in der Autorisierungsanfrage
4. Verifier senden* in der Token-Austauschanfrage
Beispiel (Node.js):*
const crypto = require('crypto');
// Code Verifier generieren
const codeVerifier = crypto.randomBytes(32).toString('base64url');
// Code Challenge erstellen
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
// Verifier in der Session für spätere Verwendung speichern
session.codeVerifier = codeVerifier;Token-Austausch
Nachdem der Benutzer Ihre App autorisiert hat, leitet Ottili ONE zu Ihrer redirect_uri mit einem code-Parameter um:
POST https://auth.ottili.one/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=IHRE_REDIRECT_URI
&client_id=IHRE_CLIENT_ID
&client_secret=IHRE_CLIENT_SECRET
&code_verifier=CODE_VERIFIERAntwort:*
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"scope": "openid profile email"
}ID Token verifizieren
Das ID Token ist ein JWT, das Benutzerinformationen enthält:
{
"iss": "https://auth.ottili.one",
"sub": "user_123456",
"aud": "IHRE_CLIENT_ID",
"exp": 1234567890,
"iat": 1234567890,
"email": "user@example.com",
"email_verified": true,
"name": "Max Mustermann",
"preferred_username": "maxmustermann"
}Verifizierungsschritte:*
1. Signatur*: Mit Ottili ONE's öffentlichem Schlüssel verifizieren (vom JWKS-Endpunkt)
2. Issuer*: Muss https://auth.ottili.one sein
3. Audience*: Muss mit Ihrer Client ID übereinstimmen
4. Expiration*: Darf nicht abgelaufen sein
5. Nonce*: Wenn Sie eine Nonce gesendet haben, muss sie übereinstimmen
JWKS-Endpunkt:*
GET https://auth.ottili.one/.well-known/jwks.jsonSchritt 3: Das Access Token verwenden
Verwenden Sie das Access Token, um den UserInfo-Endpunkt aufzurufen:
GET https://auth.ottili.one/oauth/userinfo
Authorization: Bearer ACCESS_TOKENAntwort:*
{
"sub": "user_123456",
"email": "user@example.com",
"email_verified": true,
"name": "Max Mustermann",
"preferred_username": "maxmustermann",
"picture": "https://auth.ottili.one/avatars/user_123456.jpg"
}Verfügbare Scopes
| Scope | Beschreibung | Claims |
|---|---|---|
openid | Erforderlich für OIDC | sub |
profile | Benutzerprofilinformationen | name, preferred_username, picture |
email | Benutzer-E-Mail-Adresse | email, email_verified |
Code-Beispiele
Node.js (Express)
const express = require('express');
const crypto = require('crypto');
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
const app = express();
const CLIENT_ID = process.env.OTTILI_CLIENT_ID;
const CLIENT_SECRET = process.env.OTTILI_CLIENT_SECRET;
const REDIRECT_URI = 'http://localhost:3000/auth/callback';
const jwks = jwksClient({
jwksUri: 'https://auth.ottili.one/.well-known/jwks.json'
});
// OAuth-Flow starten
app.get('/auth/login', (req, res) => {
const state = crypto.randomBytes(16).toString('hex');
const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
req.session.state = state;
req.session.codeVerifier = codeVerifier;
const params = new URLSearchParams({
response_type: 'code',
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
scope: 'openid profile email',
state: state,
code_challenge: codeChallenge,
code_challenge_method: 'S256'
});
res.redirect(`https://auth.ottili.one/oauth/authorize?${params}`);
});
// Callback verarbeiten
app.get('/auth/callback', async (req, res) => {
const { code, state } = req.query;
// State verifizieren
if (state !== req.session.state) {
return res.status(400).send('Ungültiger State');
}
// Code gegen Tokens tauschen
const tokenResponse = await fetch('https://auth.ottili.one/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: REDIRECT_URI,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
code_verifier: req.session.codeVerifier
})
});
const tokens = await tokenResponse.json();
// ID Token verifizieren
const key = await jwks.getSigningKey();
const publicKey = key.getPublicKey();
const decoded = jwt.verify(tokens.id_token, publicKey, {
issuer: 'https://auth.ottili.one',
audience: CLIENT_ID
});
// Benutzerinformationen abrufen
const userInfoResponse = await fetch('https://auth.ottili.one/oauth/userinfo', {
headers: { Authorization: `Bearer ${tokens.access_token}` }
});
const userInfo = await userInfoResponse.json();
// Session erstellen
req.session.user = userInfo;
req.session.accessToken = tokens.access_token;
res.redirect('/dashboard');
});
app.listen(3000);Python (Flask)
from flask import Flask, redirect, request, session
import requests
import hashlib
import base64
import secrets
from authlib.integrations.flask_client import OAuth
app = Flask(__name__)
app.secret_key = secrets.token_hex(32)
oauth = OAuth(app)
ottili = oauth.register(
'ottili',
client_id=os.environ['OTTILI_CLIENT_ID'],
client_secret=os.environ['OTTILI_CLIENT_SECRET'],
authorize_url='https://auth.ottili.one/oauth/authorize',
access_token_url='https://auth.ottili.one/oauth/token',
userinfo_endpoint='https://auth.ottili.one/oauth/userinfo',
client_kwargs={'scope': 'openid profile email'}
)
@app.route('/auth/login')
def login():
# PKCE generieren
code_verifier = secrets.token_urlsafe(32)
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).decode().rstrip('=')
session['code_verifier'] = code_verifier
return ottili.authorize_redirect(
redirect_uri='http://localhost:5000/auth/callback',
code_challenge=code_challenge,
code_challenge_method='S256'
)
@app.route('/auth/callback')
def callback():
token = ottili.authorize_access_token()
# Benutzerinformationen abrufen
userinfo = ottili.userinfo()
session['user'] = userinfo
session['access_token'] = token['access_token']
return redirect('/dashboard')
if __name__ == '__main__':
app.run(port=5000)PHP
<?php
session_start();
$CLIENT_ID = getenv('OTTILI_CLIENT_ID');
$CLIENT_SECRET = getenv('OTTILI_CLIENT_SECRET');
$REDIRECT_URI = 'http://localhost:8000/auth/callback.php';
// OAuth-Flow starten
function startOAuth() {
global $CLIENT_ID, $REDIRECT_URI;
$state = bin2hex(random_bytes(16));
$codeVerifier = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
$codeChallenge = rtrim(strtr(base64_encode(hash('sha256', $codeVerifier, true)), '+/', '-_'), '=');
$_SESSION['state'] = $state;
$_SESSION['code_verifier'] = $codeVerifier;
$params = http_build_query([
'response_type' => 'code',
'client_id' => $CLIENT_ID,
'redirect_uri' => $REDIRECT_URI,
'scope' => 'openid profile email',
'state' => $state,
'code_challenge' => $codeChallenge,
'code_challenge_method' => 'S256'
]);
header("Location: https://auth.ottili.one/oauth/authorize?$params");
exit;
}
// Callback verarbeiten
function handleCallback() {
global $CLIENT_ID, $CLIENT_SECRET, $REDIRECT_URI;
if ($_GET['state'] !== $_SESSION['state']) {
die('Ungültiger State');
}
// Code gegen Tokens tauschen
$ch = curl_init('https://auth.ottili.one/oauth/token');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
'grant_type' => 'authorization_code',
'code' => $_GET['code'],
'redirect_uri' => $REDIRECT_URI,
'client_id' => $CLIENT_ID,
'client_secret' => $CLIENT_SECRET,
'code_verifier' => $_SESSION['code_verifier']
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$tokens = json_decode(curl_exec($ch), true);
curl_close($ch);
// Benutzerinformationen abrufen
$ch = curl_init('https://auth.ottili.one/oauth/userinfo');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $tokens['access_token']
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$userinfo = json_decode(curl_exec($ch), true);
curl_close($ch);
$_SESSION['user'] = $userinfo;
$_SESSION['access_token'] = $tokens['access_token'];
header('Location: /dashboard.php');
exit;
}
?>Sicherheits-Best Practices
1. Immer PKCE verwenden
PKCE verhindert Authorization Code Interception Attacks. Es ist für öffentliche Clients erforderlich und für alle Clients empfohlen.
2. Den State-Parameter validieren
Der state-Parameter verhindert CSRF-Angriffe. Immer:
- Zufälligen Wert generieren
- In der Session des Benutzers speichern
- Verifizieren, dass er bei der Callback-Verarbeitung übereinstimmt
3. Das ID Token verifizieren
Immer verifizieren:
- Signatur*: Mit dem JWKS-Endpunkt
- Issuer*: Muss
https://auth.ottili.onesein - Audience*: Muss mit Ihrer Client ID übereinstimmen
- Expiration*: Darf nicht abgelaufen sein
4. HTTPS verwenden
Verwenden Sie immer HTTPS für Ihre Redirect URIs in der Produktion. HTTP ist nur für localhost während der Entwicklung erlaubt.
5. Tokens sicher speichern
- Access Tokens*: Im Speicher oder verschlüsseltem Speicher speichern
- Refresh Tokens*: In verschlüsseltem Speicher speichern
- Niemals* Tokens in localStorage speichern (XSS-Schwachstelle)
6. Token-Ablauf behandeln
Access Tokens laufen nach 15 Minuten ab. Verwenden Sie Refresh Tokens, um neue Access Tokens zu erhalten:
const refreshResponse = await fetch('https://auth.ottili.one/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: REFRESH_TOKEN,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET
})
});Testen
Lokale Entwicklung
Für lokale Entwicklung können Sie http://localhost Redirect URIs verwenden:
http://localhost:3000/auth/callback
http://127.0.0.1:3000/auth/callbackTestkonten
Erstellen Sie Testkonten in Ihrer Ottili ONE Console zum Testen verschiedener Szenarien:
- Neue Benutzerregistrierung
- Bestehende Benutzeranmeldung
- Benutzer mit aktiviertem 2FA
- Benutzer mit Passkeys
OIDC-Konformitätstests
Verwenden Sie die [OIDC Conformance Test Suite](https://conformance.openid.net/), um Ihre Implementierung zu verifizieren.
Fehlerbehebung
"Invalid redirect_uri" Fehler
Ursache*: Die Redirect URI stimmt nicht mit Ihren registrierten URIs überein.
Lösung*:
1. Überprüfen Sie Ihre registrierten Redirect URIs in der Console
2. Stellen Sie sicher, dass die URI in Ihrer Autorisierungsanfrage exakt übereinstimmt (einschließlich Protokoll und abschließender Schrägstriche)
"Invalid client" Fehler
Ursache*: Die Client ID oder das Secret ist falsch.
Lösung*:
1. Verifizieren Sie Ihre Client ID und Secret
2. Prüfen Sie auf zusätzliche Leerzeichen oder fehlende Zeichen
"Invalid code_verifier" Fehler
Ursache*: Der Code Verifier stimmt nicht mit der Challenge überein.
Lösung*:
1. Stellen Sie sicher, dass Sie denselben Verifier verwenden, der die Challenge generiert hat
2. Prüfen Sie, dass Sie SHA256 und base6url-Kodierung korrekt verwenden
"Invalid state" Fehler
Ursache*: Der State-Parameter stimmt nicht überein.
Lösung*:
1. Stellen Sie sicher, dass Sie den State in der Session des Benutzers speichern
2. Prüfen Sie, dass Sie die korrekten Werte vergleichen
ID Token-Verifizierung fehlschlägt
Ursache*: Die Token-Signatur, der Issuer oder die Audience ist ungültig.
Lösung*:
1. Verifizieren Sie, dass Sie den korrekten JWKS-Endpunkt verwenden
2. Prüfen Sie, dass Sie den Issuer und die Audience validieren
3. Stellen Sie sicher, dass Ihre Systemuhr synchronisiert ist (NTP)
Support
Wenn Sie Hilfe benötigen:
- Dokumentation*: [docs.ottili.one](https://docs.ottili.one)
- Community*: [community.ottili.one](https://community.ottili.one)
- E-Mail*: support@ottili.one
Changelog
2026-08-02
- Erstveröffentlichung
- OAuth 2.0 mit PKCE-Unterstützung
- OpenID Connect-Unterstützung
- UserInfo-Endpunkt
- Code-Beispiele für Node.js, Python und PHP
War dieser Artikel hilfreich?
