Sign in with Dazr Identity: Entwicklerdokumentation

Menschen können ihr Google- oder Microsoft-Konto bereits innerhalb von Dazr Identity nutzen. Ihre App braucht daneben also keine eigenen Google- und Microsoft-Buttons: ein Button statt drei.

Discovery und Endpunkte

Alles steht im Discovery-Dokument. Geben Sie Ihrer Bibliothek den Issuer an, den Rest findet sie selbst.

WasAdresse
Issuerhttps://identity.dazr.eu
Discoveryhttps://identity.dazr.eu/.well-known/openid-configuration
Autorisierunghttps://identity.dazr.eu/oauth/authorize
Tokenhttps://identity.dazr.eu/oauth/token
Benutzerinfohttps://identity.dazr.eu/oauth/userinfo
Öffentliche Schlüssel (JWKS)https://identity.dazr.eu/oauth/jwks
Widerruf (RFC 7009)https://identity.dazr.eu/oauth/revoke
Introspektion (RFC 7662), nur für Webserver-Appshttps://identity.dazr.eu/oauth/introspect
Abmeldung (RP-initiated logout)https://identity.dazr.eu/oauth/logout

Eine App registrieren

Integrationsanleitungen

Jeder Stack unten nutzt seine eigene Standardunterstützung für OpenID Connect. Sie brauchen drei Werte aus der Konsole: die Client-ID, das Client-Secret (nur bei Webserver-Apps) und die Weiterleitungs-URI, die Sie registriert haben. Den Rest liefert das Discovery-Dokument.

Auth.js / NextAuth

Auth.js (NextAuth.js v5) akzeptiert ein eigenes OpenID-Connect-Provider-Objekt. Registrieren Sie eine Webserver-App, setzen Sie AUTH_SECRET, AUTH_DAZR_ID und AUTH_DAZR_SECRET und verwenden Sie diese Callback-URL als Weiterleitungs-URI: https://app.example.eu/api/auth/callback/dazr.

// auth.ts
import NextAuth from "next-auth"

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    {
      id: "dazr",
      name: "Dazr Identity",
      type: "oidc",
      issuer: "https://identity.dazr.eu",
      clientId: process.env.AUTH_DAZR_ID,
      clientSecret: process.env.AUTH_DAZR_SECRET,
      authorization: { params: { scope: "openid profile email" } },
      checks: ["pkce", "state", "nonce"],
      client: { id_token_signed_response_alg: "ES256" },
    },
  ],
})

NextAuth.js v4 funktioniert genauso, mit type: "oauth" und der Discovery-URL:

// pages/api/auth/[...nextauth].ts (NextAuth.js v4), in providers: [ ... ]
{
  id: "dazr",
  name: "Dazr Identity",
  type: "oauth",
  wellKnown: "https://identity.dazr.eu/.well-known/openid-configuration",
  clientId: process.env.DAZR_CLIENT_ID,
  clientSecret: process.env.DAZR_CLIENT_SECRET,
  authorization: { params: { scope: "openid profile email" } },
  idToken: true,
  checks: ["pkce", "state"],
  client: { id_token_signed_response_alg: "ES256" },
  profile(profile) {
    return { id: profile.sub, name: profile.name ?? null, email: profile.email }
  },
}

Next.js App Router

Ergänzen Sie mit der Datei auth.ts oben den Route Handler und einen Anmeldebutton, der eine Server Action ausführt.

// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth"
export const { GET, POST } = handlers

// app/page.tsx
import { auth, signIn, signOut } from "@/auth"

export default async function Page() {
  const session = await auth()
  if (session?.user) {
    return (
      <form action={async () => { "use server"; await signOut() }}>
        <p>Signed in as {session.user.email}</p>
        <button type="submit">Sign out</button>
      </form>
    )
  }
  return (
    <form action={async () => { "use server"; await signIn("dazr") }}>
      <button type="submit" className="dazr-signin">Sign in with Dazr Identity</button>
    </form>
  )
}

Gestalten Sie den Button wie unter Der Button gezeigt. In Callbacks ist die Dazr-sub der Wert account.providerAccountId.

WordPress

