Sign in with Dazr Identity: documentatie voor ontwikkelaars

Mensen kunnen in Dazr Identity al hun Google- of Microsoft-account gebruiken, dus je app heeft er geen aparte Google- en Microsoft-knoppen naast nodig: één knop in plaats van drie.

Discovery en endpoints

Alles staat in het discovery-document. Wijs je bibliotheek naar de issuer en ze vindt de rest.

WatAdres
Issuerhttps://identity.dazr.eu
Discoveryhttps://identity.dazr.eu/.well-known/openid-configuration
Autorisatiehttps://identity.dazr.eu/oauth/authorize
Tokenhttps://identity.dazr.eu/oauth/token
Gebruikersinfohttps://identity.dazr.eu/oauth/userinfo
Publieke sleutels (JWKS)https://identity.dazr.eu/oauth/jwks
Intrekking (RFC 7009)https://identity.dazr.eu/oauth/revoke
Introspectie (RFC 7662), alleen voor webserver-appshttps://identity.dazr.eu/oauth/introspect
Uitloggen (RP-initiated logout)https://identity.dazr.eu/oauth/logout

Een app registreren

Integratiehandleidingen

Elke stack hieronder gebruikt zijn eigen standaardondersteuning voor OpenID Connect. Je hebt drie waarden uit de console nodig: de client-ID, het client secret (alleen voor webserver-apps) en de redirect-URI die je hebt geregistreerd. De rest komt uit het discovery-document.

Auth.js / NextAuth

Auth.js (NextAuth.js v5) accepteert een eigen OpenID Connect-providerobject. Registreer een webserver-app, stel AUTH_SECRET, AUTH_DAZR_ID en AUTH_DAZR_SECRET in en gebruik deze callback-URL als redirect-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 werkt op dezelfde manier, met type: "oauth" en de 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

Voeg met het bestand auth.ts hierboven de route handler toe en een inlogknop die een server action uitvoert.

// 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>
  )
}

Geef de knop de stijl uit De knop. In callbacks is de Dazr-sub gelijk aan account.providerAccountId.

WordPress

Gebruik een generieke OpenID Connect-clientplugin, zoals OpenID Connect Generic Client. Registreer een webserver-app en vul de plugininstellingen in met de waarden hieronder. De plugin toont de te registreren redirect-URI op zijn instellingenpagina; standaard is dat https://example.com/wp-admin/admin-ajax.php?action=openid-connect-authorize.

InstellingWaarde
Client-IDje client-ID
Client secretje client secret
Scopeopenid profile email
Login-endpoint (autorisatie)https://identity.dazr.eu/oauth/authorize
Token-endpointhttps://identity.dazr.eu/oauth/token
Userinfo-endpointhttps://identity.dazr.eu/oauth/userinfo
Uitlog-endpoint (end session)https://identity.dazr.eu/oauth/logout
Identity key (identiteitssleutel)sub
PKCEaan (S256)

De namen van de instellingen verschillen per plugin. Heeft een plugin geen PKCE-optie, kies dan een andere: Dazr Identity weigert inlogpogingen zonder PKCE.

Laravel

Laravel Socialite heeft geen ingebouwde generieke OpenID Connect-driver, dus voeg je een kleine eigen driver toe. Die stuurt mensen met PKCE naar Dazr en leest de gebruiker uit het userinfo-endpoint. Socialite leest het discovery-document niet, daarom staan de endpoints uitgeschreven. Registreer een webserver-app met de redirect-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 heeft geen generieke OpenID Connect-provider die je op een willekeurige issuer kunt richten: de inlogproviders en de third-party-auth-integraties zijn vaste lijsten. Je kunt Dazr Identity dus nu niet toevoegen in het Supabase-dashboard. Voegt Supabase generieke OpenID Connect-ondersteuning toe, gebruik dan de waarden uit de checklist hieronder.

Het alternatief: laat mensen inloggen met Dazr Identity op je eigen server (met een van de handleidingen op deze pagina), sla de Dazr-sub op in je gebruikerstabel en praat vanaf die server met Supabase met de service role key, waarbij je de toegang in je eigen code controleert. Stuur de service role key nooit naar een browser.

Django

Gebruik mozilla-django-oidc. Registreer een webserver-app met de redirect-URI https://app.example.eu/oidc/callback/. De bibliotheek leest het discovery-document niet, daarom staan de endpoints uitgeschreven.

# 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>

Standaard koppelt de backend gebruikers op e-mailadres. Om accounts aan sub te koppelen, maak je een subklasse van OIDCAuthenticationBackend en overschrijf je filter_users_by_claims en create_user. Gebruik een actuele versie: oudere versies kunnen ES256-handtekeningen niet controleren.

Node.js (openid-client)

openid-client (versie 6) leest het discovery-document en controleert PKCE, state, nonce en het ID-token voor je.

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 };
}

Elke OpenID Connect-bibliotheek

Staat je stack er niet bij, dan werkt elke OpenID Connect-client. Controleer deze instellingen:

