Iniciar sessão com Dazr Identity: documentação para programadores

As pessoas já podem usar a sua conta Google ou Microsoft dentro do Dazr Identity, pelo que a sua aplicação não precisa de botões Google e Microsoft separados ao lado: um botão em vez de três.

Descoberta e endpoints

Tudo está descrito no documento de descoberta. Aponte a sua biblioteca para o emissor e ela encontra o resto.

O quêEndereço
Emissorhttps://identity.dazr.eu
Discoveryhttps://identity.dazr.eu/.well-known/openid-configuration
Autorizaçãohttps://identity.dazr.eu/oauth/authorize
Tokenhttps://identity.dazr.eu/oauth/token
Informações do utilizadorhttps://identity.dazr.eu/oauth/userinfo
Chaves públicas (JWKS)https://identity.dazr.eu/oauth/jwks
Revogação (RFC 7009)https://identity.dazr.eu/oauth/revoke
Introspeção (RFC 7662), só aplicações de servidor webhttps://identity.dazr.eu/oauth/introspect
Terminar sessão (logout iniciado pela RP)https://identity.dazr.eu/oauth/logout
Relatórios de verificação, só aplicações de servidor webhttps://identity.dazr.eu/oauth/verification-report

Registar uma aplicação

Uma aplicação na consola de programadores do Dazr Identity: ID de cliente, URL de descoberta, URIs de redirecionamento, âmbitos e os passos para a ligar

Guias de integração

Cada stack abaixo usa o seu próprio suporte padrão de OpenID Connect. Precisa de três valores da consola: o ID de cliente, o segredo de cliente (só aplicações de servidor web) e o URI de redirecionamento que registou. O resto vem do documento de descoberta.

Auth.js / NextAuth

O Auth.js (NextAuth.js v5) aceita um objeto de fornecedor OpenID Connect personalizado. Registe uma aplicação de servidor web, defina AUTH_SECRET, AUTH_DAZR_ID e AUTH_DAZR_SECRET, e use este URL de callback como URI de redirecionamento: 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" },
    },
  ],
})

O NextAuth.js v4 usa a mesma ideia com type: "oauth" e o URL de descoberta:

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

Com o ficheiro auth.ts acima, adicione o route handler e um botão de início de sessão que executa uma 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>
  )
}

Formate o botão como mostrado em O botão. Nos callbacks, o sub do Dazr é account.providerAccountId.

WordPress

Use um plugin genérico de cliente OpenID Connect, como o OpenID Connect Generic Client. Registe uma aplicação de servidor web e preencha as definições do plugin com os valores abaixo. O plugin mostra o URI de redirecionamento a registar na sua página de definições; por defeito é https://example.com/wp-admin/admin-ajax.php?action=openid-connect-authorize.

DefiniçãoValor
ID de clienteo seu ID de cliente
Segredo de clienteo seu segredo de cliente
Scopeopenid profile email
Endpoint de início de sessão (autorização)https://identity.dazr.eu/oauth/authorize
Endpoint de tokenhttps://identity.dazr.eu/oauth/token
Endpoint de informações do utilizadorhttps://identity.dazr.eu/oauth/userinfo
Endpoint de fim de sessãohttps://identity.dazr.eu/oauth/logout
Chave de identidadesub
PKCEativo (S256)

Os nomes das definições variam um pouco entre plugins. Se um plugin não tiver opção de PKCE, escolha outro: o Dazr Identity rejeita inícios de sessão sem PKCE.

Laravel

O Laravel Socialite não tem um driver OpenID Connect genérico incluído, por isso adicione um pequeno driver personalizado. Envia as pessoas para o Dazr com PKCE e lê o utilizador a partir do endpoint de informações do utilizador. O Socialite não lê o documento de descoberta, por isso os endpoints estão escritos por extenso. Registe uma aplicação de servidor web com o URI de redirecionamento 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

O Supabase Auth não tem um fornecedor OpenID Connect genérico que possa apontar para qualquer emissor: os seus fornecedores de início de sessão e integrações de autenticação de terceiros são listas fixas. Por isso, hoje não pode adicionar o Dazr Identity no painel do Supabase. Se o Supabase acrescentar suporte genérico de OpenID Connect, use os valores da lista de verificação abaixo.