Verwenden Sie ein generisches OpenID-Connect-Client-Plugin, etwa OpenID Connect Generic Client. Registrieren Sie eine Webserver-App und füllen Sie die Plugin-Einstellungen mit den Werten unten aus. Das Plugin zeigt die zu registrierende Weiterleitungs-URI auf seiner Einstellungsseite; standardmäßig ist das https://example.com/wp-admin/admin-ajax.php?action=openid-connect-authorize.

EinstellungWert
Client-IDIhre Client-ID
Client-SecretIhr Client-Secret
Scopeopenid profile email
Login-Endpunkt (Autorisierung)https://identity.dazr.eu/oauth/authorize
Token-Endpunkthttps://identity.dazr.eu/oauth/token
Userinfo-Endpunkthttps://identity.dazr.eu/oauth/userinfo
Endpunkt zum Beenden der Sitzunghttps://identity.dazr.eu/oauth/logout
Identity Key (Identitätsschlüssel)sub
PKCEan (S256)

Die Namen der Einstellungen unterscheiden sich je nach Plugin etwas. Hat ein Plugin keine PKCE-Option, wählen Sie ein anderes: Dazr Identity lehnt Anmeldungen ohne PKCE ab.

Laravel

Laravel Socialite hat keinen eingebauten generischen OpenID-Connect-Treiber, daher fügen Sie einen kleinen eigenen Treiber hinzu. Er schickt Menschen mit PKCE zu Dazr und liest den Nutzer am Userinfo-Endpunkt aus. Socialite liest das Discovery-Dokument nicht, deshalb stehen die Endpunkte ausgeschrieben da. Registrieren Sie eine Webserver-App mit der Weiterleitungs-URI https://app.example.eu/auth/dazr/callback.

<?php
// app/Socialite/DazrProvider.php
namespace App\Socialite;

use Laravel\Socialite\Two\AbstractProvider;
use Laravel\Socialite\Two\User;

class DazrProvider extends AbstractProvider
{
    protected $scopes = ['openid', 'profile', 'email'];
    protected $scopeSeparator = ' ';
    protected $usesPKCE = true;

    protected function getAuthUrl($state)
    {
        return $this->buildAuthUrlFromBase('https://identity.dazr.eu/oauth/authorize', $state);
    }

    protected function getTokenUrl()
    {
        return 'https://identity.dazr.eu/oauth/token';
    }

    protected function getUserByToken($token)
    {
        $response = $this->getHttpClient()->get('https://identity.dazr.eu/oauth/userinfo', [
            'headers' => ['Authorization' => 'Bearer '.$token],
        ]);

        return json_decode((string) $response->getBody(), true);
    }

    protected function mapUserToObject(array $user)
    {
        return (new User)->setRaw($user)->map([
            'id' => $user['sub'],
            'name' => $user['name'] ?? null,
            'email' => $user['email'] ?? null,
        ]);
    }
}
// config/services.php
'dazr' => [
    'client_id' => env('DAZR_CLIENT_ID'),
    'client_secret' => env('DAZR_CLIENT_SECRET'),
    'redirect' => env('DAZR_REDIRECT_URI'),
],

// app/Providers/AppServiceProvider.php, in boot()
use Laravel\Socialite\Facades\Socialite;
use App\Socialite\DazrProvider;

Socialite::extend('dazr', function ($app) {
    return Socialite::buildProvider(DazrProvider::class, config('services.dazr'));
});

// routes/web.php
Route::get('/auth/dazr', fn () => Socialite::driver('dazr')->redirect());
Route::get('/auth/dazr/callback', function () {
    $dazr = Socialite::driver('dazr')->user();
    $user = \App\Models\User::updateOrCreate(
        ['dazr_sub' => $dazr->getId()],
        ['name' => $dazr->getName() ?? $dazr->getEmail(), 'email' => $dazr->getEmail()],
    );
    Auth::login($user);
    return redirect('/');
});

Supabase

