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

# Okta & Auth0 Identitätsverifizierung

> Didit übernimmt die Identitätsverifizierung von Okta (BYO IDV) oder Auth0 via OpenID Connect mit Pushed Authorization Requests.

Didit stellt einen OpenID Connect-Autorisierungsserver bereit, der speziell für die delegierte Identitätsverifizierung entwickelt wurde.
Er implementiert den Vertrag, den das „Bring your own identity verification vendor“-Framework (BYO IDV) von Okta erwartet: Pushed Authorization Requests (PAR, RFC 9126), den Authorization Code Flow mit PKCE und ein ID-Token, das ein OpenID Connect `verified_claims`-Objekt enthält.

Dieselben Endpoints funktionieren für jede Relying Party, die dieses Profil unterstützt, einschließlich Auth0 Post-Login Actions.

## Endpoints

Jeder Endpoint befindet sich unter der Basis-URL der Identitätsverifizierungs-Bridge.

| Umgebung | Basis-URL |
| - | - |
| Produktion | `https://apx.didit.me/auth/idv` |
| Sandbox | `https://apx.didit.me/auth/idv` |

Produktion und Sandbox verwenden dieselben Endpoints.
Die Anwendung, die mit Ihren Client-Anmeldedaten verknüpft ist, bestimmt die Umgebung.
Verwenden Sie separate Client-Anmeldedaten und einen Workflow, der zur entsprechenden Anwendung gehört, für jede Umgebung.
Siehe [Sandbox und Testdaten](https://docs.didit.me/de/integration/sandbox-testing.md) für simulierte Verifizierungsergebnisse.

Das Discovery-Dokument unter `{base}/.well-known/openid-configuration` führt sie alle auf:

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

Die Client-Authentifizierung verwendet `client_secret_basic` oder `client_secret_post`.
Der Authorization Code Flow erfordert PKCE mit `code_challenge_method=S256`.
ID-Tokens werden mit RS256 signiert; überprüfen Sie diese anhand des JWKS-Endpoints.

## Flow

1. Die Relying Party sendet den Authorization Request an `POST /par`, einschließlich der PKCE-Challenge, `state`, `nonce`, ihrer registrierten `redirect_uri`, den Scopes `openid profile identity_assurance` und den Claims, die verifiziert werden sollen.
   Didit gibt eine einmalig nutzbare `request_uri` zurück, die für 60 Sekunden gültig ist.
2. Die Relying Party leitet den Browser des Benutzers zu `GET /authorize?request_uri=...` weiter.
   Didit erstellt eine Verifizierungssitzung und leitet den Benutzer in den gehosteten Verifizierungs-Workflow.
3. Wenn der Benutzer den Vorgang abschließt, wertet Didit die Verifizierungsentscheidung aus, gleicht die extrahierte Identität mit den per Push übermittelten Claims ab und leitet den Benutzer mit einem Einmal-`code` und dem ursprünglichen `state` zur `redirect_uri` der Relying Party zurück.
4. Die Relying Party tauscht den Code an `POST /token` mit ihrem `code_verifier` aus und erhält ein signiertes ID-Token.

## Claims anfordern

Senden Sie die abzugleichenden Claims innerhalb des standardmäßigen OpenID Connect-Parameters `claims` des PAR-Requests.
Sowohl `given_name` als auch `family_name` werden unterstützt, und beide werden stets fehlertolerant abgeglichen.
`verified_claims` ist ein Array, passend zum BYO-IDV-Profil von Okta und der unten dargestellten ID-Token-Antwort.

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

## Das ID-Token

Das ID-Token enthält ein `verified_claims`-Array. `assurance_level` ist nur dann `VERIFIED`, wenn die Verifizierungssitzung genehmigt wurde **und** jeder angeforderte Claim mit dem verifizierten Dokument übereinstimmt.
Andernfalls lautet der Wert `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` ist die Sitzungs-ID von Didit, sodass Sie die Verifizierung in der Konsole oder über die Sitzungs-API nachschlagen können.

> [!NOTE]
>
> Betrachten Sie den Benutzer nur dann als verifiziert, wenn `trust_framework` gleich `IDV-DELEGATED` **und** `assurance_level` gleich `VERIFIED` ist.
> Jede andere Kombination bedeutet, dass die Verifizierung nicht erfolgreich war.

## Okta konfigurieren

Bitten Sie Didit darum, die Identitätsverifizierung für Ihre Produktions- und Sandbox-Anwendungen zu aktivieren, jede mit ihrem jeweiligen Verifizierungs-Workflow zu verknüpfen und die genaue Callback-URL für jeden Okta-Tenant zu registrieren.
Eine bestehende Anwendung oder ein API-Schlüssel allein aktiviert diese Integration nicht.

Fügen Sie in der Okta Admin Console einen benutzerdefinierten Anbieter für Identitätsverifizierung hinzu und tragen Sie die unten stehenden Werte ein.
Okta erstellt die Integration als Identitätsanbieter vom Typ `IDV_STANDARD` mit dem Protokoll `ID_PROOFING`. Anschließend verweisen Sie in einer Regel der Account Management Policy darauf.

| Feld | Wert |
| - | - |
| 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 | Die Client-ID der entsprechenden Didit-Anwendung |
| Client secret | Der primäre API-Schlüssel dieser Anwendung, verfügbar unter **Developers > API keys** in der Didit Console |

Konfigurieren Sie Produktion und Sandbox als separate Identitätsverifizierungs-Integrationen mit den jeweiligen Anmeldedaten.
Verwenden Sie die oben genannten Endpoints für beide.
Das Client Secret ist erforderlich, obwohl der Flow zusätzlich PKCE verwendet.
Ein separat erstellter benannter API-Schlüssel kann den primären API-Schlüssel der Anwendung für diese Integration nicht ersetzen.

Teilen Sie Didit Ihre Callback-URL mit, damit diese für Ihren Client registriert werden kann.
Okta setzt diese als `{yourOktaOrgUrl}/idp/identity-verification/callback` zusammen.

## Auth0 konfigurieren

Auth0 verfügt über keinen nativen IDV-Anbieter-Slot, sodass die Verifizierung über eine Post-Login Action ausgeführt wird, die den Benutzer in denselben Flow weiterleitet.
Hinterlegen Sie `DIDIT_IDV_BASE`, `DIDIT_CLIENT_ID` und `DIDIT_CLIENT_SECRET` als Action Secrets und registrieren Sie `https://{yourAuth0Domain}/continue` als Redirect-URI bei Didit.

> [!NOTE]
>
> Auth0 hängt seinen eigenen `state` an die Weiterleitung zu `/authorize` an, um den Login fortsetzen zu können. Didit übernimmt diesen `state` und gibt ihn an `/continue` zurück. Dadurch kann Auth0 die Transaktion abschließen – Sie müssen den Auth0-State nicht im PAR-Body übergeben (er wird erst bei der Weiterleitung generiert).

```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]
>
> Speichern Sie den PKCE-`code_verifier` pro Login-Transaktion (mit `event.transaction.id` als Schlüssel) und nicht in einem einzelnen `app_metadata`-Feld.
> Wenn ein Benutzer einen erneuten Versuch startet oder ein paralleler Login gestartet wird, wird ein einzelnes gemeinsames Feld überschrieben und der Token-Austausch schlägt mit `invalid_grant` fehl.

## Fehler

Fehler folgen RFC 6749 und RFC 9126.
Eine fehlgeschlagene Client-Authentifizierung gibt `401` mit `{"error": "invalid_client"}` zurück; ein abgelaufener oder wiederverwendeter `request_uri` oder Autorisierungscode gibt `400` mit `invalid_request` oder `invalid_grant` zurück.

Autorisierungscodes, `request_uri`-Werte und Verifizierungssitzungen sind alle nur einmalig verwendbar.


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