Autentificare cu Dazr Identity: documentație pentru dezvoltatori

Oamenii își pot folosi deja contul Google sau Microsoft în cadrul Dazr Identity, așa că aplicația dumneavoastră nu are nevoie de butoane Google și Microsoft separate alături: un singur buton în loc de trei.

Descoperire și endpointuri

Totul este descris în documentul de descoperire. Îndreptați biblioteca spre emitent și găsește singură restul.

CeAdresă
Emitenthttps://identity.dazr.eu
Discoveryhttps://identity.dazr.eu/.well-known/openid-configuration
Autorizarehttps://identity.dazr.eu/oauth/authorize
Tokenhttps://identity.dazr.eu/oauth/token
Informații despre utilizatorhttps://identity.dazr.eu/oauth/userinfo
Chei publice (JWKS)https://identity.dazr.eu/oauth/jwks
Revocare (RFC 7009)https://identity.dazr.eu/oauth/revoke
Introspecție (RFC 7662), doar aplicații cu server webhttps://identity.dazr.eu/oauth/introspect
Deconectare (inițiată de RP)https://identity.dazr.eu/oauth/logout
Rapoarte de verificare, doar aplicații cu server webhttps://identity.dazr.eu/oauth/verification-report

Înregistrați o aplicație

O aplicație în consola pentru dezvoltatori Dazr Identity: ID-ul clientului, URL-ul de descoperire, URI-urile de redirecționare, domeniile de acces și pașii de conectare

Ghiduri de integrare

Fiecare tehnologie de mai jos își folosește propriul suport OpenID Connect standard. Aveți nevoie de trei valori din consolă: ID-ul clientului, secretul clientului (doar aplicații cu server web) și URI-ul de redirecționare înregistrat. Restul vine din documentul de descoperire.

Auth.js / NextAuth

Auth.js (NextAuth.js v5) acceptă un obiect de furnizor OpenID Connect personalizat. Înregistrați o aplicație cu server web, setați AUTH_SECRET, AUTH_DAZR_ID și AUTH_DAZR_SECRET și folosiți acest URL de callback ca URI de redirecționare: 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 folosește aceeași idee cu type: "oauth" și URL-ul de descoperire:

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

Cu fișierul auth.ts de mai sus, adăugați handlerul de rută și un buton de autentificare care rulează o acțiune pe server.

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

Stilizați butonul așa cum se arată la Butonul. În callbackuri, sub-ul Dazr este account.providerAccountId.

WordPress

Folosiți un plugin generic de client OpenID Connect, cum ar fi OpenID Connect Generic Client. Înregistrați o aplicație cu server web și completați setările pluginului cu valorile de mai jos. Pluginul afișează pe pagina de setări URI-ul de redirecționare de înregistrat; implicit este https://example.com/wp-admin/admin-ajax.php?action=openid-connect-authorize.

SetareValoare
ID-ul clientuluiID-ul dumneavoastră de client
Secretul clientuluisecretul dumneavoastră de client
Scopeopenid profile email
Endpoint de conectare (autorizare)https://identity.dazr.eu/oauth/authorize
Endpoint de tokenhttps://identity.dazr.eu/oauth/token
Endpoint de informații despre utilizatorhttps://identity.dazr.eu/oauth/userinfo
Endpoint de încheiere a sesiuniihttps://identity.dazr.eu/oauth/logout
Cheie de identitatesub
PKCEactivat (S256)

Denumirile setărilor diferă puțin de la un plugin la altul. Dacă un plugin nu are opțiunea PKCE, alegeți altul: Dazr Identity respinge autentificările fără PKCE.

Laravel

Laravel Socialite nu are un driver generic OpenID Connect integrat, așa că adăugați un mic driver personalizat. Acesta trimite oamenii la Dazr cu PKCE și citește utilizatorul de la endpointul de informații despre utilizator. Socialite nu citește documentul de descoperire, așa că endpointurile sunt scrise explicit. Înregistrați o aplicație cu server web cu URI-ul de redirecționare 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 nu are un furnizor generic OpenID Connect pe care să îl îndreptați spre orice emitent: furnizorii de autentificare și integrările de autentificare terțe sunt liste fixe. Așa că deocamdată nu puteți adăuga Dazr Identity în tabloul de bord Supabase. Dacă Supabase adaugă suport generic OpenID Connect, folosiți valorile din lista de verificare de mai jos.

