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

> Делегируйте верификацию личности Didit из Okta Identity Engine (BYO IDV) или Auth0 через OpenID Connect и Pushed Authorization Requests.

Didit предоставляет сервер авторизации OpenID Connect, созданный специально для делегированной верификации личности.
Он реализует контракт, который ожидает фреймворк Okta «bring your own identity verification vendor» (BYO IDV): Pushed Authorization Requests (PAR, RFC 9126), поток авторизационного кода с PKCE и ID-токен, содержащий объект OpenID Connect `verified_claims`.

Одни и те же эндпоинты работают для любой доверяющей стороны, которая поддерживает этот профиль, включая Auth0 post-login Actions.

## Эндпоинты

Все эндпоинты находятся под базовым URL моста верификации личности.

| Среда | Базовый URL |
| - | - |
| Продакшн | `https://apx.didit.me/auth/idv` |
| Песочница | `https://apx.didit.me/auth/idv` |

Продакшн и песочница используют одни и те же эндпоинты.
Среду определяет приложение, связанное с вашими учётными данными клиента.
Для каждой среды используйте отдельные учётные данные клиента и сценарий, принадлежащий соответствующему приложению.
Симулированные результаты верификации описаны в разделе [Песочница и тестовые данные](https://docs.didit.me/ru/integration/sandbox-testing.md).

Документ discovery по адресу `{base}/.well-known/openid-configuration` содержит все эндпоинты:

| Назначение | Эндпоинт |
| - | - |
| Pushed Authorization Request | `POST {base}/par` |
| Авторизация | `GET {base}/authorize` |
| Токен | `POST {base}/token` |
| JWKS | `GET {base}/jwks` |
| Discovery | `GET {base}/.well-known/openid-configuration` |

Аутентификация клиента выполняется через `client_secret_basic` или `client_secret_post`.
Поток авторизационного кода требует PKCE с `code_challenge_method=S256`.
ID-токены подписываются алгоритмом RS256; проверяйте их по эндпоинту JWKS.

## Поток

1. Доверяющая сторона отправляет запрос авторизации на `POST /par`. Запрос включает PKCE-челлендж, `state`, `nonce`, зарегистрированный `redirect_uri`, области `openid profile identity_assurance` и утверждения, которые нужно верифицировать.
   Didit возвращает одноразовый `request_uri`, который действителен 60 секунд.
2. Доверяющая сторона перенаправляет браузер пользователя на `GET /authorize?request_uri=...`.
   Didit создаёт сессию верификации и направляет пользователя в размещённый процесс верификации.
3. Когда пользователь завершает процесс, Didit оценивает решение по верификации, сравнивает извлечённые данные личности с утверждениями, которые были переданы, и возвращает пользователя на `redirect_uri` доверяющей стороны с одноразовым `code` и исходным `state`.
4. Доверяющая сторона обменивает код на `POST /token`, передавая свой `code_verifier`, и получает подписанный ID-токен.

## Запрос утверждений

Передайте утверждения для сравнения в стандартном параметре `claims` OpenID Connect в PAR-запросе.
Поддерживаются `given_name` и `family_name`, и оба всегда сравниваются нечётко.
`verified_claims` является массивом, как в профиле BYO IDV Okta и в ответе с ID-токеном ниже.

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

## ID-токен

ID-токен содержит массив `verified_claims`. `assurance_level` равен `VERIFIED` только тогда, когда сессия верификации одобрена **и** все запрошенные утверждения совпали с верифицированным документом.
В противном случае он равен `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` содержит идентификатор сессии Didit, поэтому вы можете найти верификацию в консоли или через API сессий.

> [!NOTE]
>
> Считайте пользователя верифицированным только тогда, когда `trust_framework` равен `IDV-DELEGATED` **и** `assurance_level` равен `VERIFIED`.
> Любая другая комбинация означает, что верификация не прошла.

## Настройка Okta

Попросите Didit включить верификацию личности для ваших продакшн-приложений и приложений в песочнице, связать каждое приложение с его сценарием верификации и зарегистрировать точный URL обратного вызова для каждого тенанта Okta.
Одного существующего приложения или API-ключа недостаточно для включения этой интеграции.

В Okta Admin Console добавьте специального поставщика верификации личности и заполните значения из таблицы ниже.
Okta создаёт интеграцию как поставщика идентификации типа `IDV_STANDARD` с протоколом `ID_PROOFING`, после чего укажите его в правиле Account Management Policy.

| Поле | Значение |
| - | - |
| Issuer | `https://apx.didit.me/auth/idv` |
| URL PAR-запроса | `https://apx.didit.me/auth/idv/par` |
| URL авторизации | `https://apx.didit.me/auth/idv/authorize` |
| URL токена | `https://apx.didit.me/auth/idv/token` |
| URL JWKS | `https://apx.didit.me/auth/idv/jwks` |
| Области | `openid`, `profile`, `identity_assurance` |
| Client ID | идентификатор клиента соответствующего приложения Didit |
| Client secret | Основной API-ключ этого приложения, доступен в разделе **Developers > API keys** консоли Didit |

Настройте продакшн и песочницу как две отдельные интеграции верификации личности с соответствующими учётными данными.
Для обеих используйте указанные выше эндпоинты.
Секрет клиента обязателен, даже несмотря на то, что поток также использует PKCE.
Отдельно созданный именованный API-ключ не может заменить основной API-ключ приложения в этой интеграции.

Передайте Didit свой URL обратного вызова, чтобы он был зарегистрирован для вашего клиента.
Okta формирует его как `{yourOktaOrgUrl}/idp/identity-verification/callback`.

## Настройка Auth0

У Auth0 нет встроенного слота для поставщика IDV, поэтому верификация запускается из post-login Action, который перенаправляет пользователя в тот же поток.
Сохраните `DIDIT_IDV_BASE`, `DIDIT_CLIENT_ID` и `DIDIT_CLIENT_SECRET` как секреты Action, а `https://{yourAuth0Domain}/continue` зарегистрируйте в Didit как redirect URI.

> [!NOTE]
>
> Auth0 добавляет собственный `state` к перенаправлению на `/authorize`, чтобы продолжить вход. Didit использует этот `state` и возвращает его в `/continue`, и именно это позволяет Auth0 завершить транзакцию. Передавать state Auth0 в теле PAR-запроса не нужно, так как он не генерируется до перенаправления.

```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]
>
> Сохраняйте PKCE `code_verifier` для каждой транзакции входа (ключом служит `event.transaction.id`), а не в одном поле `app_metadata`.
> Если пользователь повторяет попытку или запускает параллельный процесс входа, общее поле перезаписывается, и обмен токена завершается ошибкой `invalid_grant`.

## Ошибки

Ошибки соответствуют RFC 6749 и RFC 9126.
При неудачной аутентификации клиента возвращается `401` с `{"error": "invalid_client"}`. Если `request_uri` или код авторизации истёк или был использован повторно, возвращается `400` с `invalid_request` или `invalid_grant`.

Коды авторизации, значения `request_uri` и сессии верификации одноразовые.


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