Supabase Auth hat keinen generischen OpenID-Connect-Provider, den Sie auf einen beliebigen Issuer richten können: Die Anmeldeanbieter und die Third-Party-Auth-Integrationen sind feste Listen. Sie können Dazr Identity daher derzeit nicht im Supabase-Dashboard hinzufügen. Falls Supabase generische OpenID-Connect-Unterstützung ergänzt, verwenden Sie die Werte aus der Checkliste unten.

Die Alternative: Melden Sie Menschen auf Ihrem eigenen Server mit Dazr Identity an (mit einer der Anleitungen auf dieser Seite), speichern Sie die Dazr-sub in Ihrer Nutzertabelle und sprechen Sie von diesem Server aus mit dem Service-Role-Key mit Supabase, wobei Ihr eigener Code die Zugriffe prüft. Senden Sie den Service-Role-Key nie an einen Browser.

Django

Verwenden Sie mozilla-django-oidc. Registrieren Sie eine Webserver-App mit der Weiterleitungs-URI https://app.example.eu/oidc/callback/. Die Bibliothek liest das Discovery-Dokument nicht, deshalb stehen die Endpunkte ausgeschrieben da.

# settings.py
import os

INSTALLED_APPS += ["mozilla_django_oidc"]  # after django.contrib.auth
AUTHENTICATION_BACKENDS = [
    "mozilla_django_oidc.auth.OIDCAuthenticationBackend",
    "django.contrib.auth.backends.ModelBackend",
]

OIDC_RP_CLIENT_ID = os.environ["DAZR_CLIENT_ID"]
OIDC_RP_CLIENT_SECRET = os.environ["DAZR_CLIENT_SECRET"]
OIDC_RP_SCOPES = "openid profile email"
OIDC_RP_SIGN_ALGO = "ES256"
OIDC_OP_JWKS_ENDPOINT = "https://identity.dazr.eu/oauth/jwks"
OIDC_OP_AUTHORIZATION_ENDPOINT = "https://identity.dazr.eu/oauth/authorize"
OIDC_OP_TOKEN_ENDPOINT = "https://identity.dazr.eu/oauth/token"
OIDC_OP_USER_ENDPOINT = "https://identity.dazr.eu/oauth/userinfo"
OIDC_USE_PKCE = True
OIDC_PKCE_CODE_CHALLENGE_METHOD = "S256"

LOGIN_REDIRECT_URL = "/"
LOGOUT_REDIRECT_URL = "/"

# urls.py
from django.urls import include, path
urlpatterns += [path("oidc/", include("mozilla_django_oidc.urls"))]

# template: start the sign-in
# <a href="{% url 'oidc_authentication_init' %}">Sign in with Dazr Identity</a>

Standardmäßig ordnet das Backend Nutzer über die E-Mail zu. Um Konten über sub zu verknüpfen, leiten Sie von OIDCAuthenticationBackend ab und überschreiben filter_users_by_claims und create_user. Verwenden Sie eine aktuelle Version: Ältere Versionen können ES256-Signaturen nicht prüfen.

Node.js (openid-client)

openid-client (Version 6) liest das Discovery-Dokument und prüft PKCE, State, Nonce und das ID-Token für Sie.

import * as client from 'openid-client';

const config = await client.discovery(
  new URL('https://identity.dazr.eu'),
  process.env.DAZR_CLIENT_ID,
  process.env.DAZR_CLIENT_SECRET, // public client: pass undefined here and client.None() as the 4th argument
);
const redirect_uri = 'https://app.example.eu/auth/dazr/callback';

// 1. Start: keep the three random values in the user's session.
export async function start(session) {
  session.verifier = client.randomPKCECodeVerifier();
  session.state = client.randomState();
  session.nonce = client.randomNonce();
  return client.buildAuthorizationUrl(config, {
    redirect_uri,
    scope: 'openid profile email',
    code_challenge: await client.calculatePKCECodeChallenge(session.verifier),
    code_challenge_method: 'S256',
    state: session.state,
    nonce: session.nonce,
  });
}

// 2. Callback: pass the full callback URL (a URL object).
export async function callback(session, currentUrl) {
  const tokens = await client.authorizationCodeGrant(config, currentUrl, {
    pkceCodeVerifier: session.verifier,
    expectedState: session.state,
    expectedNonce: session.nonce,
  });
  const claims = tokens.claims(); // sub, email, name
  return { sub: claims.sub, email: claims.email, name: claims.name };
}

