تنفيذ بروتوكول جهة الاعتماد

لتنفيذ عملية تأكيد عنوان البريد الإلكتروني على موقعك الإلكتروني، عدِّل ترميز النموذج لطلب الرمز المميّز وأضِف التحقّق على صعيد الخادم للرموز المميّزة الواردة.

التسجيل في مرحلة التجربة والتقييم

يجب أن تكون المواقع الإلكترونية التي تتطلّب إثبات الملكية قد أعدّت مرحلة التجربة والتقييم على موقعها الإلكتروني.

بدءًا من الإصدار 154 من Chrome، ستتوفّر التجارب الخاصة بالمصادر التابعة لجهات خارجية مع شرط مهم: يجب أن يكون المصدر المسجّل للتجربة هو الموقع الإلكتروني نفسه الخاص بالجهة المصدرة. على سبيل المثال:

  • نطاق الجهة المصدرة: issuer.example
  • المسجّل في OT: https://issuer.example
  • مصدر JavaScript: https://issuer.example (أو https://app.issuer.example مع مطابقة النطاق الفرعي)

ضبط حقول النموذج

أضِف حقل رمز مميّز مخفيًا إلى نموذج إرسال البريد الإلكتروني:

<input
  type="email"
  name="email-address"
  autocomplete="email">
<input
  type="hidden"
  name="token"
  autocomplete="email-verification-token"
  nonce="rAnD0m-VaLuE">

متطلبات الحقول:

  • حقل البريد الإلكتروني: اضبط type="email" وautocomplete="email" ليتمكّن Chrome من ملء العنوان تلقائيًا والتعرّف عليه.
  • سمات حقل الرمز المميّز:
    • اضبط القيمة على autocomplete="email-verification-token": يحدّد Chrome هذا الحقل لتعبئة الرمز المميّز عند إرسال النموذج.
    • اضبط nonce="<VALUE>": يجب أن يوفّر الموقع الإلكتروني رقمًا عشوائيًا فريدًا مرتبطًا بالجلسة للتحقّق من إرسال النموذج.

التحقّق من صحة الرمز المميّز لتأكيد عنوان البريد الإلكتروني

عندما يرسل المستخدم النموذج، يتلقّى خادمك عنوان البريد الإلكتروني والرمز المميّز من الحقل المخفي. يشير حقل الرمز المميز الفارغ إلى أنّ المتصفّح أو مقدّم الخدمة لا يتيح ميزة EVP، أو أنّ المستخدم تخطّى عملية التحقّق. في حال حدوث ذلك، عليك الرجوع إلى عملية التحقّق الحالية، مثل إرسال كلمة مرور صالحة لمرة واحدة أو رابط سحري.

في حال توفّر رمز مميّز، تحقَّق من صحته باتّباع الخطوات التالية:

  1. حلِّل الرمز المميّز باستخدام مكتبة SD-JWT.
  2. التحقّق من صحة القيم المتوقّعة ومطالبات الجلسة
  3. تحقَّق من تفويض نظام أسماء النطاقات.
  4. استكشاف البيانات الوصفية لجهة الإصدار واسترداد JWKS
  5. التحقّق من التواقيع المشفرة وربط المفاتيح

1. تحليل الرمز المميّز

يستخدم الرمز المميّز تنسيق RFC 9901: Selective Disclosure JWT (SD-JWT+KB). استخدِم المكتبات المناسبة لنظامك الأساسي من أجل تحليل الرمز المميّز والتحقّق من صحته. على سبيل المثال، يمكنك استخدام @sd-jwt/core و jose مع Node. في شكله الأولي، يبدو على النحو التالي: رمز JWT موقّع من جهة إصدار، يتبعه صفر أو أكثر من مستندات الإفصاح، وينتهي برمز JWT لربط المفتاح، مع فصل كل مكوّن بعلامة التلدة:

<Issuer-signed EVT>~<Disclosure 1>~...~<Disclosure N>~<Key Binding JWT>

في التنفيذ الحالي، لا يحتوي الرمز المميّز على أي إفصاحات (<Issuer-signed EVT>~<Key Binding JWT>)، ولكن قد يتغيّر ذلك في المستقبل.

فك ترميز الرمز المميّز باستخدام المكتبة:

import { decodeSdJwtSync } from "@sd-jwt/core";
import { createHash } from "node:crypto";
const hasher = (data, alg) =>
  createHash(alg === "sha-256" ? "sha256" : alg)
    .update(data)
    .digest();
const decoded = decodeSdJwtSync(rawToken, hasher);
const evtPayload = decoded.jwt.payload;
const kbPayload = decoded.kbJwt?.payload;

إذا أثبتت verifier.example صحة demo@provider.example، سيبدو الرمز المميّز الذي تم فك ترميزه مشابهًا لما يلي:

