Sign in with Dazr Identity: documentazione per sviluppatori
OpenID Connect 1.0 standard con il flusso authorization code e PKCE. Nessun SDK necessario: funziona qualsiasi libreria OpenID Connect certificata, e anche il codice scritto a mano.
Le persone possono già usare il loro account Google o Microsoft dentro Dazr Identity, quindi alla tua app non servono pulsanti Google e Microsoft separati accanto: un pulsante invece di tre.
Discovery ed endpoint
Tutto è descritto nel documento di discovery. Indica l’issuer alla tua libreria e lei troverà il resto.
| Cosa | Indirizzo |
|---|---|
| Issuer | https://identity.dazr.eu |
| Discovery | https://identity.dazr.eu/.well-known/openid-configuration |
| Autorizzazione | https://identity.dazr.eu/oauth/authorize |
| Token | https://identity.dazr.eu/oauth/token |
| Informazioni utente | https://identity.dazr.eu/oauth/userinfo |
| Chiavi pubbliche (JWKS) | https://identity.dazr.eu/oauth/jwks |
| Revoca (RFC 7009) | https://identity.dazr.eu/oauth/revoke |
| Introspezione (RFC 7662), solo app web server | https://identity.dazr.eu/oauth/introspect |
| Disconnessione (RP-initiated logout) | https://identity.dazr.eu/oauth/logout |
Registrare un’app
- Le app appartengono a un’organizzazione. I proprietari e gli amministratori dell’organizzazione le registrano in Sviluppatori.
- Le app web server (client confidenziali) ricevono un client secret, mostrato una sola volta. Autenticati all’endpoint dei token con HTTP Basic (
client_secret_basic) o nel corpo del modulo (client_secret_post). - Le app single-page e mobili (client pubblici) non ricevono alcun secret. Inviano
client_ide si affidano a PKCE. - Gli URI di reindirizzamento devono corrispondere esattamente e iniziare con
https://. Finché l’app è in fase di test funzionano anchehttp://localhostehttp://127.0.0.1su qualsiasi porta. - Sono obbligatori l’indirizzo dell’informativa privacy, un’email di supporto e una finalità di una riga: le persone li vedono nella schermata di consenso.
Guide all’integrazione
Ogni stack qui sotto usa il proprio supporto standard per OpenID Connect. Ti servono tre valori dalla console: il client ID, il client secret (solo per le app web server) e l’URI di reindirizzamento che hai registrato. Il resto arriva dal documento di discovery.
- Auth.js / NextAuth
- Next.js App Router
- WordPress
- Laravel
- Supabase
- Django
- Node.js (openid-client)
- Qualsiasi libreria OpenID Connect
sub è pairwise: lo stesso per ogni app della tua organizzazione, diverso per ogni altra organizzazione. Per una persona non cambia mai, mentre un indirizzo email può cambiare. Salva sub come chiave dell’account e tratta email come dato di contatto.
Auth.js / NextAuth
Auth.js (NextAuth.js v5) accetta un oggetto provider OpenID Connect personalizzato. Registra un’app web server, imposta AUTH_SECRET, AUTH_DAZR_ID e AUTH_DAZR_SECRET e usa questo URL di callback come URI di reindirizzamento: 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 funziona allo stesso modo, con type: "oauth" e l’URL di discovery:
// 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
Con il file auth.ts qui sopra, aggiungi il route handler e un pulsante di accesso che esegue una server action.
// 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>
)
}
Dai al pulsante lo stile mostrato in Il pulsante. Nelle callback, il sub di Dazr è account.providerAccountId.
WordPress
Usa un plugin client OpenID Connect generico, come OpenID Connect Generic Client. Registra un’app web server e compila le impostazioni del plugin con i valori qui sotto. Il plugin mostra l’URI di reindirizzamento da registrare nella sua pagina delle impostazioni; di default è https://example.com/wp-admin/admin-ajax.php?action=openid-connect-authorize.
| Impostazione | Valore |
|---|---|
| Client ID | il tuo client ID |
| Client secret | il tuo client secret |
| Scope | openid profile email |
| Endpoint di login (autorizzazione) | https://identity.dazr.eu/oauth/authorize |
| Endpoint dei token | https://identity.dazr.eu/oauth/token |
| Endpoint userinfo | https://identity.dazr.eu/oauth/userinfo |
| Endpoint di fine sessione | https://identity.dazr.eu/oauth/logout |
| Identity key (chiave d’identità) | sub |
| PKCE | attivo (S256) |
I nomi delle impostazioni variano un po’ da plugin a plugin. Se un plugin non ha l’opzione PKCE, scegline un altro: Dazr Identity rifiuta gli accessi senza PKCE.
Laravel
Laravel Socialite non ha un driver OpenID Connect generico integrato, quindi aggiungi un piccolo driver personalizzato. Manda le persone a Dazr con PKCE e legge l’utente dall’endpoint userinfo. Socialite non legge il documento di discovery, per questo gli endpoint sono scritti per esteso. Registra un’app web server con l’URI di reindirizzamento 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 non ha un provider OpenID Connect generico da puntare su un issuer qualsiasi: i provider di accesso e le integrazioni di autenticazione di terze parti sono elenchi fissi. Quindi oggi non puoi aggiungere Dazr Identity nella dashboard di Supabase. Se Supabase aggiungerà il supporto OpenID Connect generico, usa i valori della checklist qui sotto.
L’alternativa: fai accedere le persone con Dazr Identity sul tuo server (con una delle guide di questa pagina), salva il sub di Dazr nella tabella degli utenti e comunica con Supabase da quel server con la service role key, controllando gli accessi nel tuo codice. Non inviare mai la service role key a un browser.
Django
Usa mozilla-django-oidc. Registra un’app web server con l’URI di reindirizzamento https://app.example.eu/oidc/callback/. La libreria non legge il documento di discovery, per questo gli endpoint sono scritti per esteso.
# 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>
Di default il backend abbina gli utenti per email. Per collegare gli account a sub, crea una sottoclasse di OIDCAuthenticationBackend e sovrascrivi filter_users_by_claims e create_user. Usa una versione recente: le versioni più vecchie non verificano le firme ES256.
Node.js (openid-client)
openid-client (versione 6) legge il documento di discovery e verifica per te PKCE, state, nonce e l’ID token.
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 };
}
Qualsiasi libreria OpenID Connect
Se il tuo stack non è nell’elenco, funziona qualsiasi client OpenID Connect. Controlla queste impostazioni:
- Issuer
https://identity.dazr.eu, URL di discoveryhttps://identity.dazr.eu/.well-known/openid-configuration. - Response type
code(authorization code flow). Niente flusso implicit o hybrid. - PKCE è obbligatorio, con
code_challenge_method=S256. - Scope:
openidpiù solo ciò che ti serve traprofile,email,address,organisationseoffline_access. La tua app deve essere registrata per ogni scope che chiede. - URI di reindirizzamento: esattamente come registrato nella console, compresi il percorso e l’eventuale query string.
- Autenticazione del client:
client_secret_basicoclient_secret_postper le app web server,noneper le app single-page e mobile. - Firma dell’ID token: ES256. Alcune librerie presumono RS256: imposta l’algoritmo su ES256.
- Usa
subcome chiave dell’account. È pairwise, per organizzazione. - Con
offline_access, salva il nuovo refresh token dopo ogni refresh: ruotano.
Il flusso
- Crea un
code_verifiercasuale (da 43 a 128 caratteri) e la suacode_challenge= base64url(SHA-256(verifier)). Crea unostatee unnoncecasuali. Conserva tutti e tre nella sessione dell’utente. - Reindirizza all’endpoint di autorizzazione con
response_type=code,client_id,redirect_uri,scope(sempre conopenid),state,nonce,code_challengeecode_challenge_method=S256. - La persona accede se necessario e vede la schermata di consenso con i dati reali. Se approva, torna al tuo URI di reindirizzamento con
code,stateeiss. Controlla chestatecorrisponda e cheisssiahttps://identity.dazr.eu. - Sul tuo server, invia un POST all’endpoint dei token con
grant_type=authorization_code,code, lo stessoredirect_urie ilcode_verifier. - Verifica l’ID token: firma ES256 con una chiave del JWKS (in base al
kid),iss,aud= il tuo client ID,expe il tuononce. Usasubcome chiave dell’account.
Il parametro prompt accetta none (risposta immediata con login_required o consent_required se la persona dovrebbe intervenire), login (chiedere alla persona di accedere di nuovo) e consent (mostrare la schermata di consenso anche se era già stata approvata). max_age è supportato. Consenso memorizzato: se la persona ha già approvato gli stessi scope, torna subito alla tua app. Se in seguito chiedi di più, la schermata di consenso mostra solo le novità.
Scope e claim
Chiedi solo ciò che ti serve. Al di fuori di questa tabella non esiste nulla: niente contatti, niente file, niente password.
| Scope | Claim |
|---|---|
openid (obbligatorio) | sub: un ID casuale per la persona, uguale per ogni app della tua organizzazione e diverso per ogni altra organizzazione |
profile | name (se impostato) e locale |
email | email ed email_verified (sempre true) |
address | address (formatted, street_address, locality, region, postal_code, country), solo se la persona ne ha salvato uno |
organisations | organisations: le organizzazioni che la persona seleziona nella schermata di consenso, ciascuna con id, name, country, register_number, vat, vat_verified (true solo se il servizio UE VIES ha confermato la partita IVA), verified, verification (vedi Organizzazioni verificate) e role |
offline_access | nessun claim; ricevi anche un refresh token |
I claim compaiono nell’ID token per gli scope approvati dalla persona. L’endpoint delle informazioni utente restituisce lo stesso insieme, con i valori attuali.
Organizzazioni verificate
Dazr verifica un’organizzazione quando il suo amministratore, o un’altra persona che può firmare per essa, firma una breve dichiarazione con una firma elettronica qualificata (QES). È immediato e funziona in tutti i paesi UE. Senza firma qualificata, il rappresentante può inviare invece una visura camerale e un documento d’identità; Dazr li controlla a mano. Ogni organizzazione nella claim organisations riporta la sua verifica attuale:
| verification.status | Significato |
|---|---|
unverified | Non verificata, oppure una verifica non è riuscita, è scaduta o è stata revocata (per esempio dopo una modifica della ragione sociale o del numero di registro). |
pending | La verifica è in corso: al rappresentante è stato chiesto di firmare, oppure Dazr sta controllando i documenti. |
verified | Verificata. method è qes, qes_org (il certificato stesso indica l’organizzazione, tramite partita IVA o numero di registro), documents o extract, e verified_at è un tempo Unix. |
Il booleano verified resta per compatibilità ed è uguale a verification.status === 'verified'. Ogni organizzazione ha anche un id, lo stesso usato dai webhook.
"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"
}]
Richiedere un’organizzazione verificata
Nella console imposta Richiedi un’organizzazione verificata su Verifica avviata o Verificata, oppure chiedilo per singola richiesta con acr_values=urn:dazr:org:verification-started o acr_values=urn:dazr:org:verified (vale la più rigorosa delle due). La tua app deve anche richiedere lo scope organisations.
Chi non ha un’organizzazione idonea la completa nello stesso accesso: il nome, l’organizzazione (con la partita IVA controllata in VIES), poi la verifica. Può firmare subito, farsi inviare la dichiarazione per email o indicare un’altra persona che firma. Poi seleziona l’organizzazione nella schermata di consenso (si possono selezionare solo organizzazioni idonee) e torna al tuo URI di reindirizzamento.
Se deve ancora firmare qualcun altro, la persona torna con stato pending, anche se hai chiesto Verificata: ricevi un codice, la claim dice pending e l’acr dell’ID token è urn:dazr:org:verification-started invece di urn:dazr:org:verified. Decidi sempre in base a verification.status, mai sul fatto che l’accesso sia riuscito. Con prompt=none ricevi interaction_required quando il requisito non è soddisfatto.
Le modifiche all’impostazione valgono dal prossimo accesso. Chi ha già effettuato l’accesso non viene disconnesso: le risposte di refresh, l’endpoint userinfo e i webhook riportano sempre lo stato attuale, quindi usali per reagire alle sessioni esistenti.
Webhook
Aggiungi un URL del webhook (https) nella console. Vedi il segreto di firma una sola volta; puoi sostituirlo. Dazr invia organisation.verification.updated quando un’organizzazione che uno dei tuoi utenti ha condiviso con la tua app diventa pending, verified, failed o unverified. Le app che non hanno mai ricevuto un’organizzazione tramite consenso non ne sanno nulla.
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 è pending, verified, failed o unverified. subjects sono i valori sub, nella tua app, delle persone che hanno condiviso questa organizzazione con essa. Controlla la firma prima di fidarti del contenuto:
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]));
}
Rispondi con un 2xx entro 8 secondi. Altrimenti Dazr riprova dopo circa 5 minuti, 30 minuti, 2, 6, 12 e 24 ore, poi segna la consegna come non riuscita. Ogni tentativo ha lo stesso ID Dazr-Delivery, quindi ignora i duplicati. La console elenca le consegne recenti, può reinviarne una e può inviare un evento di prova (webhook.test). Funziona anche interrogare periodicamente l’endpoint userinfo.
Token e durata
| Token | Durata e regole |
|---|---|
| Codice di autorizzazione | 60 secondi, monouso, legato al tuo client, all’URI di reindirizzamento e alla challenge PKCE. Usare un codice due volte revoca i token che ha generato. |
| Access token | 10 minuti. Un JWT firmato (ES256, typ at+jwt) con aud = il tuo client ID. Invialo come Authorization: Bearer. |
| ID token | 10 minuti. ES256, con iss, sub, aud, exp, iat, auth_time, nonce e amr quando noto. |
| Refresh token | Solo con offline_access. Cambia a ogni utilizzo: salva sempre quello nuovo. Inviare di nuovo un vecchio refresh token revoca l’intero accesso. Scade dopo 30 giorni di inutilizzo e al massimo dopo 180 giorni. |
Le chiavi di firma vengono sostituite. Scegli sempre la chiave in base al kid dal JWKS e ricarica il JWKS quando trovi un kid sconosciuto.
Errori
Se client ID e URI di reindirizzamento sono validi, gli errori tornano al tuo URI di reindirizzamento come error, error_description, state e iss: per esempio invalid_request (come PKCE mancante o plain), invalid_scope, unsupported_response_type, access_denied (la persona ha annullato, oppure l’app è in test e la persona non è un membro), login_required, consent_required e interaction_required (con prompt=none, quando manca un’organizzazione verificata richiesta). Con un client sconosciuto o un URI di reindirizzamento non registrato, Dazr mostra una pagina di errore e non reindirizza mai. L’endpoint dei token risponde con errori JSON come invalid_client, invalid_grant e unsupported_grant_type.
Disconnessione
Per disconnettere qualcuno, invialo all’endpoint di disconnessione con id_token_hint (o client_id), un eventuale post_logout_redirect_uri registrato nelle impostazioni dell’app e state. Dazr chiede se vuole uscire anche da Dazr Identity su quel dispositivo, poi lo rimanda indietro. Revoca all’endpoint di revoca i refresh token che non ti servono più.
Test e messa online
Una nuova app è in fase di test: possono accedere solo i membri della tua organizzazione e la schermata di consenso lo indica. Per andare online, fai verificare la tua organizzazione in Dazr Identity, aggiungi un URI di reindirizzamento https:// e richiedi una revisione nella console. Modificare nome, finalità, informativa privacy o dati di un’app online richiede una nuova revisione.
Il pulsante
Usa il testo "Sign in with Dazr Identity" e il logo Dazr come qui sotto. Puoi modificarne le dimensioni, non il logo, i colori o il testo. Collegalo a una tua route che avvia il flusso.
<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>
Un esempio minimo in Node.js
Node 18 o successivo, senza dipendenze. Un’app web server con client secret; per un client pubblico, togli l’header Authorization e invia client_id nel corpo.
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;
}
Limiti
Gli endpoint di autorizzazione e dei token hanno un limite di richieste. Se ricevi HTTP 429, attendi e riprova; non riprovare in un ciclo continuo.
I tuoi obblighi
La tua organizzazione è titolare autonomo del trattamento dei dati che riceve. Le Condizioni per sviluppatori di Dazr Identity stabiliscono cosa puoi farne, come proteggerli e come segnalare le violazioni. Domande: hello@dazr.eu.