A alternativa: faça o início de sessão com o Dazr Identity no seu próprio servidor (com um dos guias desta página), guarde o sub do Dazr na sua tabela de utilizadores e comunique com o Supabase a partir desse servidor com a chave service role, verificando o acesso no seu próprio código. Nunca envie a chave service role para um navegador.

Django

Use o mozilla-django-oidc. Registe uma aplicação de servidor web com o URI de redirecionamento https://app.example.eu/oidc/callback/. A biblioteca não lê o documento de descoberta, por isso os endpoints estão escritos por extenso.

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

Por defeito, o backend associa os utilizadores pelo e-mail. Para associar as contas ao sub, crie uma subclasse de OIDCAuthenticationBackend e substitua filter_users_by_claims e create_user. Use uma versão atual: as versões antigas não conseguem verificar assinaturas ES256.

Node.js (openid-client)

O openid-client (versão 6) lê o documento de descoberta e verifica por si o PKCE, o state, o nonce e o token de 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 };
}

Qualquer biblioteca OpenID Connect

Se a sua stack não estiver na lista, qualquer cliente OpenID Connect funciona. Verifique estas definições:

O fluxo

  1. Crie um code_verifier aleatório (43 a 128 caracteres) e o respetivo code_challenge = base64url(SHA-256(verifier)). Crie um state e um nonce aleatórios. Guarde os três na sessão do utilizador.
  2. Redirecione para o endpoint de autorização com response_type=code, client_id, redirect_uri, scope (incluindo sempre openid), state, nonce, code_challenge e code_challenge_method=S256.
  3. A pessoa inicia sessão se necessário e vê o ecrã de consentimento com os dados reais. Quando autoriza, volta ao seu URI de redirecionamento com code, state e iss. Verifique se o state corresponde e se iss é https://identity.dazr.eu.
  4. No seu servidor, faça um POST para o endpoint de token com grant_type=authorization_code, code, o mesmo redirect_uri e o code_verifier.
  5. Verifique o token de ID: assinatura ES256 com uma chave do JWKS (corresponda o kid), iss, aud = o seu ID de cliente, exp e o seu nonce. Use sub como chave da conta.
O ecrã de consentimento do Dazr Identity para uma aplicação chamada Ledgerline: indica o nome, o endereço de e-mail e a organização verificada que o utilizador escolheu partilhar

O parâmetro prompt aceita none (responde de imediato com login_required ou consent_required se a pessoa tiver de agir), login (pede à pessoa que volte a iniciar sessão) e consent (mostra o ecrã de consentimento mesmo que já tenha sido aprovado). max_age é suportado. Consentimento memorizado: se a pessoa já aprovou os mesmos âmbitos, volta diretamente à sua aplicação. Se pedir mais depois, o ecrã de consentimento mostra apenas o que é novo.

Âmbitos e claims

Peça apenas o que precisa. Nada fora desta tabela existe: sem contactos, sem ficheiros, sem palavras-passe.

ScopeClaims
openid (obrigatório)sub: um ID aleatório para a pessoa, igual para todas as aplicações da sua organização e diferente para qualquer outra organização
profilename (se definido) e locale
emailemail e email_verified (sempre true)
addressaddress (formatted, street_address, locality, region, postal_code, country), só se a pessoa tiver guardado uma morada
organisationsorganisations: as organizações que a pessoa assinala no ecrã de consentimento, cada uma com id, name, display_name, website, country, register_number, vat, vat_verified (true apenas quando o serviço VIES da UE confirmou o número de IVA), verified, verification (ver Organizações verificadas) e role (owner, admin ou member)
offline_accessnenhuma claim; recebe também um refresh token

As claims aparecem no token de ID para os âmbitos que a pessoa aprovou. O endpoint de informações do utilizador devolve o mesmo conjunto, com os valores atuais.

Organizações verificadas

O Dazr só verifica uma organização quando fontes oficiais provam duas coisas: quem é a pessoa e que representa esta organização, com o seu nome, número de registo e número de IVA. Normalmente, o administrador assina uma breve declaração com uma assinatura eletrónica qualificada (QES) e junta a certidão do registo comercial em que consta; com uma certidão selada pelo registo, como a visura italiana ou um extrato KvK neerlandês, é imediato; caso contrário, o Dazr analisa-a. Uma assinatura qualificada, por si só, nunca torna uma organização verificada. Cada organização na claim organisations traz a sua verificação atual:

