Sign in with Dazr Identity: documentación para desarrolladores

La gente ya puede usar su cuenta de Google o Microsoft dentro de Dazr Identity, así que tu app no necesita botones de Google y Microsoft aparte: un botón en lugar de tres.

Discovery y endpoints

Todo está descrito en el documento de discovery. Indica el emisor a tu biblioteca y encontrará el resto.

QuéDirección
Emisorhttps://identity.dazr.eu
Discoveryhttps://identity.dazr.eu/.well-known/openid-configuration
Autorizaciónhttps://identity.dazr.eu/oauth/authorize
Tokenhttps://identity.dazr.eu/oauth/token
Información del usuariohttps://identity.dazr.eu/oauth/userinfo
Claves públicas (JWKS)https://identity.dazr.eu/oauth/jwks
Revocación (RFC 7009)https://identity.dazr.eu/oauth/revoke
Introspección (RFC 7662), solo apps de servidor webhttps://identity.dazr.eu/oauth/introspect
Cierre de sesión (RP-initiated logout)https://identity.dazr.eu/oauth/logout

Registrar una app

Guías de integración

Cada stack de abajo usa su propio soporte estándar de OpenID Connect. Necesitas tres valores de la consola: el client ID, el client secret (solo apps de servidor web) y la URI de redirección que registraste. El resto sale del documento de discovery.

Auth.js / NextAuth

Auth.js (NextAuth.js v5) acepta un objeto de proveedor OpenID Connect personalizado. Registra una app de servidor web, define AUTH_SECRET, AUTH_DAZR_ID y AUTH_DAZR_SECRET y usa esta URL de callback como URI de redirección: 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 funciona igual, con type: "oauth" y la URL de 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 el archivo auth.ts de arriba, añade el route handler y un botón de inicio de sesión que ejecuta 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>
  )
}

Da al botón el estilo de El botón. En los callbacks, el sub de Dazr es account.providerAccountId.

WordPress

Usa un plugin cliente OpenID Connect genérico, como OpenID Connect Generic Client. Registra una app de servidor web y rellena los ajustes del plugin con los valores de abajo. El plugin muestra en su página de ajustes la URI de redirección que debes registrar; por defecto es https://example.com/wp-admin/admin-ajax.php?action=openid-connect-authorize.

AjusteValor
Client IDtu client ID
Client secrettu client secret
Scopeopenid profile email
Endpoint de inicio de sesión (autorización)https://identity.dazr.eu/oauth/authorize
Endpoint de tokenshttps://identity.dazr.eu/oauth/token
Endpoint de userinfohttps://identity.dazr.eu/oauth/userinfo
Endpoint de cierre de sesiónhttps://identity.dazr.eu/oauth/logout
Identity key (clave de identidad)sub
PKCEactivado (S256)

Los nombres de los ajustes cambian un poco según el plugin. Si un plugin no tiene opción PKCE, elige otro: Dazr Identity rechaza los inicios de sesión sin PKCE.

Laravel

Laravel Socialite no trae un driver OpenID Connect genérico, así que añade un pequeño driver propio. Envía a las personas a Dazr con PKCE y lee el usuario del endpoint de userinfo. Socialite no lee el documento de discovery, por eso los endpoints están escritos. Registra una app de servidor web con la URI de redirección 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 no tiene un proveedor OpenID Connect genérico que puedas apuntar a cualquier emisor: sus proveedores de inicio de sesión y sus integraciones de autenticación de terceros son listas fijas. Por eso hoy no puedes añadir Dazr Identity en el panel de Supabase. Si Supabase añade soporte genérico de OpenID Connect, usa los valores de la lista de abajo.

La alternativa: inicia la sesión de las personas con Dazr Identity en tu propio servidor (con una de las guías de esta página), guarda el sub de Dazr en tu tabla de usuarios y habla con Supabase desde ese servidor con la service role key, comprobando el acceso en tu propio código. Nunca envíes la service role key a un navegador.

Django

Usa mozilla-django-oidc. Registra una app de servidor web con la URI de redirección https://app.example.eu/oidc/callback/. La biblioteca no lee el documento de discovery, por eso los endpoints están escritos.

# 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 defecto, el backend asocia a los usuarios por correo. Para vincular las cuentas a sub, crea una subclase de OIDCAuthenticationBackend y sobrescribe filter_users_by_claims y create_user. Usa una versión actual: las versiones antiguas no verifican firmas ES256.

Node.js (openid-client)

openid-client (versión 6) lee el documento de discovery y comprueba por ti PKCE, state, nonce y el 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 };
}

