> ## 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 com Okta e Auth0

> Delegue a verificação de identidade na Didit via Okta Identity Engine (BYO IDV) ou Auth0, usando OpenID Connect e Pushed Authorization Requests.

Didit expõe um servidor de autorização OpenID Connect concebido para a verificação de identidade delegada.
Implementa o contrato que a estrutura "bring your own identity verification vendor" (BYO IDV) da Okta espera: Pushed Authorization Requests (PAR, RFC 9126), o fluxo de código de autorização com PKCE e um ID token que transporta um objeto OpenID Connect `verified_claims`.

Os mesmos endpoints funcionam para qualquer parte confiadora que siga este perfil, incluindo as Actions de post-login do Auth0.

## Endpoints

Todos os endpoints estão sob o 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` |

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

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

| Finalidade | Endpoint |
| - | - |
| Pushed Authorization Request | `POST {base}/par` |
| Autorização | `GET {base}/authorize` |
| Token | `POST {base}/token` |
| JWKS | `GET {base}/jwks` |
| Discovery | `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 através do endpoint JWKS.

## Fluxo

1. A parte confiadora envia pedido de autorização para `POST /par`, incluindo o desafio PKCE, `state`, `nonce`, o seu `redirect_uri` registado, os scopes `openid profile identity_assurance` e os claims que pretende que sejam verificados.
   A Didit devolve um `request_uri` de utilização única, válido durante 60 segundos.
2. A parte confiadora redireciona o navegador do utilizador para `GET /authorize?request_uri=...`.
   A Didit cria uma sessão de verificação e encaminha o utilizador para o fluxo de verificação alojado.
3. Quando o utilizador termina, a Didit avalia a decisão de verificação, compara a identidade extraída com os claims enviados e devolve o utilizador ao `redirect_uri` da parte confiadora com um `code` de utilização única e o `state` original.
4. A parte confiadora troca o código em `POST /token` com o seu `code_verifier` e recebe um ID token assinado.

## Pedido de claims

Envie os claims a comparar dentro do parâmetro `claims` padrão do OpenID Connect no pedido PAR.
Tanto `given_name` como `family_name` são suportados, e ambos são sempre comparados de forma aproximada.
`verified_claims` é um array, em conformidade com o perfil BYO IDV da 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 transporta um array `verified_claims`. `assurance_level` é `VERIFIED` apenas quando a sessão de verificação foi aprovada **e** todos os claims pedidos 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 na Didit, por isso pode consultar a verificação na consola ou através da API de sessões.

> [!NOTE]
>
> Considere o utilizador verificado apenas quando `trust_framework` for `IDV-DELEGATED` **e** `assurance_level` for `VERIFIED`.
> Qualquer outra combinação significa que a verificação não teve êxito.

## Configurar a Okta

Peça à Didit para ativar a verificação de identidade para as suas aplicações de produção e de sandbox, associe cada uma ao respetivo fluxo de verificação e registe o URL de retorno exato para cada organização da Okta.
Uma aplicação ou chave de API existente, por si só, não ativa esta integração.

Na Okta Admin Console, adicione um fornecedor personalizado de verificação de identidade e preencha os valores abaixo.
A Okta cria a integração como um fornecedor de identidade do tipo `IDV_STANDARD` com o protocolo `ID_PROOFING`, e depois referencia-a numa regra de Account Management Policy.

| Campo | Valor |
| - | - |
| Issuer | `https://apx.didit.me/auth/idv` |
| URL do pedido PAR | `https://apx.didit.me/auth/idv/par` |
| URL de autorização | `https://apx.didit.me/auth/idv/authorize` |
| URL do token | `https://apx.didit.me/auth/idv/token` |
| URL do JWKS | `https://apx.didit.me/auth/idv/jwks` |
| Scopes | `openid`, `profile`, `identity_assurance` |
| Client ID | O ID de cliente da aplicação Didit correspondente |
| Client secret | A chave de API principal dessa aplicação, disponível em **Developers > API keys** na consola da Didit |

Configure a produção e o sandbox como integrações de verificação de identidade separadas, com as respetivas credenciais.
Use os endpoints acima para ambas.
O segredo do cliente é obrigatório, embora o fluxo também utilize PKCE.
Uma chave de API nomeada criada à parte não pode substituir a chave de API principal da aplicação nesta integração.

Indique à Didit o seu URL de retorno para que seja registado no seu cliente.
A Okta constrói-o como `{yourOktaOrgUrl}/idp/identity-verification/callback`.

## Configurar o Auth0

O Auth0 não tem uma opção nativa para fornecedores de IDV, por isso a verificação é executada a partir de uma Action de post-login que redireciona o utilizador para o mesmo fluxo.
Guarde `DIDIT_IDV_BASE`, `DIDIT_CLIENT_ID` e `DIDIT_CLIENT_SECRET` como segredos da Action, e registe `https://{yourAuth0Domain}/continue` como URI de redirecionamento na Didit.

> [!NOTE]
>
> O Auth0 acrescenta o seu próprio `state` ao redirecionamento para `/authorize`, para poder retomar o login. A Didit adota esse `state` e devolve-o a `/continue`, o que permite ao Auth0 concluir a transação. Não inclua o state do Auth0 no corpo do PAR, porque só é gerado no 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]
>
> Guarde o `code_verifier` do PKCE por transação de login (identificado por `event.transaction.id`), e não num único campo de `app_metadata`.
> Se um utilizador repetir a tentativa ou iniciar um login sobreposto, um campo partilhado único é sobrescrito e a troca do token falha com `invalid_grant`.

## Erros

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

Os códigos de autorização, os valores de `request_uri` e as sessões de verificação são todos de utilização única.


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