Verificar uma organização no Dazr Identity: o administrador assina a declaração com uma assinatura qualificada já ou mais tarde por e-mail, ou carrega a certidão da empresa e um documento de identificação
verification.statusSignificado
unverifiedNão verificada, ou uma verificação falhou, expirou ou foi revogada (por exemplo, depois de mudar a denominação ou o número de registo).
pendingA verificação está em curso: foi pedido ao representante que assinasse, a certidão do registo comercial ainda falta ou o Dazr está a analisar documentos. method indica a via iniciada, por exemplo qes (o representante assina a declaração), qseal, pec, letter, video ou documents.
verifiedVerificada. method é qes_org (assinatura qualificada, o certificado indica a organização), extract_sealed (assinatura qualificada ou documento de identificação analisado, mais uma certidão selada pelo registo comercial), qes_reviewed (assinatura qualificada, certidão verificada pelo Dazr), qseal (o selo eletrónico qualificado da organização), documents, pec (um código enviado para o endereço PEC da organização, Itália), letter (um código enviado por correio para a sede), video (uma videochamada com o Dazr) ou extract. authority indica o que provou que a pessoa representa a organização (ver abaixo), e verified_at é um tempo Unix.

Apenas quando o estado é verified, verification.authority (e authority nos webhooks) indica como foi provada a ligação entre a pessoa e a organização:

verification.authoritySignificado
certificateO certificado qualificado do signatário, ou o selo qualificado da organização, indica a organização com o seu número de registo. Se o certificado tiver apenas um número de IVA, uma certidão do registo provou o número de registo.
register_extractUma certidão selada pelo registo comercial, com no máximo 3 meses, indica a organização com os seus números de registo e de IVA e inclui o signatário como administrador ou representante legal.
reviewedO Dazr verificou uma certidão que inclui o signatário ou, sem assinatura qualificada, a certidão e um documento de identificação.
pecUm código enviado para o endereço PEC indicado na visura selada pelo registo (Itália).
letterUm código enviado por correio para a sede, depois de o Dazr ter verificado os dados no registo.
videoUma videochamada com o Dazr: um documento de identificação e uma certidão oficial em que a pessoa consta.

O booleano verified mantém-se por compatibilidade e é igual a verification.status === 'verified'. Cada organização tem também um id, o mesmo usado pelos webhooks.

Quando o estado é pending ou verified, verification.report_url é o endereço do respetivo relatório de verificação.

"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 e a pertença são lidas de novo em cada renovação de token e pedido de informações do utilizador: após uma mudança de função, a resposta seguinte traz a nova função, e uma organização que a pessoa deixou ou da qual foi removida deixa de ser incluída. Quando um administrador da organização desliga a sua aplicação, a organização é omitida dos tokens emitidos antes, até um membro a voltar a partilhar com a sua aplicação num novo início de sessão. display_name e website são null quando a organização não os definiu; name é sempre a denominação legal.

Exigir uma organização verificada

Na consola, defina Exigir uma organização verificada como Verificação iniciada ou Verificada, ou peça-o por pedido com acr_values=urn:dazr:org:verification-started ou acr_values=urn:dazr:org:verified (prevalece o mais rigoroso dos dois). A sua aplicação tem também de pedir o âmbito organisations.

A definição Exigir uma organização verificada na consola de programadores: não exigida, verificação iniciada ou verificada

Quem não tem uma organização que cumpra o requisito é conduzido pelo processo no mesmo início de sessão: o nome, a organização (com o número de IVA verificado no VIES) e depois a verificação. Pode assinar de imediato, receber a declaração por e-mail ou indicar outra pessoa que assine. Depois assinala a organização no ecrã de consentimento (só se podem assinalar organizações que cumpram o requisito) e volta ao seu URI de redirecionamento.

Se outra pessoa ainda tiver de assinar, a pessoa volta com o estado pending, mesmo que tenha pedido Verificada: recebe um código, a claim indica pending e o acr do token de ID é urn:dazr:org:verification-started em vez de urn:dazr:org:verified. Decida sempre com base em verification.status, nunca no facto de o início de sessão ter sido bem-sucedido. Com prompt=none recebe interaction_required quando o requisito não é cumprido.