Cualquier biblioteca OpenID Connect

Si tu stack no aparece, funciona cualquier cliente OpenID Connect. Revisa estos ajustes:

El flujo

  1. Crea un code_verifier aleatorio (de 43 a 128 caracteres) y su code_challenge = base64url(SHA-256(verifier)). Crea un state y un nonce aleatorios. Guarda los tres en la sesión del usuario.
  2. Redirige al endpoint de autorización con response_type=code, client_id, redirect_uri, scope (siempre con openid), state, nonce, code_challenge y code_challenge_method=S256.
  3. La persona inicia sesión si hace falta y ve la pantalla de consentimiento con los datos reales. Si lo permite, vuelve a tu URI de redirección con code, state e iss. Comprueba que state coincide y que iss es https://identity.dazr.eu.
  4. En tu servidor, haz un POST al endpoint de tokens con grant_type=authorization_code, code, el mismo redirect_uri y el code_verifier.
  5. Verifica el ID token: firma ES256 con una clave del JWKS (según el kid), iss, aud = tu ID de cliente, exp y tu nonce. Usa sub como clave de la cuenta.

El parámetro prompt acepta none (respuesta inmediata con login_required o consent_required si la persona tuviera que actuar), login (pedir a la persona que vuelva a iniciar sesión) y consent (mostrar la pantalla de consentimiento aunque ya se hubiera aprobado). Se admite max_age. Consentimiento recordado: si la persona ya aprobó los mismos scopes, vuelve directamente a tu app. Si más adelante pides más, la pantalla de consentimiento muestra solo lo nuevo.

Scopes y claims

Pide solo lo que necesitas. Fuera de esta tabla no existe nada: ni contactos, ni archivos, ni contraseñas.

ScopeClaims
openid (obligatorio)sub: un ID aleatorio para la persona, igual para todas las apps de tu organización y distinto para cualquier otra organización
profilename (si está definido) y locale
emailemail y email_verified (siempre true)
addressaddress (formatted, street_address, locality, region, postal_code, country), solo si la persona guardó una
organisationsorganisations: las organizaciones que la persona marca en la pantalla de consentimiento, cada una con id, name, country, register_number, vat, vat_verified (true solo si el servicio VIES de la UE ha confirmado el número de IVA), verified, verification (ver Organizaciones verificadas) y role
offline_accesssin claims; además recibes un refresh token

Los claims aparecen en el ID token para los scopes que la persona aprobó. El endpoint de información del usuario devuelve el mismo conjunto, con los valores actuales.

Organizaciones verificadas

Dazr verifica una organización cuando su administrador, u otra persona que puede firmar en su nombre, firma una breve declaración con una firma electrónica cualificada (QES). Es instantáneo y funciona en todos los países de la UE. Sin firma cualificada, el representante puede enviar un extracto del registro mercantil y un documento de identidad; Dazr los revisa a mano. Cada organización de la claim organisations lleva su verificación actual:

verification.statusSignificado
unverifiedNo verificada, o una verificación falló, caducó o se revocó (por ejemplo tras cambiar la razón social o el número de registro).
pendingLa verificación está en curso: se ha pedido al representante que firme, o Dazr está revisando documentos.
verifiedVerificada. method es qes, qes_org (el propio certificado indica la organización, por número de IVA o de registro), documents o extract, y verified_at es un tiempo Unix.

El booleano verified se mantiene por compatibilidad y equivale a verification.status === 'verified'. Cada organización tiene también un id, el mismo que usan los webhooks.

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

Exigir una organización verificada

En la consola, pon Exigir una organización verificada en Verificación iniciada o Verificada, o pídelo por solicitud con acr_values=urn:dazr:org:verification-started o acr_values=urn:dazr:org:verified (se aplica la más estricta). Tu app también debe pedir el scope organisations.

Quien no tiene una organización válida la completa en el mismo inicio de sesión: su nombre, la organización (con el número de IVA comprobado en VIES) y luego la verificación. Puede firmar en el acto, recibir la declaración por correo o indicar a otra persona que firme. Después marca la organización en la pantalla de consentimiento (solo se pueden marcar organizaciones válidas) y vuelve a tu URI de redirección.

Si otra persona aún tiene que firmar, la persona vuelve con estado pending, también si pediste Verificada: recibes un código, la claim dice pending y el acr del ID token es urn:dazr:org:verification-started en lugar de urn:dazr:org:verified. Decide siempre según verification.status, nunca por el hecho de que el inicio de sesión funcionó. Con prompt=none recibes interaction_required si no se cumple el requisito.