Alternativa: autentificați oamenii cu Dazr Identity pe propriul server (cu unul dintre ghidurile de pe această pagină), stocați sub-ul Dazr în tabelul de utilizatori și comunicați cu Supabase de pe acel server cu cheia service role, verificând accesul în propriul cod. Nu trimiteți niciodată cheia service role într-un browser.

Django

Folosiți mozilla-django-oidc. Înregistrați o aplicație cu server web cu URI-ul de redirecționare https://app.example.eu/oidc/callback/. Biblioteca nu citește documentul de descoperire, așa că endpointurile sunt scrise explicit.

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

Implicit, backendul identifică utilizatorii după e-mail. Pentru a identifica conturile după sub, creați o subclasă a OIDCAuthenticationBackend și suprascrieți filter_users_by_claims și create_user. Folosiți o versiune actuală: versiunile mai vechi nu pot verifica semnăturile ES256.

Node.js (openid-client)

openid-client (versiunea 6) citește documentul de descoperire și verifică pentru dumneavoastră PKCE, state, nonce și tokenul ID.

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

Orice bibliotecă OpenID Connect

Dacă tehnologia dumneavoastră nu este listată, funcționează orice client OpenID Connect. Verificați aceste setări:

Fluxul

  1. Creați un code_verifier aleatoriu (43-128 de caractere) și code_challenge-ul său = base64url(SHA-256(verifier)). Creați un state și un nonce aleatorii. Păstrați-le pe toate trei în sesiunea utilizatorului.
  2. Redirecționați la endpointul de autorizare cu response_type=code, client_id, redirect_uri, scope (incluzând întotdeauna openid), state, nonce, code_challenge și code_challenge_method=S256.
  3. Persoana se autentifică dacă este nevoie și vede ecranul de consimțământ cu datele reale. Când permite, revine la URI-ul de redirecționare cu code, state și iss. Verificați că state corespunde și că iss este https://identity.dazr.eu.
  4. Pe server, trimiteți un POST la endpointul de token cu grant_type=authorization_code, code, același redirect_uri și code_verifier.
  5. Verificați tokenul ID: semnătura ES256 cu o cheie din JWKS (potriviți kid), iss, aud = ID-ul dumneavoastră de client, exp și nonce-ul dumneavoastră. Folosiți sub drept cheie a contului.
Ecranul de consimțământ Dazr Identity pentru o aplicație numită Ledgerline: arată numele, adresa de e-mail și organizația verificată pe care utilizatorul a ales să le partajeze

Parametrul prompt acceptă none (răspunde imediat cu login_required sau consent_required dacă persoana ar trebui să acționeze), login (cere persoanei să se autentifice din nou) și consent (afișează ecranul de consimțământ chiar dacă a fost aprobat anterior). max_age este acceptat. Consimțământ reținut: dacă persoana a aprobat anterior aceleași domenii de acces, revine direct în aplicație. Dacă cereți mai mult ulterior, ecranul de consimțământ listează doar ce este nou.

Domenii de acces și claimuri

Cereți doar ce aveți nevoie. Nimic din afara acestui tabel nu există: fără contacte, fără fișiere, fără parole.

ScopeClaimuri
openid (obligatoriu)sub: un ID aleatoriu pentru persoană, același pentru fiecare aplicație a organizației dumneavoastră și diferit pentru orice altă organizație
profilename (dacă este setat) și locale
emailemail și email_verified (întotdeauna true)
addressaddress (formatted, street_address, locality, region, postal_code, country), doar dacă persoana a salvat una
organisationsorganisations: organizațiile pe care persoana le bifează pe ecranul de consimțământ, fiecare cu id, name, display_name, website, country, register_number, vat, vat_verified (true doar când serviciul VIES al UE a confirmat codul de TVA), verified, verification (vedeți Organizații verificate) și role (owner, admin sau member)
offline_accessfără claimuri; primiți și un token de reîmprospătare

