> ## 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 dell'identità con Okta e Auth0

> Delega la verifica dell'identità a Didit da Okta Identity Engine (BYO IDV) o Auth0, tramite OpenID Connect con Pushed Authorization Requests.

Didit espone un server di autorizzazione OpenID Connect progettato appositamente per la verifica dell'identità delegata.
Implementa il contratto che si aspetta il framework "bring your own identity verification vendor" (BYO IDV) di Okta: Pushed Authorization Requests (PAR, RFC 9126), il flusso del codice di autorizzazione con PKCE e un ID token che contiene un oggetto OpenID Connect `verified_claims`.

Gli stessi endpoint funzionano per qualsiasi parte che fa affidamento e che adotta questo profilo, inclusi gli Actions post-login di Auth0.

## Endpoint

Tutti gli endpoint si trovano sotto l'URL di base del ponte di verifica dell'identità.

| Ambiente | URL di base |
| - | - |
| Produzione | `https://apx.didit.me/auth/idv` |
| Sandbox | `https://apx.didit.me/auth/idv` |

Produzione e sandbox usano gli stessi endpoint.
L'applicazione associata alle tue credenziali client determina l'ambiente.
Usa credenziali client separate e un workflow appartenente all'applicazione corrispondente per ciascun ambiente.
Consulta [Sandbox e dati di test](https://docs.didit.me/it/integration/sandbox-testing.md) per gli esiti di verifica simulati.

Il documento di discovery in `{base}/.well-known/openid-configuration` li elenca tutti:

| Scopo | Endpoint |
| - | - |
| Pushed Authorization Request | `POST {base}/par` |
| Autorizzazione | `GET {base}/authorize` |
| Token | `POST {base}/token` |
| JWKS | `GET {base}/jwks` |
| Discovery | `GET {base}/.well-known/openid-configuration` |

L'autenticazione del client usa `client_secret_basic` o `client_secret_post`.
Il flusso del codice di autorizzazione richiede PKCE con `code_challenge_method=S256`.
Gli ID token sono firmati con RS256; verificali tramite l'endpoint JWKS.

## Flusso

1. La parte che fa affidamento invia la richiesta di autorizzazione a `POST /par`, includendo la sfida PKCE, `state`, `nonce`, il `redirect_uri` registrato, gli scope `openid profile identity_assurance` e i claim da verificare.
   Didit restituisce un `request_uri` monouso valido per 60 secondi.
2. La parte che fa affidamento reindirizza il browser dell'utente a `GET /authorize?request_uri=...`.
   Didit crea una sessione di verifica e porta l'utente nel flusso di verifica ospitato.
3. Al termine, Didit valuta la decisione di verifica, confronta l'identità estratta con i claim inviati e riporta l'utente al `redirect_uri` della parte che fa affidamento con un `code` monouso e lo `state` originale.
4. La parte che fa affidamento scambia il codice in `POST /token` con il proprio `code_verifier` e riceve un ID token firmato.

## Richiesta dei claim

Invia i claim da confrontare nel parametro standard `claims` di OpenID Connect della richiesta PAR.
Sono supportati sia `given_name` sia `family_name`, e vengono sempre confrontati mediante una corrispondenza approssimativa.
`verified_claims` è un array, in linea con il profilo BYO IDV di Okta e con la risposta dell'ID token riportata di seguito.

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

## L'ID token

L'ID token contiene un array `verified_claims`. `assurance_level` è `VERIFIED` solo quando la sessione di verifica è stata approvata **e** ogni claim richiesto corrisponde al documento verificato.
Altrimenti è `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` è l'ID della sessione Didit, così puoi consultare la verifica nella console o tramite l'API delle sessioni.

> [!NOTE]
>
> Considera l'utente verificato solo quando `trust_framework` è `IDV-DELEGATED` **e** `assurance_level` è `VERIFIED`.
> Qualsiasi altra combinazione significa che la verifica non è riuscita.

## Configurare Okta

Chiedi a Didit di abilitare la verifica dell'identità per le tue applicazioni di produzione e sandbox, di associare ciascuna al proprio workflow di verifica e di registrare l'URL di callback esatto per ogni tenant Okta.
Un'applicazione o una chiave API esistente da sola non abilita questa integrazione.

Nell'Okta Admin Console, aggiungi un fornitore personalizzato di verifica dell'identità e compila i valori seguenti.
Okta crea l'integrazione come identity provider di tipo `IDV_STANDARD` con protocollo `ID_PROOFING`, quindi la referenzi da una regola di Account Management Policy.

| Campo | Valore |
| - | - |
| 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` |
| Scope | `openid`, `profile`, `identity_assurance` |
| Client ID | Il client ID dell'applicazione Didit corrispondente |
| Client secret | La chiave API primaria di quell'applicazione, disponibile in **Developers > API keys** nella Console Didit |

Configura produzione e sandbox come integrazioni di verifica dell'identità separate, con le rispettive credenziali.
Usa gli endpoint sopra per entrambe.
Il client secret è obbligatorio anche se il flusso usa PKCE.
Una chiave API denominata creata separatamente non può sostituire la chiave API primaria dell'applicazione per questa integrazione.

Fornisci a Didit il tuo URL di callback in modo che venga registrato sul tuo client.
Okta lo costruisce come `{yourOktaOrgUrl}/idp/identity-verification/callback`.

## Configurare Auth0

Auth0 non ha uno slot nativo per un fornitore IDV, quindi la verifica viene eseguita da un Action post-login che reindirizza l'utente allo stesso flusso.
Memorizza `DIDIT_IDV_BASE`, `DIDIT_CLIENT_ID` e `DIDIT_CLIENT_SECRET` come segreti dell'Action, e registra `https://{yourAuth0Domain}/continue` come redirect URI presso Didit.

> [!NOTE]
>
> Auth0 aggiunge il proprio `state` al redirect verso `/authorize` per poter riprendere il login. Didit adotta quello `state` e lo restituisce a `/continue`, ed è ciò che permette ad Auth0 di completare la transazione: non devi inserire lo state di Auth0 nel corpo della PAR, perché viene generato solo al momento del redirect.

```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]
>
> Memorizza il `code_verifier` PKCE per ogni transazione di login (indicizzato con `event.transaction.id`), non in un unico campo `app_metadata`.
> Se un utente riprova o esegue un login sovrapposto, un unico campo condiviso viene sovrascritto e lo scambio del token fallisce con `invalid_grant`.

## Errori

Gli errori seguono la RFC 6749 e la RFC 9126.
Un'autenticazione del client non riuscita restituisce `401` con `{"error": "invalid_client"}`; un `request_uri` o un codice di autorizzazione scaduto o già utilizzato restituisce `400` con `invalid_request` o `invalid_grant`.

I codici di autorizzazione, i valori `request_uri` e le sessioni di verifica sono tutti monouso.


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