Jede OpenID-Connect-Bibliothek

Ist Ihr Stack nicht aufgeführt, funktioniert jeder OpenID-Connect-Client. Prüfen Sie diese Einstellungen:

Der Ablauf

  1. Erzeugen Sie einen zufälligen code_verifier (43 bis 128 Zeichen) und die zugehörige code_challenge = base64url(SHA-256(verifier)). Erzeugen Sie einen zufälligen state und nonce. Speichern Sie alle drei in der Sitzung des Nutzers.
  2. Leiten Sie zum Autorisierungsendpunkt weiter mit response_type=code, client_id, redirect_uri, scope (immer mit openid), state, nonce, code_challenge und code_challenge_method=S256.
  3. Die Person meldet sich bei Bedarf an und sieht den Zustimmungsbildschirm mit den echten Daten. Stimmt sie zu, kommt sie mit code, state und iss zu Ihrer Weiterleitungs-URI zurück. Prüfen Sie, dass state übereinstimmt und iss gleich https://identity.dazr.eu ist.
  4. Senden Sie auf Ihrem Server einen POST an den Token-Endpunkt mit grant_type=authorization_code, code, derselben redirect_uri und dem code_verifier.
  5. Verifizieren Sie das ID-Token: ES256-Signatur mit einem Schlüssel aus dem JWKS (passend zur kid), iss, aud = Ihre Client-ID, exp und Ihre nonce. Nutzen Sie sub als Kontoschlüssel.

Der Parameter prompt akzeptiert none (sofortige Antwort mit login_required oder consent_required, wenn die Person etwas tun müsste), login (die Person erneut anmelden lassen) und consent (den Zustimmungsbildschirm zeigen, auch wenn schon zugestimmt wurde). max_age wird unterstützt. Gespeicherte Zustimmung: Hat die Person dieselben Scopes schon genehmigt, geht es direkt zurück zu Ihrer App. Fragen Sie später nach mehr, zeigt der Zustimmungsbildschirm nur das Neue.

Scopes und Claims

Fordern Sie nur an, was Sie brauchen. Außerhalb dieser Tabelle gibt es nichts: keine Kontakte, keine Dateien, keine Passwörter.

ScopeClaims
openid (Pflicht)sub: eine zufällige ID für die Person, gleich für jede App Ihrer Organisation und anders für jede andere Organisation
profilename (falls festgelegt) und locale
emailemail und email_verified (immer true)
addressaddress (formatted, street_address, locality, region, postal_code, country), nur wenn die Person eine Adresse gespeichert hat
organisationsorganisations: die Organisationen, die die Person auf dem Zustimmungsbildschirm ankreuzt, jeweils mit id, name, country, register_number, vat, vat_verified (nur true, wenn der EU-Dienst VIES die USt-IdNr. bestätigt hat), verified, verification (siehe Verifizierte Organisationen) und role
offline_accesskeine Claims; Sie erhalten zusätzlich ein Refresh-Token

Claims stehen im ID-Token für die Scopes, die die Person genehmigt hat. Der Userinfo-Endpunkt liefert dieselben Angaben mit den aktuellen Werten.

Verifizierte Organisationen

Dazr verifiziert eine Organisation, wenn ihr Geschäftsführer oder eine andere zeichnungsberechtigte Person eine kurze Erklärung mit einer qualifizierten elektronischen Signatur (QES) unterschreibt. Das geht sofort und funktioniert in jedem EU-Land. Ohne qualifizierte Signatur kann der Vertreter stattdessen einen Handelsregisterauszug und einen Ausweis senden; Dazr prüft diese von Hand. Jede Organisation im Claim organisations enthält ihre aktuelle Verifizierung:

verification.statusBedeutung
unverifiedNicht verifiziert, oder eine Verifizierung ist fehlgeschlagen, abgelaufen oder wurde widerrufen (zum Beispiel nach einer Änderung des Firmennamens oder der Registernummer).
pendingDie Verifizierung läuft: Der Vertreter wurde um seine Unterschrift gebeten, oder Dazr prüft Dokumente.
verifiedVerifiziert. method ist qes, qes_org (das Zertifikat selbst nennt die Organisation, per USt-IdNr. oder Registernummer), documents oder extract, und verified_at ist eine Unix-Zeit.