Claimurile apar în tokenul ID pentru domeniile de acces aprobate de persoană. Endpointul de informații despre utilizator returnează același set, cu valorile curente.

Organizații verificate

Dazr verifică o organizație doar când surse oficiale dovedesc două lucruri: cine este persoana și că reprezintă această organizație, cu numele, numărul de înregistrare și codul de TVA ale acesteia. De obicei, administratorul semnează o scurtă declarație cu o semnătură electronică calificată (QES) și adaugă extrasul din registrul comerțului care îl menționează; cu un extras sigilat de registru, precum visura italiană sau un extras KvK neerlandez, este instantaneu, altfel Dazr îl analizează. O semnătură calificată singură nu face niciodată o organizație verificată. Fiecare organizație din claimul organisations poartă verificarea sa actuală:

Verificarea unei organizații în Dazr Identity: administratorul semnează declarația cu o semnătură calificată acum sau mai târziu prin e-mail ori încarcă extrasul companiei și un act de identitate
verification.statusSemnificație
unverifiedNeverificată sau o verificare a eșuat, a expirat ori a fost revocată (de exemplu după schimbarea denumirii legale sau a numărului de înregistrare).
pendingVerificarea este în curs: reprezentantului i s-a cerut să semneze, extrasul din registrul comerțului lipsește încă sau Dazr analizează documentele. method arată ce cale a fost începută, de exemplu qes (reprezentantul semnează declarația), qseal, pec, letter, video sau documents.
verifiedVerificată. method este qes_org (semnătură calificată, certificatul numește organizația), extract_sealed (semnătură calificată sau act de identitate analizat, plus un extras sigilat de registrul comerțului), qes_reviewed (semnătură calificată, extras verificat de Dazr), qseal (sigiliul electronic calificat al organizației), documents, pec (un cod trimis la adresa PEC a organizației, Italia), letter (un cod trimis prin poștă la sediul social), video (un apel video cu Dazr) sau extract. authority arată ce a dovedit că persoana reprezintă organizația (vedeți mai jos), iar verified_at este un timp Unix.

Doar când statutul este verified, verification.authority (și authority în webhookuri) arată cum a fost dovedită legătura dintre persoană și organizație:

verification.authoritySemnificație
certificateCertificatul calificat al semnatarului sau sigiliul calificat al organizației numește organizația cu numărul său de înregistrare. Când certificatul conține doar codul de TVA, un extras din registru a dovedit numărul de înregistrare.
register_extractUn extras sigilat de registrul comerțului, cu o vechime de cel mult 3 luni, numește organizația cu numărul de înregistrare și codul de TVA și îl menționează pe semnatar ca administrator sau reprezentant legal.
reviewedDazr a verificat un extras care îl menționează pe semnatar sau, fără semnătură calificată, extrasul și un act de identitate.
pecUn cod trimis la adresa PEC din visura sigilată de registru (Italia).
letterUn cod trimis prin poștă la sediul social, după ce Dazr a verificat datele în raport cu registrul.
videoUn apel video cu Dazr: un act de identitate și un extras oficial care menționează persoana.

Valoarea booleană verified rămâne pentru compatibilitate și este egală cu verification.status === 'verified'. Fiecare organizație are și un id, același pe care îl folosesc webhookurile.

Când statutul este pending sau verified, verification.report_url este adresa raportului de verificare.

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

role și apartenența sunt citite din nou la fiecare reîmprospătare a tokenului și la fiecare cerere de informații despre utilizator: după o schimbare de rol, următorul răspuns conține noul rol, iar o organizație pe care persoana a părăsit-o sau din care a fost eliminată nu mai este inclusă. Când un administrator al organizației deconectează aplicația dumneavoastră, organizația este omisă din tokenurile emise anterior, până când un membru o partajează din nou cu aplicația într-o nouă autentificare. display_name și website sunt null când organizația nu le-a setat; name este întotdeauna denumirea legală.

