> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Verificación de identidad con Okta y Auth0

> Delega la verificación de identidad a Didit desde Okta Identity Engine (BYO IDV) o Auth0 con OpenID Connect y Pushed Authorization Requests.

Didit expone un servidor de autorización OpenID Connect diseñado específicamente para la verificación de identidad delegada.
Implementa el contrato que espera el marco "bring your own identity verification vendor" (BYO IDV) de Okta: Pushed Authorization Requests (PAR, RFC 9126), el flujo de código de autorización con PKCE y un ID token que contiene un objeto `verified_claims` de OpenID Connect.

Los mismos endpoints funcionan para cualquier relying party que implemente este perfil, incluidas las Actions post-login de Auth0.

## Endpoints

Todos los endpoints se encuentran bajo la URL base del puente de verificación de identidad.

| Entorno | URL base |
| - | - |
| Producción | `https://apx.didit.me/auth/idv` |
| Sandbox | `https://apx.didit.me/auth/idv` |

Producción y sandbox usan los mismos endpoints.
La aplicación asociada a tus credenciales de cliente determina el entorno.
Usa credenciales de cliente independientes y un flujo de verificación (workflow) que pertenezca a la aplicación correspondiente en cada entorno.
Consulta [Sandbox y datos de prueba](https://docs.didit.me/es/integration/sandbox-testing.md) para obtener resultados de verificación simulados.

El documento de descubrimiento en `{base}/.well-known/openid-configuration` los anuncia todos:

| Propósito | Endpoint |
| - | - |
| Pushed Authorization Request | `POST {base}/par` |
| Autorización | `GET {base}/authorize` |
| Token | `POST {base}/token` |
| JWKS | `GET {base}/jwks` |
| Descubrimiento | `GET {base}/.well-known/openid-configuration` |

La autenticación del cliente usa `client_secret_basic` o `client_secret_post`.
El flujo de código de autorización requiere PKCE con `code_challenge_method=S256`.
Los tokens de ID se firman con RS256; verifícalos con el endpoint de JWKS.

## Flujo

1. La relying party envía la solicitud de autorización a `POST /par`, con el desafío PKCE, `state`, `nonce`, su `redirect_uri` registrada, los scopes `openid profile identity_assurance` y los claims que quiere verificar.
   Didit devuelve un `request_uri` de un solo uso, válido durante 60 segundos.
2. La relying party redirige el navegador del usuario a `GET /authorize?request_uri=...`.
   Didit crea una sesión de verificación y lleva al usuario al flujo de verificación alojado.
3. Cuando el usuario termina, Didit evalúa la decisión de verificación, compara la identidad extraída con los claims enviados y devuelve al usuario a la `redirect_uri` de la relying party con un `code` de un solo uso y el `state` original.
4. La relying party intercambia el código en `POST /token` con su `code_verifier` y recibe un ID token firmado.

## Solicitar claims

Envía los claims que quieres comparar dentro del parámetro estándar `claims` de OpenID Connect en la solicitud PAR.
Se admiten tanto `given_name` como `family_name`, y ambos se comparan siempre de forma difusa.
`verified_claims` es un array, conforme al perfil BYO IDV de Okta y a la respuesta del ID token que se muestra a continuación.

```json theme={null}
{
  "id_token": {
    "verified_claims": [
      {
        "verification": {
          "trust_framework": { "value": "IDV-DELEGATED" },
          "assurance_level": { "value": "VERIFIED" }
        },
        "claims": {
          "given_name": { "value": "Lucas", "fuzzy": true },
          "family_name": { "value": "Fraggioli", "fuzzy": true }
        }
      }
    ]
  }
}
```

## El ID token

El ID token contiene un array `verified_claims`. `assurance_level` es `VERIFIED` solo cuando la sesión de verificación fue aprobada **y** todos los claims solicitados coincidieron con el documento verificado.
En caso contrario, es `FAILED`.

```json theme={null}
{
  "iss": "https://apx.didit.me/auth/idv",
  "sub": "eea9c5ca-63e6-437b-8e94-131c33f5dce8",
  "aud": "<your client id>",
  "nonce": "<your nonce>",
  "verified_claims": [
    {
      "verification": {
        "trust_framework": "IDV-DELEGATED",
        "assurance_level": "VERIFIED",
        "time": "2026-07-09T11:59:33Z",
        "verification_process": "eea9c5ca-63e6-437b-8e94-131c33f5dce8"
      },
      "claims": {
        "given_name": { "value": "Lucas Agustin", "fuzzy": true },
        "family_name": { "value": "Fraggioli", "fuzzy": true }
      }
    }
  ]
}
```

`verification_process` es el ID de la sesión de Didit, así que puedes consultar la verificación en la consola o mediante la API de sesiones.

> [!NOTE]
>
> Considera al usuario verificado solo cuando `trust_framework` sea `IDV-DELEGATED` **y** `assurance_level` sea `VERIFIED`.
> Cualquier otra combinación significa que la verificación no tuvo éxito.

## Configurar Okta

Pide a Didit que habilite la verificación de identidad para tus aplicaciones de producción y sandbox, que asocie cada una a su flujo de verificación y que registre la URL de callback exacta de cada tenant de Okta.
Una aplicación o una clave de API existentes no bastan por sí solas para habilitar esta integración.

En la Okta Admin Console, añade un proveedor personalizado de verificación de identidad y completa los valores siguientes.
Okta crea la integración como un proveedor de identidad de tipo `IDV_STANDARD` con el protocolo `ID_PROOFING`, y después la referencias desde una regla de Account Management Policy.

| Campo | Valor |
| - | - |
| Issuer | `https://apx.didit.me/auth/idv` |
| PAR request URL | `https://apx.didit.me/auth/idv/par` |
| Authorize URL | `https://apx.didit.me/auth/idv/authorize` |
| Token URL | `https://apx.didit.me/auth/idv/token` |
| JWKS URL | `https://apx.didit.me/auth/idv/jwks` |
| Scopes | `openid`, `profile`, `identity_assurance` |
| Client ID | El client ID de la aplicación de Didit correspondiente |
| Client secret | La clave de API principal (Primary API key) de esa aplicación, disponible en **Developers > API keys** en la Didit Console |

Configura producción y sandbox como integraciones de verificación de identidad independientes, cada una con sus credenciales.
Usa los endpoints anteriores para ambas.
El client secret es obligatorio aunque el flujo también use PKCE.
Una clave de API con nombre creada por separado no puede sustituir a la clave de API principal de la aplicación en esta integración.

Proporciona a Didit tu URL de callback para que la registre en tu cliente.
Okta la construye como `{yourOktaOrgUrl}/idp/identity-verification/callback`.

## Configurar Auth0

Auth0 no tiene un espacio nativo para proveedores de IDV, así que la verificación se ejecuta desde una Action post-login que redirige al usuario al mismo flujo.
Guarda `DIDIT_IDV_BASE`, `DIDIT_CLIENT_ID` y `DIDIT_CLIENT_SECRET` como secretos de la Action y registra `https://{yourAuth0Domain}/continue` como URI de redirección en Didit.

> [!NOTE]
>
> Auth0 añade su propio `state` a la redirección a `/authorize` para poder reanudar el inicio de sesión. Didit adopta ese `state` y lo devuelve a `/continue`, lo que permite a Auth0 completar la transacción: no tienes que pasar el state de Auth0 en el cuerpo de la PAR (no se genera hasta la redirección).

```javascript theme={null}
const crypto = require("crypto");

// Correlate each login's PKCE verifier with its own transaction so a retried
// or overlapping login can't overwrite it (which would fail the token exchange).
const txKey = (event) =>
  event.transaction?.id || event.request?.query?.state || "default";

const basicAuth = (event) =>
  Buffer.from(
    `${event.secrets.DIDIT_CLIENT_ID}:${event.secrets.DIDIT_CLIENT_SECRET}`
  ).toString("base64");

exports.onExecutePostLogin = async (event, api) => {
  if (event.user.app_metadata?.idv_status === "verified") return;

  const verifier = crypto.randomBytes(48).toString("base64url");
  const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");

  const pending = { ...event.user.app_metadata?.idv_pkce };
  pending[txKey(event)] = verifier;
  api.user.setAppMetadata("idv_pkce", pending);

  const params = {
    response_type: "code",
    client_id: event.secrets.DIDIT_CLIENT_ID,
    redirect_uri: `https://${event.request.hostname}/continue`,
    scope: "openid profile identity_assurance",
    code_challenge: challenge,
    code_challenge_method: "S256",
    login_hint: event.user.email ?? "",
  };

  // Ask Didit to match the scanned document against this user's known name, so an
  // approved session for a different person can't mark this account verified.
  if (event.user.given_name && event.user.family_name) {
    params.claims = JSON.stringify({
      id_token: {
        verified_claims: [
          {
            verification: { trust_framework: { value: "IDV-DELEGATED" } },
            claims: {
              given_name: { value: event.user.given_name, fuzzy: true },
              family_name: { value: event.user.family_name, fuzzy: true },
            },
          },
        ],
      },
    });
  }

  const res = await fetch(`${event.secrets.DIDIT_IDV_BASE}/par`, {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      Authorization: `Basic ${basicAuth(event)}`,
    },
    body: new URLSearchParams(params).toString(),
  });
  if (!res.ok) return api.access.deny("Identity verification is unavailable.");

  const { request_uri } = await res.json();
  api.redirect.sendUserTo(`${event.secrets.DIDIT_IDV_BASE}/authorize`, {
    query: { request_uri, client_id: event.secrets.DIDIT_CLIENT_ID },
  });
};