Das Boolean verified bleibt aus Kompatibilitätsgründen und entspricht verification.status === 'verified'. Jede Organisation hat außerdem eine id, dieselbe wie in den Webhooks.

"organisations": [{
  "id": "org_3k9x2m0q8w1v5t7a", "name": "Conti Logistica S.r.l.", "country": "IT",
  "register_number": "REA MI1234567", "vat": "IT12345678903", "vat_verified": true,
  "verified": false, "verification": { "status": "pending", "method": "qes" }, "role": "owner"
}]

Verifizierte Organisation verlangen

Setzen Sie in der Konsole Verifizierte Organisation verlangen auf Verifizierung gestartet oder Verifiziert, oder verlangen Sie es pro Anfrage mit acr_values=urn:dazr:org:verification-started oder acr_values=urn:dazr:org:verified (die strengere Vorgabe gilt). Ihre App muss außerdem den Scope organisations anfragen.

Wer keine passende Organisation hat, durchläuft das in derselben Anmeldung: Name, Organisation (mit in VIES geprüfter USt-IdNr.), dann die Verifizierung. Die Person kann sofort unterschreiben, sich die Erklärung per E-Mail schicken lassen oder eine andere Person nennen, die unterschreibt. Danach kreuzt sie die Organisation auf dem Zustimmungsbildschirm an (nur passende Organisationen sind auswählbar) und kehrt zu Ihrer Weiterleitungs-URI zurück.

Muss noch jemand anderes unterschreiben, kommt die Person mit Status pending zurück, auch wenn Sie Verifiziert verlangt haben: Sie erhalten einen Code, der Claim sagt pending, und das acr des ID-Tokens ist urn:dazr:org:verification-started statt urn:dazr:org:verified. Entscheiden Sie immer anhand von verification.status, nie anhand einer erfolgreichen Anmeldung. Mit prompt=none erhalten Sie interaction_required, wenn die Vorgabe nicht erfüllt ist.

Änderungen der Einstellung gelten ab der nächsten Anmeldung. Bereits angemeldete Personen werden nicht abgemeldet: Refresh-Antworten, der Userinfo-Endpunkt und Webhooks enthalten immer den aktuellen Status, nutzen Sie diese also für bestehende Sitzungen.

Webhooks

Fügen Sie in der Konsole eine Webhook-URL (https) hinzu. Das Signatur-Secret sehen Sie einmal; Sie können es erneuern. Dazr sendet organisation.verification.updated, wenn eine Organisation, die einer Ihrer Nutzer mit Ihrer App geteilt hat, pending, verified, failed oder unverified wird. Apps, die eine Organisation nie per Zustimmung erhalten haben, erfahren nie davon.

POST /your/webhook
Content-Type: application/json
Dazr-Event: organisation.verification.updated
Dazr-Delivery: dlv_8Fq2…
Dazr-Signature: t=1791021600,v1=5e0c…

{ "id": "evt_…", "type": "organisation.verification.updated", "created": 1791021600,
  "data": { "organisation": { "id": "org_3k9x2m0q8w1v5t7a", "name": "Conti Logistica S.r.l.", "country": "IT" },
            "status": "verified", "method": "qes_org", "verified_at": 1791021598,
            "subjects": ["Xo1c…"] } }

status ist pending, verified, failed oder unverified. subjects sind die sub-Werte, in Ihrer App, der Personen, die diese Organisation mit ihr geteilt haben. Prüfen Sie die Signatur, bevor Sie dem Inhalt vertrauen:

import crypto from 'node:crypto';
// raw = the request body exactly as received (a string), header = the Dazr-Signature header
function verifyDazrSignature(raw, header, secret) {
  const m = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(header || '');
  if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false; // reject old deliveries
  const want = crypto.createHmac('sha256', secret).update(m[1] + '.' + raw).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(want), Buffer.from(m[2]));
}