Cereți o organizație verificată

În consolă, setați Cereți o organizație verificată la Verificare începută sau Verificată ori cereți per solicitare cu acr_values=urn:dazr:org:verification-started sau acr_values=urn:dazr:org:verified (câștigă cea mai strictă dintre ele). Aplicația trebuie să ceară și domeniul de acces organisations.

Setarea Cereți o organizație verificată din consola pentru dezvoltatori: necerută, verificare începută sau verificată

Cineva fără o organizație eligibilă este condus prin proces în aceeași autentificare: numele său, organizația (cu codul de TVA verificat în VIES), apoi verificarea. Poate semna imediat, poate primi declarația pe e-mail sau poate numi pe altcineva care să semneze. Apoi bifează organizația pe ecranul de consimțământ (pot fi bifate doar organizațiile eligibile) și revine la URI-ul de redirecționare.

Dacă altcineva mai trebuie să semneze, persoana revine cu statutul pending, chiar și când ați cerut Verificată: primiți un cod, claimul spune pending, iar acr din tokenul ID este urn:dazr:org:verification-started în loc de urn:dazr:org:verified. Decideți întotdeauna pe baza verification.status, niciodată pe baza faptului că autentificarea a reușit. Cu prompt=none primiți interaction_required când cerința nu este îndeplinită.

Modificările setării se aplică de la următoarea autentificare. Persoanele deja autentificate nu sunt deconectate: răspunsurile la reîmprospătare, endpointul de informații despre utilizator și webhookurile conțin întotdeauna statutul curent, așa că folosiți-le pentru a reacționa în sesiunile existente.

Webhookuri

Adăugați un URL de webhook (https) în consolă. Vedeți secretul de semnare o singură dată; îl puteți roti. Dazr trimite organisation.verification.updated când o organizație pe care unul dintre utilizatori a partajat-o cu aplicația dumneavoastră devine pending, verified, failed sau unverified. Aplicațiile care nu au primit niciodată o organizație prin consimțământ nu află nimic despre ea.

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", "authority": "certificate", "verified_at": 1791021598,
            "report_url": "https://identity.dazr.eu/oauth/verification-report?org=org_3k9x2m0q8w1v5t7a",
            "subjects": ["Xo1c…"] } }

status este pending, verified, failed sau unverified. subjects sunt valorile sub, în aplicația dumneavoastră, ale persoanelor care au partajat această organizație cu ea. Verificați semnătura înainte să aveți încredere în conținut:

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

Răspundeți cu orice cod 2xx în 8 secunde. Altfel, Dazr reîncearcă după aproximativ 5 minute, 30 de minute, 2, 6, 12 și 24 de ore, apoi marchează livrarea ca eșuată. Fiecare încercare are același ID Dazr-Delivery, așa că ignorați duplicatele. Consola listează livrările recente, poate retrimite una și poate trimite un eveniment de test (webhook.test). Funcționează și interogarea periodică a endpointului de informații despre utilizator.

Rapoarte de verificare pentru audituri

Pentru fiecare organizație partajată cu aplicația dumneavoastră puteți obține un raport despre cum și când a verificat-o Dazr, pentru dosarul de audit. Există cât timp organizația este verificată și cât timp verificarea este în curs, arătând ce s-a făcut până atunci.

Un raport nu conține niciodată imagini sau numere ale actelor de identitate, coduri unice, adresa PEC, adrese de e-mail sau vreo altă adresă în afară de sediul social.

Obțineți un raport

Autentificați-vă cu ID-ul și secretul clientului, ca la endpointul de token: HTTP Basic (client_secret_basic) sau corpul formularului unui POST (client_secret_post). Nu puneți niciodată secretul în URL. Folosiți id-ul organizației din claimuri sau webhookuri ori pur și simplu verification.report_url pe care îl conțin.