As alterações à definição aplicam-se a partir do próximo início de sessão. As pessoas com sessão já iniciada não são desligadas: as respostas de renovação, o endpoint de informações do utilizador e os webhooks trazem sempre o estado atual, por isso use-os para reagir nas sessões existentes.

Webhooks

Adicione um URL de webhook (https) na consola. Vê o segredo de assinatura uma vez; pode rodá-lo. O Dazr envia organisation.verification.updated quando uma organização que um dos seus utilizadores partilhou com a sua aplicação passa a pendente, verificada, falhada ou não verificada. As aplicações que nunca receberam uma organização através de consentimento nunca são notificadas.

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 é pending, verified, failed ou unverified. subjects são os valores sub, na sua aplicação, das pessoas que partilharam esta organização com ela. Verifique a assinatura antes de confiar no corpo:

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

Responda com qualquer 2xx em 8 segundos. Caso contrário, o Dazr tenta de novo após cerca de 5 minutos, 30 minutos, 2, 6, 12 e 24 horas e depois marca a entrega como falhada. Todas as tentativas têm o mesmo ID Dazr-Delivery, por isso ignore duplicados. A consola mostra as entregas recentes, pode reenviar uma e pode enviar um evento de teste (webhook.test). Consultar periodicamente o endpoint de informações do utilizador também funciona.

Relatórios de verificação para auditorias

Para cada organização partilhada com a sua aplicação, pode obter um relatório de como e quando o Dazr a verificou, para o seu dossiê de auditoria. Existe enquanto a organização estiver verificada e enquanto a verificação estiver em curso, mostrando o que já foi feito.

Um relatório nunca contém imagens nem números de documentos de identificação, códigos de utilização única, o endereço PEC, endereços de e-mail nem qualquer morada que não seja a sede.

Obter um relatório

Autentique-se com o seu ID de cliente e segredo de cliente, como no endpoint de token: HTTP Basic (client_secret_basic) ou o corpo do formulário de um POST (client_secret_post). Nunca coloque o segredo no URL. Use o id da organização das claims ou dos webhooks, ou simplesmente o verification.report_url que trazem.

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"
ParâmetroSignificado
orgO id da organização.
formatpdf (predefinição) ou jws, os mesmos factos em JSON assinado.
langIdioma do PDF: en (predefinição), nl, it, de, fr, es ou pl.
docEm vez do relatório: declaration, a declaração assinada pelo administrador (só assinatura ou selo qualificado), ou extract, a certidão do registo comercial.

Só é possível obter organizações que pelo menos um dos seus utilizadores partilhou com a sua aplicação através de consentimento. Qualquer outra organização responde 404, tal como uma que não existe ou não tem verificação; um segredo de cliente errado responde 401. As aplicações sem segredo de cliente não podem obter relatórios. As transferências têm limite de frequência, e cada uma aparece na atividade da organização para os seus administradores, com o nome da sua aplicação. Na consola, a página da sua aplicação mostra as organizações ligadas com as mesmas transferências.

Verificar o JSON assinado

Com format=jws recebe um JWS compacto (ES256, cabeçalho typ dazr-verification-report+jwt), assinado com as mesmas chaves que os tokens de ID. As suas claims são iss, iat, jti (o ID do documento), sub (o id da organização), aud (o seu ID de cliente) e report, os factos. O PDF traz o mesmo JWS na última página e nos metadados (DazrVerificationReport), pelo que um PDF também pode ser verificado 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);

Deixar um auditor verificar a assinatura

Para uma assinatura ou selo qualificado, doc=declaration devolve a declaração assinada exatamente como foi carregada: um PDF assinado (PAdES) ou um ficheiro .p7m (CAdES). Um auditor pode validá-la de forma independente com o validador de demonstração DSS da Comissão Europeia: carregue o ficheiro, mantenha a política de validação predefinida e execute-a. O resultado mostra se a assinatura é qualificada (QESig, ou QESeal para um selo), quem assinou e o prestador de serviços de confiança da lista de confiança da UE. doc=extract devolve a certidão do registo comercial; uma certidão selada pelo registo pode ser validada da mesma forma.

O relatório é uma prova para o seu dossiê de auditoria. As entidades reguladas continuam responsáveis pela sua própria diligência devida quanto aos clientes.

