> ## 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 وطلبات التفويض المدفوعة (PAR).

يوفّر Didit خادم تفويض OpenID Connect مصمّماً خصيصاً للتحقق المفوّض من الهوية.
يطبّق العقد الذي يتوقعه إطار عمل Okta المسمّى "bring your own identity verification vendor" (BYO IDV): طلبات التفويض المرسلة مسبقاً (PAR، RFC 9126)، وتدفق رمز التفويض مع PKCE، ورمز ID يحمل كائن OpenID Connect باسم `verified_claims`.

تعمل نقاط النهاية نفسها مع أي طرف معتمِد يلتزم بهذا الملف، بما في ذلك إجراءات Auth0 بعد تسجيل الدخول.

## نقاط النهاية

تقع جميع نقاط النهاية ضمن عنوان URL الأساسي لجسر التحقق من الهوية.

| البيئة | عنوان URL الأساسي |
| - | - |
| الإنتاج | `https://apx.didit.me/auth/idv` |
| البيئة التجريبية | `https://apx.didit.me/auth/idv` |

تستخدم بيئة الإنتاج والبيئة التجريبية نقاط النهاية نفسها.
يحدد التطبيق المرتبط ببيانات اعتماد العميل الخاصة بك البيئة التي تعمل فيها.
استخدم بيانات اعتماد عميل منفصلة وسير عمل يتبع التطبيق المقابل لكل بيئة.
راجع [Sandbox & Test Data](https://docs.didit.me/ar/integration/sandbox-testing.md) للاطلاع على نتائج التحقق المحاكاة.

يعلن مستند الاكتشاف في `{base}/.well-known/openid-configuration` عن جميع نقاط النهاية:

| الغرض | نقطة النهاية |
| - | - |
| طلب التفويض المرسل مسبقاً | `POST {base}/par` |
| التفويض | `GET {base}/authorize` |
| الرمز | `POST {base}/token` |
| JWKS | `GET {base}/jwks` |
| الاكتشاف | `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. يستبدل الطرف المعتمِد الـ code عبر `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، لذلك يمكنك البحث عن عملية التحقق في وحدة التحكم أو عبر واجهة برمجة الجلسات.

> [!NOTE]
>
> لا تعامل المستخدم على أنه موثّق إلا عندما تكون `trust_framework` مساوية لـ `IDV-DELEGATED` **و** `assurance_level` مساوية لـ `VERIFIED`.
> أي تركيبة أخرى تعني أن التحقق لم ينجح.

## إعداد Okta

اطلب من Didit تفعيل التحقق من الهوية لتطبيقي الإنتاج والبيئة التجريبية، وربط كل تطبيق بسير عمل التحقق الخاص به، وتسجيل رابط الاستدعاء الدقيق لكل مستأجر Okta.
لا يكفي وجود تطبيق أو مفتاح API حالي لتفعيل هذا التكامل.

في Okta Admin Console، أضف مزوّد تحقق من الهوية مخصصاً، وأدخل القيم الواردة أدناه.
ينشئ Okta التكامل بوصفه مزوّد هوية من النوع `IDV_STANDARD` مع البروتوكول `ID_PROOFING`، ثم تشير إليه من قاعدة في Account Management Policy.

| الحقل | القيمة |
| - | - |
| Issuer | `https://apx.didit.me/auth/idv` |
| عنوان طلب PAR | `https://apx.didit.me/auth/idv/par` |
| عنوان Authorize | `https://apx.didit.me/auth/idv/authorize` |
| عنوان Token | `https://apx.didit.me/auth/idv/token` |
| عنوان JWKS | `https://apx.didit.me/auth/idv/jwks` |
| Scopes | `openid`, `profile`, `identity_assurance` |
| Client ID | معرّف العميل لتطبيق Didit المقابل |
| Client secret | مفتاح API الأساسي لهذا التطبيق، ويتوفر في **Developers > API keys** في Didit Console |

اضبط بيئة الإنتاج والبيئة التجريبية بوصفهما تكاملين منفصلين للتحقق من الهوية، كل منهما ببياناته الخاصة.
استخدم نقاط النهاية أعلاه في الحالتين.
إن سر العميل مطلوب حتى مع استخدام PKCE في التدفق.
لا يمكن لمفتاح API مسمّى أُنشئ بشكل منفصل أن يحل محل مفتاح API الأساسي للتطبيق في هذا التكامل.

أرسل إلى Didit رابط الاستدعاء الخاص بك ليتم تسجيله على العميل لديك.
يبنيه Okta بالصيغة `{yourOktaOrgUrl}/idp/identity-verification/callback`.

## إعداد Auth0

لا يوفّر Auth0 خانة أصلية لمزوّد التحقق من الهوية، لذلك يعمل التحقق من خلال إجراء بعد تسجيل الدخول يوجّه المستخدم إلى التدفق نفسه.
خزّن `DIDIT_IDV_BASE` و`DIDIT_CLIENT_ID` و`DIDIT_CLIENT_SECRET` بوصفها أسراراً للإجراء، وسجّل `https://{yourAuth0Domain}/continue` بوصفه رابط إعادة التوجيه لدى Didit.

> [!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]
>
> خزّن `code_verifier` الخاص بـ PKCE لكل معاملة تسجيل دخول، مع استخدام `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.