De flow

  1. Maak een willekeurige code_verifier (43 tot 128 tekens) en de bijbehorende code_challenge = base64url(SHA-256(verifier)). Maak een willekeurige state en nonce. Bewaar alle drie in de sessie van de gebruiker.
  2. Stuur door naar het authorization-endpoint met response_type=code, client_id, redirect_uri, scope (altijd met openid), state, nonce, code_challenge en code_challenge_method=S256.
  3. De persoon logt zo nodig in en ziet het toestemmingsscherm met de echte gegevens. Keurt hij of zij het goed, dan komt de persoon terug op je redirect-URI met code, state en iss. Controleer dat state klopt en dat iss gelijk is aan https://identity.dazr.eu.
  4. Doe op je server een POST naar het token-endpoint met grant_type=authorization_code, code, dezelfde redirect_uri en de code_verifier.
  5. Verifieer het ID-token: ES256-handtekening met een sleutel uit de JWKS (op basis van de kid), iss, aud = je client-ID, exp en je nonce. Gebruik sub als sleutel van het account.

De parameter prompt accepteert none (meteen antwoorden met login_required of consent_required als de persoon iets zou moeten doen), login (de persoon opnieuw laten inloggen) en consent (het toestemmingsscherm tonen, ook als er eerder toestemming was). max_age wordt ondersteund. Onthouden toestemming: heeft de persoon dezelfde scopes eerder goedgekeurd, dan gaat hij of zij meteen terug naar je app. Vraag je later om meer, dan toont het toestemmingsscherm alleen wat nieuw is.

Scopes en claims

Vraag alleen om wat je nodig hebt. Buiten deze tabel bestaat niets: geen contacten, geen bestanden, geen wachtwoorden.

ScopeClaims
openid (verplicht)sub: een willekeurig ID voor de persoon, gelijk voor elke app van je organisatie en anders voor elke andere organisatie
profilename (indien ingevuld) en locale
emailemail en email_verified (altijd true)
addressaddress (formatted, street_address, locality, region, postal_code, country), alleen als de persoon een adres heeft opgeslagen
organisationsorganisations: de organisaties die de persoon op het toestemmingsscherm aanvinkt, elk met id, name, country, register_number, vat, vat_verified (alleen true als de EU-dienst VIES het btw-nummer heeft bevestigd), verified, verification (zie Geverifieerde organisaties) en role
offline_accessgeen claims; je krijgt ook een refresh token

Claims staan in het ID-token voor de scopes die de persoon heeft goedgekeurd. Het userinfo-endpoint geeft dezelfde set terug, met de actuele waarden.

Geverifieerde organisaties

Dazr verifieert een organisatie wanneer de bestuurder, of een andere tekenbevoegde, een korte verklaring ondertekent met een gekwalificeerde elektronische handtekening (QES). Dat gaat direct en werkt in elk EU-land. Zonder gekwalificeerde handtekening kan de vertegenwoordiger in plaats daarvan een uittreksel uit het handelsregister en een identiteitsbewijs sturen; Dazr beoordeelt die handmatig. Elke organisatie in de claim organisations bevat haar actuele verificatie:

verification.statusBetekenis
unverifiedNiet geverifieerd, of een verificatie is mislukt, verlopen of ingetrokken (bijvoorbeeld nadat de statutaire naam of het registratienummer is gewijzigd).
pendingDe verificatie loopt: de vertegenwoordiger is gevraagd te ondertekenen, of Dazr beoordeelt documenten.
verifiedGeverifieerd. method is qes, qes_org (het certificaat noemt de organisatie zelf, via btw- of registratienummer), documents of extract, en verified_at is een Unix-tijd.

De boolean verified blijft bestaan voor compatibiliteit en is gelijk aan verification.status === 'verified'. Elke organisatie heeft ook een id, dezelfde die webhooks gebruiken.

"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"
}]

Geverifieerde organisatie vereisen

Zet in de console Geverifieerde organisatie vereisen op Verificatie gestart of Geverifieerd, of vraag het per verzoek met acr_values=urn:dazr:org:verification-started of acr_values=urn:dazr:org:verified (de strengste van de twee geldt). Je app moet ook de scope organisations aanvragen.

Wie geen geschikte organisatie heeft, doorloopt dat in dezelfde inlog: de naam, de organisatie (met het btw-nummer gecontroleerd in VIES) en dan de verificatie. Diegene kan direct ondertekenen, de verklaring naar zichzelf laten mailen of iemand anders noemen die tekent. Daarna vinkt diegene de organisatie aan op het toestemmingsscherm (alleen geschikte organisaties zijn aan te vinken) en komt terug op je redirect-URI.

Moet iemand anders nog tekenen, dan komt de persoon terug met status pending, ook als je om Geverifieerd vroeg: je ontvangt een code, de claim zegt pending en de acr van het ID-token is urn:dazr:org:verification-started in plaats van urn:dazr:org:verified. Baseer toegang altijd op verification.status, nooit op het feit dat inloggen lukte. Met prompt=none krijg je interaction_required als niet aan de eis wordt voldaan.