exports.onContinuePostLogin = async (event, api) => {
  const code = event.request.query.code;
  const pending = { ...event.user.app_metadata?.idv_pkce };
  const key = txKey(event);
  const verifier = pending[key];

  // Consume the verifier so it can't be reused.
  if (key in pending) {
    delete pending[key];
    api.user.setAppMetadata("idv_pkce", pending);
  }
  if (!code || !verifier) return api.access.deny("Identity verification was not completed.");

  const res = await fetch(`${event.secrets.DIDIT_IDV_BASE}/token`, {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      Authorization: `Basic ${basicAuth(event)}`,
    },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      code,
      code_verifier: verifier,
      redirect_uri: `https://${event.request.hostname}/continue`,
    }).toString(),
  });
  if (!res.ok) return api.access.deny("Identity verification could not be confirmed.");

  const { id_token } = await res.json();
  // This token comes directly from Didit's token endpoint over TLS with client
  // authentication (the trusted back-channel), so decoding is sufficient here.
  // For defense-in-depth, verify the RS256 signature against the JWKS endpoint
  // (plus iss / aud / exp / nonce) before trusting it.
  const payload = JSON.parse(Buffer.from(id_token.split(".")[1], "base64url").toString());
  const verification = payload.verified_claims?.[0]?.verification ?? {};

  if (verification.trust_framework === "IDV-DELEGATED" && verification.assurance_level === "VERIFIED") {
    api.user.setAppMetadata("idv_status", "verified");
    api.user.setAppMetadata("idv_session", verification.verification_process ?? null);
  } else {
    api.access.deny("Identity verification failed.");
  }
};
```

> [!NOTE]
>
> Guarda el `code_verifier` de PKCE por cada transacción de inicio de sesión (con `event.transaction.id` como clave), no en un único campo de `app_metadata`.
> Si un usuario vuelve a intentarlo o realiza inicios de sesión superpuestos, un único campo compartido se sobrescribe y el intercambio del token falla con `invalid_grant`.

## Errores

Los errores siguen RFC 6749 y RFC 9126.
Si la autenticación del cliente falla, se devuelve `401` con `{"error": "invalid_client"}`; un `request_uri` o un código de autorización expirado o reutilizado devuelve `400` con `invalid_request` o `invalid_grant`.

Los códigos de autorización, los valores `request_uri` y las sesiones de verificación son todos de un solo uso.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.