Antworten Sie innerhalb von 8 Sekunden mit einem 2xx. Sonst versucht Dazr es nach etwa 5 Minuten, 30 Minuten, 2, 6, 12 und 24 Stunden erneut und markiert die Zustellung dann als fehlgeschlagen. Jeder Versuch hat dieselbe Dazr-Delivery-ID, ignorieren Sie also Duplikate. Die Konsole listet die letzten Zustellungen, kann eine erneut senden und ein Testereignis senden (webhook.test). Regelmäßiges Abfragen des Userinfo-Endpunkts funktioniert ebenfalls.

Tokens und Laufzeiten

TokenLaufzeit und Regeln
Autorisierungscode60 Sekunden, einmalig, gebunden an Ihren Client, die Weiterleitungs-URI und die PKCE-Challenge. Wird ein Code zweimal verwendet, werden die damit ausgestellten Tokens widerrufen.
Access-Token10 Minuten. Ein signiertes JWT (ES256, typ at+jwt) mit aud = Ihre Client-ID. Senden Sie es als Authorization: Bearer.
ID-Token10 Minuten. ES256, mit iss, sub, aud, exp, iat, auth_time, nonce und amr, soweit bekannt.
Refresh-TokenNur mit offline_access. Wird bei jeder Nutzung erneuert: Speichern Sie immer das neue. Wird ein altes Refresh-Token erneut gesendet, wird die gesamte Anmeldung widerrufen. Läuft nach 30 Tagen ohne Nutzung und spätestens nach 180 Tagen ab.

Signaturschlüssel werden gewechselt. Wählen Sie den Schlüssel immer anhand der kid aus dem JWKS und laden Sie das JWKS neu, wenn Sie eine unbekannte kid sehen.

Fehler

Solange Client-ID und Weiterleitungs-URI gültig sind, kommen Fehler an Ihre Weiterleitungs-URI zurück als error, error_description, state und iss: zum Beispiel invalid_request (etwa fehlendes oder plain PKCE), invalid_scope, unsupported_response_type, access_denied (die Person hat abgebrochen, oder die App ist im Testmodus und die Person ist kein Mitglied), login_required, consent_required und interaction_required (mit prompt=none, wenn eine verlangte verifizierte Organisation fehlt). Bei einem unbekannten Client oder einer nicht registrierten Weiterleitungs-URI zeigt Dazr eine Fehlerseite und leitet nie weiter. Der Token-Endpunkt antwortet mit JSON-Fehlern wie invalid_client, invalid_grant und unsupported_grant_type.

Abmeldung

Um jemanden abzumelden, schicken Sie die Person zum Abmeldeendpunkt mit id_token_hint (oder client_id), einer optionalen, in den App-Einstellungen registrierten post_logout_redirect_uri und state. Dazr fragt, ob sie sich auf diesem Gerät auch von Dazr Identity abmelden möchte, und schickt sie dann zurück. Widerrufen Sie nicht mehr benötigte Refresh-Tokens am Widerrufsendpunkt.

Testen und live gehen

Eine neue App ist im Testmodus: Nur Mitglieder Ihrer Organisation können sich anmelden, und der Zustimmungsbildschirm weist darauf hin. Um live zu gehen, lassen Sie Ihre Organisation in Dazr Identity verifizieren, fügen eine https://-Weiterleitungs-URI hinzu und beantragen in der Konsole eine Prüfung. Ändern Sie Name, Zweck, Datenschutzerklärung oder Daten einer Live-App, ist eine neue Prüfung nötig.

Der Button

Verwenden Sie den Text „Sign in with Dazr Identity“ und das Dazr-Logo wie unten. Sie dürfen die Größe ändern, nicht das Logo, die Farben oder den Text. Verlinken Sie ihn auf Ihre eigene Route, die den Ablauf startet.

