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

# Verificação de identidade no Okta e Auth0

> Delegue a verificação de identidade à Didit no Okta Identity Engine (BYO IDV) ou Auth0 via OpenID Connect com Pushed Authorization Requests.

O Didit expõe um servidor de autorização OpenID Connect projetado especificamente para verificação de identidade delegada.
Ele implementa o contrato esperado pelo framework "bring your own identity verification vendor" (BYO IDV) do Okta: Pushed Authorization Requests (PAR, RFC 9126), o fluxo de código de autorização com PKCE e um ID token que contém um objeto `verified_claims` do OpenID Connect.

Os mesmos endpoints funcionam com qualquer relying party compatível com esse perfil, incluindo as Actions pós-login do Auth0.

## Endpoints

Todos os endpoints estão disponíveis na URL base da ponte de verificação de identidade.

| Ambiente | URL base |
| - | - |
| Produção | `https://apx.didit.me/auth/idv` |
| Sandbox | `https://apx.didit.me/auth/idv` |

Produção e sandbox usam os mesmos endpoints.
A aplicação associada às suas credenciais de cliente determina o ambiente.
Use credenciais de cliente separadas e um workflow de verificação pertencente à aplicação correspondente para cada ambiente.
Consulte [Sandbox e dados de teste](https://docs.didit.me/pt-br/integration/sandbox-testing.md) para ver resultados de verificação simulados.

O documento de descoberta em `{base}/.well-known/openid-configuration` lista todos eles:

| Finalidade | Endpoint |
| - | - |
| Pushed Authorization Request | `POST {base}/par` |
| Autorização | `GET {base}/authorize` |
| Token | `POST {base}/token` |
| JWKS | `GET {base}/jwks` |
| Descoberta | `GET {base}/.well-known/openid-configuration` |

A autenticação do cliente usa `client_secret_basic` ou `client_secret_post`.
O fluxo de código de autorização exige PKCE com `code_challenge_method=S256`.
Os ID tokens são assinados com RS256. Verifique-os usando o endpoint JWKS.

## Fluxo

1. A relying party envia a requisição de autorização para `POST /par`, incluindo o desafio PKCE, `state`, `nonce`, sua `redirect_uri` registrada, os escopos `openid profile identity_assurance` e os claims que deseja verificar.
   O Didit retorna um `request_uri` de uso único, válido por 60 segundos.
2. A relying party redireciona o navegador do usuário para `GET /authorize?request_uri=...`.
   O Didit cria uma sessão de verificação e encaminha o usuário para o fluxo de verificação hospedado.
3. Quando o usuário termina, o Didit avalia a decisão da verificação, compara a identidade extraída com os claims enviados e devolve o usuário para a `redirect_uri` da parte confiável com um `code` de uso único e o `state` original.
4. A relying party troca o código no endpoint `POST /token` usando seu `code_verifier` e recebe um ID token assinado.

## Solicitação de claims

Envie os claims a serem comparados dentro do parâmetro padrão OpenID Connect `claims` da requisição PAR.
Tanto `given_name` quanto `family_name` são aceitos, e ambos são sempre comparados de forma aproximada (fuzzy).
`verified_claims` é um array, de acordo com o perfil BYO IDV do Okta e com a resposta do ID token abaixo.

```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 }
        }
      }
    ]
  }
}
```

## O ID token

O ID token contém um array `verified_claims`. `assurance_level` é `VERIFIED` somente quando a sessão de verificação foi aprovada **e** todos os claims solicitados corresponderam ao documento verificado.
Caso contrário, é `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` é o ID da sessão no Didit, para que você possa consultar a verificação no console ou pela API de sessões.

> [!NOTE]
>
> Considere o usuário verificado somente quando `trust_framework` for `IDV-DELEGATED` **e** `assurance_level` for `VERIFIED`.
> Qualquer outra combinação significa que a verificação não foi bem-sucedida.

## Configurar o Okta

Peça ao Didit para habilitar a verificação de identidade nas suas aplicações de produção e sandbox, associar cada uma ao seu fluxo de verificação e registrar a URL de retorno exata de cada tenant do Okta.
Uma aplicação ou chave de API existente, por si só, não habilita esta integração.

No Okta Admin Console, adicione um fornecedor personalizado de verificação de identidade e preencha os valores abaixo.
O Okta cria a integração como um provedor de identidade do tipo `IDV_STANDARD` com o protocolo `ID_PROOFING`. Em seguida, você faz referência a ele em uma regra de Account Management Policy.

| Campo | Valor |
| - | - |
| Emissor | `https://apx.didit.me/auth/idv` |
| URL de requisição PAR | `https://apx.didit.me/auth/idv/par` |
| URL de autorização | `https://apx.didit.me/auth/idv/authorize` |
| URL de token | `https://apx.didit.me/auth/idv/token` |
| URL do JWKS | `https://apx.didit.me/auth/idv/jwks` |
| Escopos | `openid`, `profile`, `identity_assurance` |
| ID do cliente | O ID do cliente da aplicação do Didit correspondente |
| Segredo do cliente | A chave de API principal dessa aplicação, disponível em **Developers > API keys** no Didit Console |

Configure produção e sandbox como integrações de verificação de identidade separadas, cada uma com suas respectivas credenciais.
Use os endpoints acima para ambas.
O segredo do cliente é obrigatório, mesmo que o fluxo também use PKCE.
Uma chave de API nomeada criada separadamente não pode substituir a chave de API principal da aplicação nesta integração.

Informe sua URL de retorno ao Didit para que ela seja registrada no seu cliente.
O Okta a monta como `{yourOktaOrgUrl}/idp/identity-verification/callback`.

## Configurar o Auth0

O Auth0 não tem uma opção nativa de fornecedor de IDV, então a verificação é executada a partir de uma Action pós-login que redireciona o usuário para o mesmo fluxo.
Armazene `DIDIT_IDV_BASE`, `DIDIT_CLIENT_ID` e `DIDIT_CLIENT_SECRET` como secrets da Action e registre `https://{yourAuth0Domain}/continue` como URI de redirecionamento junto ao Didit.

> [!NOTE]
>
> O Auth0 acrescenta seu próprio `state` ao redirecionamento para `/authorize` para poder retomar o login. O Didit adota esse `state` e o devolve para `/continue`, o que permite ao Auth0 concluir a transação. Você não precisa passar o state do Auth0 no corpo da PAR (ele só é gerado no momento do redirecionamento).

```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]
>
> Armazene o `code_verifier` do PKCE por transação de login (usando `event.transaction.id` como chave), e não em um único campo `app_metadata`.
> Se um usuário tentar novamente ou iniciar um login simultâneo, um único campo compartilhado será sobrescrito e a troca de token falhará com `invalid_grant`.

## Erros

Os erros seguem as RFC 6749 e RFC 9126.
Uma falha na autenticação do cliente retorna `401` com `{"error": "invalid_client"}`; um `request_uri` ou código de autorização expirado ou reutilizado retorna `400` com `invalid_request` ou `invalid_grant`.

Códigos de autorização, valores de `request_uri` e sessões de verificação são todos de uso único.


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