curl -u "$DAZR_CLIENT_ID:$DAZR_CLIENT_SECRET" -o report.pdf \
  "https://identity.dazr.eu/oauth/verification-report?org=org_3k9x2m0q8w1v5t7a&format=pdf&lang=en"
ParametruSemnificație
orgid-ul organizației.
formatpdf (implicit) sau jws, aceleași fapte ca JSON semnat.
langLimba PDF-ului: en (implicit), nl, it, de, fr, es sau pl.
docÎn locul raportului: declaration, declarația semnată a administratorului (doar semnătură sau sigiliu calificat), ori extract, extrasul din registrul comerțului.

Pot fi obținute doar organizațiile pe care cel puțin unul dintre utilizatori le-a partajat cu aplicația prin consimțământ. Orice altă organizație răspunde 404, la fel ca una care nu există sau nu are verificare; un secret de client greșit răspunde 401. Aplicațiile fără secret de client nu pot obține rapoarte. Descărcările au limite de frecvență și fiecare apare în activitatea organizației pentru administratorii ei, cu numele aplicației dumneavoastră. În consolă, pagina aplicației listează organizațiile conectate, cu aceleași descărcări.

Verificați JSON-ul semnat

Cu format=jws primiți un JWS compact (ES256, antet typ dazr-verification-report+jwt), semnat cu aceleași chei ca tokenurile ID. Claimurile sale sunt iss, iat, jti (ID-ul documentului), sub (id-ul organizației), aud (ID-ul dumneavoastră de client) și report, faptele. PDF-ul conține același JWS pe ultima pagină și în metadate (DazrVerificationReport), așa că și un PDF poate fi verificat offline.

import { createRemoteJWKSet, jwtVerify } from 'jose';

const jwks = createRemoteJWKSet(new URL('https://identity.dazr.eu/oauth/jwks'));
// jws = the response body of format=jws, or the text from the PDF metadata
const { payload } = await jwtVerify(jws, jwks, {
  issuer: 'https://identity.dazr.eu',
  audience: process.env.DAZR_CLIENT_ID,
  typ: 'dazr-verification-report+jwt',
});
console.log(payload.report.verification.status, payload.report.verification.authority);

Lăsați un auditor să verifice semnătura

Pentru o semnătură sau un sigiliu calificat, doc=declaration returnează declarația semnată exact așa cum a fost încărcată: un PDF semnat (PAdES) sau un fișier .p7m (CAdES). Un auditor o poate valida independent cu validatorul demonstrativ DSS al Comisiei Europene: încărcați fișierul, păstrați politica de validare implicită și rulați. Rezultatul arată dacă semnătura este calificată (QESig sau QESeal pentru un sigiliu), cine a semnat și prestatorul de servicii de încredere din lista de încredere a UE. doc=extract returnează extrasul din registrul comerțului; un extras sigilat de registru poate fi validat în același mod.

Raportul este o dovadă pentru dosarul de audit. Firmele reglementate rămân responsabile pentru propria verificare a clienților.

Dazr păstrează declarația semnată, extrasul și datele raportului cât timp organizația este verificată și timp de 5 ani după încheierea verificării sau ștergerea organizației. Păstrați propriile copii atât timp cât o cer regulile dumneavoastră.

Tokenuri și durate de valabilitate

TokenDurată de valabilitate și reguli
Cod de autorizare60 de secunde, de unică folosință, legat de clientul, URI-ul de redirecționare și provocarea PKCE. Folosirea unui cod de două ori revocă tokenurile emise cu el.
Token de acces10 minute. Un JWT semnat (ES256, typ at+jwt) cu aud = ID-ul dumneavoastră de client. Trimiteți-l ca Authorization: Bearer.
Token ID10 minute. ES256, cu iss, sub, aud, exp, iat, auth_time, nonce și amr acolo unde sunt cunoscute.
Token de reîmprospătareDoar cu offline_access. Se rotește la fiecare utilizare: stocați întotdeauna noul token. Retrimiterea unui token de reîmprospătare vechi revocă întreaga autentificare. Expiră după 30 de zile de neutilizare și după cel mult 180 de zile.

