Skip to main content
integration

Mit Ottili anmelden - Entwicklerleitfaden

Vollständiger Leitfaden zur Integration von 'Mit Ottili anmelden' in Ihre Anwendung mit OAuth 2.0 und OpenID Connect.

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=S256

Parameter:*

  • response_type: Muss code sein
  • client_id: Client ID Ihrer Anwendung
  • redirect_uri: Muss mit einer Ihrer registrierten Redirect URIs übereinstimmen
  • scope: Leerzeichen-getrennte Liste von Scopes (openid ist für OIDC erforderlich)
  • state: Zufälliger Wert zur Verhinderung von CSRF-Angriffen
  • code_challenge: PKCE-Challenge (siehe unten)
  • code_challenge_method: Muss S256 sein

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_VERIFIER

Antwort:*

{
  "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.json

Schritt 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_TOKEN

Antwort:*

{
  "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

ScopeBeschreibungClaims
openidErforderlich für OIDCsub
profileBenutzerprofilinformationenname, preferred_username, picture
emailBenutzer-E-Mail-Adresseemail, 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.one sein
  • 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/callback

Testkonten

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?