{
  "evtJwtDecodedHeader": {
    "typ": "evt+jwt",
    "alg": "EdDSA",
    "kid": "issuer-key-id"
  },
  "evtJwtDecodedPayload": {
    "iss": "https://provider.example",
    "iat": 12345678901,
    "exp": 12345679901,
    "cnf": {
      "jwk": {
        "kty": "OKP",
        "crv": "Ed25519",
        "x": "pUbLiCkEy123pUbLiCkEy123pUbLiCkEy123"
      }
    },
    "email": "demo@provider.example",
    "email_verified": true
  },
  "kbJwtDecodedHeader": {
    "alg": "EdDSA",
    "typ": "kb+jwt"
  },
  "kbJwtDecodedPayload": {
    "aud": "https://verifier.example",
    "iat": 12345678901,
    "nonce": "rAnDoM123rAnDoM123rAnDoM123rAnDoM123",
    "sd_hash": "hAsH456hAsH456hAsH456hAsH456hAsH456"
  },
  "disclosures": []
}

2. التحقّق من صحة القيم المتوقّعة ومطالبات الجلسة

تأكَّد من أنّ القيم الأساسية في الحمولة تتطابق مع القيم المقدَّمة والمتوقّعة:

  • ‫email_verified: يجب أن تكون القيمة true.
  • email: يجب أن يتطابق مع عنوان البريد الإلكتروني الذي تم إرساله في النموذج.
  • ‫aud (الجمهور): يجب أن يتطابق مع مصدر موقعك الإلكتروني.
  • nonce: يجب أن تتطابق مع الرقم العشوائي المقدَّم في النموذج.
  • ‫iat (تاريخ الإصدار) وexp (تاريخ انتهاء الصلاحية): تأكَّد من أنّ الرمز المميّز يقع ضمن الفترة الزمنية الصالحة ولم تنتهِ صلاحيته.

3- التحقّق من تفويض نظام أسماء النطاقات

تأكَّد من سجلّ نظام أسماء النطاقات _email-verification لنطاق عنوان البريد الإلكتروني. على سبيل المثال، بالنسبة إلى demo@gmail.com، استخدِم طلب البحث عن سجلّ TXT الخاص بـ _email-verification.gmail.com. بالنسبة إلى موفّر الخدمة هذا، يعرض طلب البحث الموقع الجغرافي لموفّر الحساب، أي accounts.google.com.

$ dig +short TXT _email-verification.gmail.com
"iss=accounts.google.com"

تأكَّد من أنّ مخطط الجهة المصدرة هو https:// وأنّ https://<domain> يتطابق مع مطالبة iss في EVT.

4. التحقّق من توقيع EVT

استرجاع بيانات التعريف الخاصة بالجهة المصدرة من https://<issuer>/.well-known/email-verification:

{
  "issuance_endpoint": "https://accounts.google.com/gsi/email-verification/issue",
  "jwks_uri": "https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks",
  "signing_alg_values_supported": ["EdDSA"]
}

استرجِع مجموعة مفاتيح الويب JSON من jwks_uri.

استخدِم مكتبة SD-JWT للتحقّق من حزمة الرموز المميّزة. تتولّى المكتبة تنسيق عملية التحقّق من الصحة:

  1. التحقّق من صحة توقيع جهة الإصدار على رمز EVT مقارنةً بمجموعة JWKS التي تم استرجاعها
  2. التحقّق من صحة توقيع المتصفّح على KB-JWT باستخدام المفتاح العام المؤقت في cnf.jwk
  3. التحقّق من ربط المفتاح (aud وnonce وتجزئة الملخّص sd_hash)

مثال على منطق التحقّق في Node.js:

import { SDJwtInstance } from "@sd-jwt/core";
import { importJWK, compactVerify } from "jose";
import { createHash } from "node:crypto";
const hasher = (data, alg) =>
  createHash(alg === "sha-256" ? "sha256" : alg)
    .update(data)
    .digest();
const sdJwt = new SDJwtInstance({ hasher });
sdJwt.config({
  hasher,
  // Verifier for the Issuer-signed EVT
  verifier: async (data, sig) => {
    const token = `${data}.${sig}`;
    const header = decoded.jwt.header;
    const headerAlg = header.alg || "ES256";
    // Match by kid if present, or iterate across matching algorithm keys
    const keysToTry = header.kid
      ? jwksData.keys.filter(k => k.kid === header.kid)
      : jwksData.keys;
    for (const jwk of keysToTry) {
      try {
        const pubKey = await importJWK(jwk, jwk.alg || headerAlg);
        await compactVerify(token, pubKey);
        return true;
      } catch {
        // Try next candidate key
      }
    }
    return false;
  },
  // Verifier for the Key Binding JWT (KB-JWT)
  kbVerifier: async (data, sig) => {
    try {
      const browserJwkKey = evtPayload.cnf?.jwk;
      if (!browserJwkKey) return false;
      const pubKey = await importJWK(browserJwkKey, decoded.kbJwt.header.alg || "ES256");
      await compactVerify(`${data}.${sig}`, pubKey);
      return true;
    } catch {
      return false;
    }
  },
});
// The library automatically verifies EVT signature, KB-JWT signature, audience, nonce, and sd_hash
const result = await sdJwt.verify(rawToken, {
  kb: {
    expectedNonce: sessionNonce,
    expectedAudience: "https://example.com",
    required: true,
  },
});
const verifiedPayload = result.payload;