Cheile de semnare se rotesc. Alegeți întotdeauna cheia după kid din JWKS și reîmprospătați JWKS când vedeți un kid necunoscut.

Erori

Cât timp ID-ul clientului și URI-ul de redirecționare sunt valide, erorile revin la URI-ul de redirecționare ca error, error_description, state și iss: de exemplu invalid_request (cum ar fi PKCE lipsă sau plain), invalid_scope, unsupported_response_type, access_denied (persoana a anulat sau aplicația este în testare și persoana nu este membru), login_required, consent_required și interaction_required (cu prompt=none, când lipsește o organizație verificată cerută). Cu un client necunoscut sau un URI de redirecționare neînregistrat, Dazr afișează o pagină de eroare și nu redirecționează niciodată. Endpointul de token răspunde cu erori JSON precum invalid_client, invalid_grant și unsupported_grant_type.

Deconectare

Pentru a deconecta pe cineva, trimiteți persoana la endpointul de deconectare cu id_token_hint (sau client_id), un post_logout_redirect_uri opțional înregistrat în setările aplicației și state. Dazr o întreabă dacă vrea să se deconecteze și din Dazr Identity pe acel dispozitiv, apoi o trimite înapoi. Revocați tokenurile de reîmprospătare de care nu mai aveți nevoie la endpointul de revocare.

Testare și lansare

O aplicație nouă este în testare: doar membrii organizației dumneavoastră se pot autentifica, iar ecranul de consimțământ menționează acest lucru. Pentru lansare, verificați organizația în Dazr Identity, adăugați un URI de redirecționare https:// și solicitați o evaluare pentru lansare în consolă. Schimbarea numelui, scopului, politicii de confidențialității sau datelor unei aplicații lansate necesită o nouă evaluare.

Transferul unei aplicații către altă organizație

O aplicație poate trece la altă organizație înregistrată pe Dazr, de exemplu după o vânzare sau o reorganizare. Un proprietar sau administrator pornește transferul din Transferați aplicația, în setările aplicației, în Dazr Identity sau în portalul Sign. Un proprietar sau administrator al celeilalte organizații îl acceptă sau îl refuză în 14 zile. Până atunci, expeditorul poate anula și nu se schimbă nimic.

Butonul

Folosiți formularea „Sign in with Dazr Identity” și logoul Dazr ca mai jos. Puteți schimba dimensiunea, nu și logoul, culorile sau formularea. Legați-l de propria rută care pornește fluxul.

<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 'Dazr', 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>

Fontul Dazr. Butonul folosește fontul Dazr, care poate fi folosit gratuit sub licența SIL Open Font License. Acolo unde Dazr nu este încărcat, se folosește în schimb fontul sistemului. Descărcați fontul Dazr (ZIP cu fișiere TTF și WOFF2) pentru a-l găzdui pe propriul site:

/* woff2 files from the ZIP, copied to /fonts/ on your site */
@font-face { font-family: 'Dazr'; font-weight: 400; font-display: swap;
  src: url('/fonts/dazr-regular-latin.woff2') format('woff2'); }
@font-face { font-family: 'Dazr'; font-weight: 700; font-display: swap;
  src: url('/fonts/dazr-bold-latin.woff2') format('woff2'); }
.dazr-signin { font: 600 15px/1 'Dazr', system-ui, sans-serif; }

Un exemplu minimal în Node.js

Node 18 sau mai nou, fără dependențe. O aplicație cu server web cu secret de client; pentru un client public, eliminați antetul Authorization și trimiteți client_id în corp.

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

Limite

Endpointurile de autorizare și de token au limite de frecvență. Dacă primiți HTTP 429, așteptați și încercați din nou; nu reîncercați într-o buclă strânsă.

Obligațiile dumneavoastră

Organizația dumneavoastră este operator independent pentru datele pe care le primește. Termenii pentru dezvoltatori Dazr Identity stabilesc ce puteți face cu ele, cum le păstrați în siguranță și cum raportați încălcările. Întrebări: hello@dazr.eu.