Wijzigingen van de instelling gelden vanaf de volgende inlog. Wie al is ingelogd, wordt niet uitgelogd: refresh-antwoorden, het userinfo-endpoint en webhooks bevatten altijd de actuele status, dus gebruik die om te reageren op bestaande sessies.

Webhooks

Voeg in de console een webhook-URL (https) toe. Je ziet het ondertekensecret één keer; je kunt het vervangen. Dazr stuurt organisation.verification.updated wanneer een organisatie die een van je gebruikers met je app deelde pending, verified, failed of unverified wordt. Apps die een organisatie nooit via toestemming ontvingen, horen er nooit iets over.

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 is pending, verified, failed of unverified. subjects zijn de sub-waarden, bij jouw app, van de mensen die deze organisatie ermee deelden. Controleer de handtekening voordat je de inhoud vertrouwt:

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]));
}

Antwoord binnen 8 seconden met een 2xx. Anders probeert Dazr het opnieuw na ongeveer 5 minuten, 30 minuten, 2, 6, 12 en 24 uur, en markeert de aflevering daarna als mislukt. Elke poging heeft hetzelfde Dazr-Delivery-ID, dus negeer dubbele. De console toont recente afleveringen, kan er een opnieuw sturen en kan een testevent sturen (webhook.test). Het userinfo-endpoint pollen werkt ook.

Tokens en geldigheid

TokenGeldigheid en regels
Autorisatiecode60 seconden, eenmalig, gekoppeld aan je client, redirect-URI en PKCE-challenge. Een code die twee keer wordt gebruikt, trekt de tokens in die ermee zijn uitgegeven.
Access token10 minuten. Een ondertekende JWT (ES256, typ at+jwt) met aud = je client-ID. Stuur hem mee als Authorization: Bearer.
ID-token10 minuten. ES256, met iss, sub, aud, exp, iat, auth_time, nonce en amr waar bekend.
Refresh tokenAlleen met offline_access. Wordt bij elk gebruik vervangen: sla altijd de nieuwe op. Wie een oude refresh token opnieuw stuurt, trekt de hele inlogsessie in. Verloopt na 30 dagen zonder gebruik en na hooguit 180 dagen.

Ondertekeningssleutels worden vervangen. Kies de sleutel altijd op kid uit de JWKS en haal de JWKS opnieuw op als je een onbekende kid ziet.

Fouten

Zolang je client-ID en redirect-URI geldig zijn, komen fouten terug op je redirect-URI als error, error_description, state en iss: bijvoorbeeld invalid_request (zoals ontbrekende of plain PKCE), invalid_scope, unsupported_response_type, access_denied (de persoon heeft geannuleerd, of de app is in de testfase en de persoon is geen lid), login_required, consent_required en interaction_required (met prompt=none, als een vereiste geverifieerde organisatie ontbreekt). Bij een onbekende client of een niet-geregistreerde redirect-URI toont Dazr een foutpagina en stuurt nooit door. Het token-endpoint antwoordt met JSON-fouten zoals invalid_client, invalid_grant en unsupported_grant_type.

Uitloggen

Om iemand uit te loggen, stuur je de persoon naar het logout-endpoint met id_token_hint (of client_id), een optionele post_logout_redirect_uri die in je app-instellingen staat, en state. Dazr vraagt of de persoon ook op dat apparaat wil uitloggen bij Dazr Identity en stuurt hem of haar daarna terug. Trek refresh tokens die je niet meer nodig hebt in via het revocation-endpoint.

Testen en live gaan

Een nieuwe app staat in de testfase: alleen leden van je organisatie kunnen inloggen, en het toestemmingsscherm vermeldt dat. Om live te gaan, laat je je organisatie verifiëren in Dazr Identity, voeg je een https://-redirect-URI toe en vraag je in de console een beoordeling aan. Wijzig je de naam, het doel, de privacyverklaring of de gegevens van een live app, dan is een nieuwe beoordeling nodig.

De knop

Gebruik de tekst "Sign in with Dazr Identity" en het Dazr-logo zoals hieronder. Je mag de grootte aanpassen, niet het logo, de kleuren of de tekst. Link de knop naar je eigen route die de flow start.

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

Een minimaal voorbeeld in Node.js

Node 18 of nieuwer, zonder afhankelijkheden. Een webserver-app met een clientgeheim; laat bij een public client de Authorization-header weg en stuur client_id mee in de 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;
}

Limieten

Op de authorization- en token-endpoints zit een limiet. Krijg je HTTP 429, wacht dan en probeer het opnieuw; probeer het niet in een snelle lus.

Je verplichtingen

Je organisatie is zelfstandig verwerkingsverantwoordelijke voor de gegevens die ze ontvangt. De Ontwikkelaarsvoorwaarden Dazr Identity beschrijven wat je ermee mag doen, hoe je ze beveiligt en hoe je datalekken meldt. Vragen: hello@dazr.eu.