Los cambios del ajuste se aplican desde el próximo inicio de sesión. Quien ya ha iniciado sesión no se desconecta: las respuestas de refresh, el endpoint userinfo y los webhooks llevan siempre el estado actual, así que úsalos para las sesiones existentes.

Webhooks

Añade una URL de webhook (https) en la consola. Ves el secreto de firma una sola vez; puedes renovarlo. Dazr envía organisation.verification.updated cuando una organización que uno de tus usuarios compartió con tu app pasa a pending, verified, failed o unverified. Las apps que nunca recibieron una organización mediante consentimiento nunca se enteran.

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 es pending, verified, failed o unverified. subjects son los valores sub, en tu app, de las personas que le compartieron esta organización. Comprueba la firma antes de fiarte del contenido:

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

Responde con un 2xx en menos de 8 segundos. Si no, Dazr reintenta tras unos 5 minutos, 30 minutos, 2, 6, 12 y 24 horas, y después marca la entrega como fallida. Cada intento lleva el mismo ID Dazr-Delivery, así que ignora los duplicados. La consola muestra las entregas recientes, puede reenviar una y enviar un evento de prueba (webhook.test). Consultar periódicamente el endpoint userinfo también funciona.

Tokens y duración

TokenDuración y reglas
Código de autorización60 segundos, de un solo uso, vinculado a tu cliente, a la URI de redirección y al challenge PKCE. Usar un código dos veces revoca los tokens que emitió.
Access token10 minutos. Un JWT firmado (ES256, typ at+jwt) con aud = tu ID de cliente. Envíalo como Authorization: Bearer.
ID token10 minutos. ES256, con iss, sub, aud, exp, iat, auth_time, nonce y amr cuando se conoce.
Refresh tokenSolo con offline_access. Cambia con cada uso: guarda siempre el nuevo. Volver a enviar un refresh token antiguo revoca todo el inicio de sesión. Caduca tras 30 días sin uso y como máximo a los 180 días.

Las claves de firma se renuevan. Elige siempre la clave por su kid en el JWKS y vuelve a cargar el JWKS cuando veas un kid desconocido.

Errores

Mientras tu ID de cliente y tu URI de redirección sean válidos, los errores vuelven a tu URI de redirección como error, error_description, state e iss: por ejemplo invalid_request (como PKCE ausente o plain), invalid_scope, unsupported_response_type, access_denied (la persona canceló, o la app está en pruebas y la persona no es miembro), login_required, consent_required e interaction_required (con prompt=none, cuando falta una organización verificada exigida). Con un cliente desconocido o una URI de redirección no registrada, Dazr muestra una página de error y nunca redirige. El endpoint de tokens responde con errores JSON como invalid_client, invalid_grant y unsupported_grant_type.

Cierre de sesión

Para cerrar la sesión de alguien, envía a la persona al endpoint de cierre de sesión con id_token_hint (o client_id), un post_logout_redirect_uri opcional registrado en los ajustes de la app y state. Dazr le pregunta si quiere cerrar también la sesión de Dazr Identity en ese dispositivo y después la devuelve. Revoca en el endpoint de revocación los refresh tokens que ya no necesites.

Pruebas y puesta en marcha

Una app nueva está en pruebas: solo pueden iniciar sesión los miembros de tu organización, y la pantalla de consentimiento lo indica. Para publicarla, haz verificar tu organización en Dazr Identity, añade una URI de redirección https:// y solicita una revisión en la consola. Cambiar el nombre, la finalidad, la política de privacidad o los datos de una app publicada requiere una nueva revisión.

El botón

Usa el texto «Sign in with Dazr Identity» y el logotipo de Dazr como se muestra abajo. Puedes cambiar el tamaño, pero no el logotipo, los colores ni el texto. Enlázalo a tu propia ruta que inicia el flujo.

<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 ejemplo mínimo en Node.js

Node 18 o posterior, sin dependencias. Una app de servidor web con secreto de cliente; para un cliente público, quita la cabecera Authorization y envía client_id en el cuerpo.

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

Límites

Los endpoints de autorización y de tokens tienen límite de peticiones. Si recibes HTTP 429, espera y vuelve a intentarlo; no reintentes en un bucle rápido.

Tus obligaciones

Tu organización es responsable independiente del tratamiento de los datos que recibe. Las Condiciones para desarrolladores de Dazr Identity establecen qué puedes hacer con ellos, cómo protegerlos y cómo notificar las brechas. Preguntas: hello@dazr.eu.