<a href="/auth/dazr" style="display:inline-flex;align-items:center;gap:10px;height:44px;padding:0 18px;border-radius:22px;background:#18203a;color:#fff;font:600 15px/1 system-ui,sans-serif;text-decoration:none">
  <svg width="20" height="20" viewBox="0 0 100 100" aria-hidden="true"><path d="M50 4 89 16v31c0 25.5-16.8 42.5-39 50C27.8 89.5 11 72.5 11 47V16z" fill="#fff"/><path d="M50 15.5 79 24.5v22.8c0 19.5-12.4 32.6-29 38.7C33.4 79.9 21 66.8 21 47.3V24.5z" fill="#18203a"/><path d="M50 24 71 30.6v16.9c0 14.5-9 24.4-21 29.2-12-4.8-21-14.7-21-29.2V30.6z" fill="#fff"/></svg>
  Sign in with Dazr Identity
</a>

Ein minimales Beispiel in Node.js

Node 18 oder neuer, ohne Abhängigkeiten. Eine Webserver-App mit Client-Secret; bei einem öffentlichen Client entfällt der Authorization-Header und Sie senden client_id im Body.

import crypto from 'node:crypto';
const issuer = 'https://identity.dazr.eu';
const clientId = process.env.DAZR_CLIENT_ID;
const clientSecret = process.env.DAZR_CLIENT_SECRET;
const redirectUri = 'https://app.example.eu/auth/dazr/callback';
const b64url = (buf) => Buffer.from(buf).toString('base64url');

// 1. Start: keep verifier, state and nonce in the user's session.
export function start(session) {
  session.verifier = b64url(crypto.randomBytes(32));
  session.state = b64url(crypto.randomBytes(16));
  session.nonce = b64url(crypto.randomBytes(16));
  const challenge = b64url(crypto.createHash('sha256').update(session.verifier).digest());
  return issuer + '/oauth/authorize?' + new URLSearchParams({
    response_type: 'code', client_id: clientId, redirect_uri: redirectUri,
    scope: 'openid profile email', state: session.state, nonce: session.nonce,
    code_challenge: challenge, code_challenge_method: 'S256',
  });
}

// 2. Callback: check state, exchange the code, verify the ID token.
export async function callback(session, query) {
  if (query.error) throw new Error('sign-in failed: ' + query.error);
  if (query.state !== session.state || query.iss !== issuer) throw new Error('state or issuer mismatch');
  const res = await fetch(issuer + '/oauth/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      Authorization: 'Basic ' + Buffer.from(clientId + ':' + clientSecret).toString('base64'),
    },
    body: new URLSearchParams({ grant_type: 'authorization_code', code: query.code, redirect_uri: redirectUri, code_verifier: session.verifier }),
  });
  const tokens = await res.json();
  if (!res.ok) throw new Error(tokens.error);
  const claims = await verifyIdToken(tokens.id_token, session.nonce);
  return { sub: claims.sub, email: claims.email, name: claims.name, tokens };
}

async function verifyIdToken(idToken, nonce) {
  const [h, p, s] = idToken.split('.');
  const header = JSON.parse(Buffer.from(h, 'base64url'));
  const { keys } = await (await fetch(issuer + '/oauth/jwks')).json();
  const jwk = keys.find((k) => k.kid === header.kid);
  if (!jwk || header.alg !== 'ES256') throw new Error('unknown signing key');
  const ok = crypto.verify('sha256', Buffer.from(h + '.' + p),
    { key: crypto.createPublicKey({ key: jwk, format: 'jwk' }), dsaEncoding: 'ieee-p1363' }, Buffer.from(s, 'base64url'));
  const c = JSON.parse(Buffer.from(p, 'base64url'));
  const now = Math.floor(Date.now() / 1000);
  if (!ok || c.iss !== issuer || c.aud !== clientId || c.exp < now - 30 || c.nonce !== nonce) throw new Error('invalid ID token');
  return c;
}

Limits

Der Autorisierungs- und der Token-Endpunkt sind begrenzt. Erhalten Sie HTTP 429, warten Sie und versuchen Sie es erneut; wiederholen Sie es nicht in einer engen Schleife.

Ihre Pflichten

Ihre Organisation ist eigenständiger Verantwortlicher für die Daten, die sie erhält. Die Entwicklerbedingungen für Dazr Identity legen fest, was Sie damit tun dürfen, wie Sie sie schützen und wie Sie Datenpannen melden. Fragen: hello@dazr.eu.