في حال نجاح جميع الخطوات، تكون قد أثبتّ ملكية عنوان البريد الإلكتروني لدى مزوّد الخدمة. في حال فشل عملية التحقّق، عليك الرجوع إلى إرسال رسالة تأكيد إلكترونية إلى المستخدم باستخدام مسارك العادي.

تحديد المشاكل وحلّها

إذا تعذّر إثبات الملكية أو لم يقدّم المتصفّح رمزًا مميّزًا، يُرجى التحقّق من المشاكل الشائعة التالية:

حقل الرمز المميز فارغ عند الإرسال

  • التسجيل في مرحلة التجربة والتقييم: تأكَّد من عرض العنوان Origin-Trial أو العلامة <meta> على الصفحة. بالنسبة إلى مراحل التجربة والتقييم التابعة لجهات خارجية (الإصدار 154 من Chrome أو إصدار أحدث)، يجب أن يكون مصدر مرحلة التجربة والتقييم المسجَّل هو الموقع الإلكتروني نفسه الخاص بالجهة المصدرة (https://<issuer-domain>). يمكنك فحص إعدادات مرحلة التجربة والتقييم على أحد المواقع الإلكترونية في &quot;أدوات مطوري البرامج&quot; ضمن التطبيق > الإطارات > (اختَر الإطار ذي الصلة) > مراحل التجربة والتقييم.
  • ترميز النموذج: يجب أن يكون كل من <input type="email" autocomplete="email"> و<input type="hidden" autocomplete="email-verification-token" nonce="..."> في عنصر <form> نفسه (وليس بشكل منفصل ضمن حدود Shadow DOM)، ويجب ألا يكون nonce فارغًا.
  • الإرسال المبكر أو إعادة استخدام الصفحة: يسترد المتصفّح الرمز المميّز في الخلفية بعد إدخال عنوان البريد الإلكتروني أو ملؤه تلقائيًا. سيؤدي إرسال الرمز المميّز قبل اكتمال الطلب إلى تركه فارغًا. يمكن أن يحدث ذلك إذا ضغط المستخدم على مفتاح الرجوع لإرسال النموذج بعد إدخال عنوان بريده الإلكتروني.
  • المتطلبات الأساسية للمتصفّح ومقدّم الخدمة: يجب أن يكون المستخدم مسجّلاً الدخول إلى مقدّم خدمة مشارك في ملف المتصفّح نفسه وأن يكون خيار البريد الإلكتروني الذي تم التحقّق منه مفعّلاً في إعدادات Chrome (chrome://settings/contactInfo).

تعذُّر التحقّق من توقيع جهة إصدار البطاقة

  • عنوان kid غير متوفّر: إنّ المطالبة kid (معرّف المفتاح) في عنوان EVT وJWKS اختيارية (على سبيل المثال، تحذف Gmail kid). وفي حال عدم توفّر kid، يجب تكرار جميع المفاتيح المرشّحة في jwks_uri الخاص بالجهة المصدرة بدلاً من تعذُّر البحث عن معرّف المفتاح.
  • معرّفات الخوارزميات (EdDSA وEd25519): يمكن للجهات المصدرة والمكتبات تحديد EdDSA أو Ed25519 (إلى جانب ES256). تأكَّد من أنّ منطق استيراد JWK والتحقّق منه يقبل كلا المعرّفَين.
  • تنسيق مصدر جهة الإصدار (iss): يحتوي سجلّ TXT لنظام أسماء النطاقات (_email-verification.<domain>) على اسم مضيف مجرّد (iss=accounts.issuer.example)، بينما يكون الادعاء iss في EVT مصدر HTTPS كاملاً (https://accounts.issuer.example، بدون شرطة مائلة لاحقة). أضِف البادئة https:// إلى قيمة سجلّ نظام أسماء النطاقات قبل المقارنة.

تعذُّر التحقّق من صحة ربط المفتاح (KB-JWT)

  • قيمة nonce غير متطابقة أو منتهية الصلاحية: تأكَّد من أنّ قيمة nonce المعروضة في <input> تتطابق مع قيمة nonce للجلسة النشطة على الخادم ولم تتم الكتابة فوقها من خلال علامة تبويب أخرى أو استهلاكها من خلال طلب سابق.
  • عدم تطابق الجمهور (aud): تمثّل المطالبة aud مصدر HTTPS الخاص بجهة التحقّق (https://verifier.example، بدون مسار أو شرطة مائلة لاحقة).

تعذُّر مقارنة المطالبة بالبريد الإلكتروني (email)

  • حالة الأحرف والتحويل إلى نموذج أساسي: يعرض الإصدار 156 من Chrome والإصدارات الأحدث المطالبة email بايتًا ببايت كما تم إدخالها في النموذج، ولكن قد تعرض إصدارات المتصفّح أو مقدّمو الخدمة الأقدم عنوانًا محوَّلاً إلى نموذج أساسي (على سبيل المثال، First.Last@example.com بدلاً من first.last@example.com). استخدِم مقارنة غير حساسة لحالة الأحرف عند مطابقة المطالبة email للرمز المميز مع قيمة النموذج المُرسَل.