O Dazr conserva a declaração assinada, a certidão e os dados do relatório enquanto a organização estiver verificada e durante 5 anos após o fim da verificação ou a eliminação da organização. Guarde as suas próprias cópias durante o tempo que as suas regras exigirem.

Tokens e validades

TokenValidade e regras
Código de autorização60 segundos, utilização única, associado ao seu cliente, URI de redirecionamento e desafio PKCE. Usar um código duas vezes revoga os tokens que emitiu.
Token de acesso10 minutos. Um JWT assinado (ES256, typ at+jwt) com aud = o seu ID de cliente. Envie-o como Authorization: Bearer.
Token de ID10 minutos. ES256, com iss, sub, aud, exp, iat, auth_time, nonce e amr quando conhecido.
Refresh tokenSó com offline_access. Roda a cada utilização: guarde sempre o novo. Enviar novamente um refresh token antigo revoga todo o início de sessão. Expira após 30 dias sem utilização e, no máximo, após 180 dias.

As chaves de assinatura são rotativas. Escolha sempre a chave pelo kid do JWKS e atualize o JWKS quando vir um kid desconhecido.

Erros

Desde que o seu ID de cliente e o URI de redirecionamento sejam válidos, os erros regressam ao seu URI de redirecionamento como error, error_description, state e iss: por exemplo invalid_request (como PKCE em falta ou simples), invalid_scope, unsupported_response_type, access_denied (a pessoa cancelou, ou a aplicação está em teste e a pessoa não é membro), login_required, consent_required e interaction_required (com prompt=none, quando falta uma organização verificada exigida). Com um cliente desconhecido ou um URI de redirecionamento não registado, o Dazr mostra uma página de erro e nunca redireciona. O endpoint de token responde com erros JSON como invalid_client, invalid_grant e unsupported_grant_type.

Terminar sessão

Para terminar a sessão de alguém, envie a pessoa para o endpoint de fim de sessão com id_token_hint (ou client_id), um post_logout_redirect_uri opcional registado nas definições da sua aplicação e state. O Dazr pergunta se também quer terminar a sessão do Dazr Identity nesse dispositivo e depois envia-a de volta. Revogue os refresh tokens de que já não precisa no endpoint de revogação.

Testar e entrar em produção

Uma aplicação nova está em teste: só os membros da sua organização podem iniciar sessão, e o ecrã de consentimento indica-o. Para entrar em produção, verifique a sua organização no Dazr Identity, adicione um URI de redirecionamento https:// e peça uma revisão para produção na consola. Alterar o nome, a finalidade, a política de privacidade ou os dados de uma aplicação em produção exige uma nova revisão.

Transferir uma aplicação para outra organização

Uma aplicação pode passar para outra organização registada na Dazr, por exemplo após uma venda ou uma reorganização. Um proprietário ou administrador inicia a transferência em Transferir aplicação, nas definições da aplicação, no Dazr Identity ou no portal do Sign. Um proprietário ou administrador da outra organização aceita-a ou recusa-a no prazo de 14 dias. Até lá, o remetente pode cancelar e nada muda.

O botão

Use o texto «Iniciar sessão com Dazr Identity» e o logótipo do Dazr como abaixo. Pode alterar o tamanho, mas não o logótipo, as cores nem o texto. Ligue-o à sua própria rota que inicia o fluxo.

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

Tipo de letra Dazr. O botão usa o tipo de letra Dazr, de utilização gratuita ao abrigo da SIL Open Font License. Onde o Dazr não estiver carregado, é usado o tipo de letra do sistema. Transfira o tipo de letra Dazr (ZIP com ficheiros TTF e WOFF2) para o alojar no seu próprio 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; }

Um exemplo mínimo em Node.js

Node 18 ou posterior, sem dependências. Uma aplicação de servidor web com segredo de cliente; para um cliente público, retire o cabeçalho Authorization e envie client_id no 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;
}

Limites

Os endpoints de autorização e de token têm limite de frequência. Se receber HTTP 429, aguarde e tente novamente; não repita num ciclo apertado.

As suas obrigações

A sua organização é responsável pelo tratamento, de forma independente, dos dados que recebe. Os Termos para Programadores do Dazr Identity definem o que pode fazer com eles, como os manter seguros e como comunicar violações. Dúvidas: hello@dazr.eu.