يُعدّ إرسال رمز مرور صالح لمرة واحدة (OTP) عبر رسالة قصيرة SMS إجراءً شائعًا لتأكيد رقم الهاتف واسترداد الحساب والمصادقة الإضافية وتأكيد الدفع. إنّ التبديل بين المتصفّح وتطبيق المراسلة القصيرة لحفظ رمز متعدّد الأرقام أو نسخه ولصقه يسبّب إزعاجًا للمستخدمين، ويزيد من معدلات الانسحاب، ويجعل المستخدمين عرضة للتصيّد الاحتيالي على النطاقات المخادعة.
يؤدي الجمع بين ترميز <form> الدلالي و<input autocomplete="one-time-code" inputmode="numeric"> وتنسيق الرسائل القصيرة SMS المرتبط بالمصدر (@domain #code) وواجهة WebOTP API (OTPCredential) إلى إنشاء عملية تحقّق متعددة الطبقات ومحسّنة تدريجيًا:
- الاستخراج والإرسال التلقائيان: تتحقّق المتصفّحات التي تتوافق مع WebOTP API من المصدر من خلال السطر
@domain #codeفي رسالة SMS، وتملأ الحقل<input>، وتبدأ عملية التحقّق بنقرة واحدة. - الملء التلقائي للوحة المفاتيح المرتبط بالمصدر: المتصفّحات وأنظمة التشغيل التي تتيح استخدام
autocomplete="one-time-code"(مثل Safari على iOS وiPadOS وmacOS) تحلّل رسالة@domain #codeوتقترح الرمز في شريط الملء التلقائي للوحة المفاتيح فقط عندما يتطابق النطاق. - الإدخال اليدوي السلس: يتلقّى المستخدمون الذين يُدخلون رمزًا من جهاز آخر لوحة مفاتيح افتراضية رقمية من خلال
inputmode="numeric"بدون مخاطر سهولة الاستخدام في<input type="number">.
الاعتبارات الأمنية
تتحقّق كلمة المرور الصالحة لمرة واحدة (OTP) عبر الرسائل القصيرة من حيازة رقم الهاتف وتوفّر عملية تحقّق إضافية ملائمة، ولكن يمكن أن تتعرّض الرسائل القصيرة وأرقام الهواتف لعمليات تبديل شريحة SIM وإعادة استخدام الأرقام واعتراضها. يجب دائمًا إقران ميزة الملء التلقائي من جهة العميل وواجهة WebOTP API بتنسيق رسائل SMS @domain #code المرتبط بالمصدر لكي يتحقّق المتصفّح ونظام التشغيل من تطابق النطاق المستهدَف قبل اقتراح الرمز أو إرساله. للمصادقة الأساسية المقاومة للتصيّد الاحتيالي، يُفضَّل استخدام مفاتيح المرور بدلاً من الرموز المستندة إلى الرسائل القصيرة (راجِع التوجيهات بشأن مفاتيح المرور).
ترميز النماذج الدلالي
يجب تضمين عنصر التحكّم في عملية التحقّق في عنصر <form> دلالي يتضمّن method="POST" و<button type="submit"> صريحًا. استخدِم دائمًا عنصر <input> واحدًا بدلاً من تقسيم الأرقام على مربّعات متعددة.
- استخدام
type="text"معinputmode="numeric": لا تستخدِمtype="number"أبدًا لرموز OTP. رمز المرور الصالح لمرة واحدة هو سلسلة معرّفات قد تحتوي على أصفار بادئة (مثل012345)، وليس كمية قابلة للعدّ. يؤدي استخدامtype="number"إلى إضافة أزرار زيادة/إنقاص، ويمكن أن يزيل الأصفار البادئة، ويسمح بتغييرات غير مقصودة في قيمة عجلة التمرير. تجنَّب أيضًا استخدامtype="tel"، وهو حل بديل قديم مخصّص لأرقام الهواتف الكاملة. يمكنك إقرانtype="text"معinputmode="numeric"لعرض لوحة مفاتيح افتراضية للأرقام فقط على الأجهزة الجوّالة. - إضافة
autocomplete="one-time-code": يوجّه المتصفّحات وأنظمة التشغيل لاقتراح رموز التحقّق الواردة عبر رسائل SMS في شريط الملء التلقائي عند التركيز على<input>. - وضع تلميحات إرشادية فوق
<input>: يمكنك ربط<label>مرئي باستخدامforوid، ووضع أي تعليمات تنسيق (مرتبطة عبرaria-describedby) بشكل مرئي فوق<input>حتى لا تغطي النوافذ المنبثقة للملء التلقائي ولوحات المفاتيح الافتراضية للأجهزة الجوّالة التعليمات أبدًا. - عدم تقسيم الأرقام على عناصر
<input>متعددة: تؤدي أنماط كلمات المرور الصالحة لمرة واحدة (OTP) المتعددة المربعات (ستة إدخالاتmaxlength="1"منفصلة) إلى تعطيل ميزة الملء التلقائي الأصلية فيautocomplete="one-time-code"في العديد من المتصفحات، وتعيق عملية النسخ واللصق، وتجعل التنقّل باستخدام قارئ الشاشة أكثر تعقيدًا. إذا كان التصميم المرئي المقسّم مطلوبًا، يمكنك تصميم عنصر<input>واحد باستخدامletter-spacingوخط أحادي المسافة.
<form id="otp-form" action="/verify-otp" method="POST">
<div class="form-group">
<label for="otp-input">Verification code</label>
<!-- Place the hint above the input so autofill popovers and mobile keyboards do not obscure it -->
<p id="otp-format-hint" class="field-hint">
Enter the 6-digit code sent to your phone.
</p>
<!-- MANDATORY: Use a single input with type="text", inputmode="numeric", and autocomplete="one-time-code" -->
<input
type="text"
id="otp-input"
name="otp"
inputmode="numeric"
autocomplete="one-time-code"
pattern="\d{6}"
maxlength="6"
aria-describedby="otp-format-hint"
required>
</div>
<button type="submit">Verify code</button>
</form>
/* Optional: Style a single input to visually space digits without splitting the DOM element */
#otp-input {
font-family: ui-monospace, monospace;
font-size: 1.25rem; /* Example value: must be at least 1rem (16px) to prevent mobile browser auto-zoom */
letter-spacing: 0.35em;
min-height: 48px;
padding: 0.5rem 0.75rem;
box-sizing: border-box;
}
تنسيق رسائل SMS المرتبطة بالمصدر
لكي تتحقّق كل من WebOTP API وميزة الملء التلقائي لرموز autocomplete="one-time-code" المرتبطة بالنطاق في Safari من أنّ الرمز ينتمي إلى موقعك الإلكتروني، يجب أن تنسّق خدمة الرسائل القصيرة في الخلفية السطر الأخير من الرسالة النصية مع النطاق الأصلي مسبوقًا بـ @ وكلمة المرور الصالحة لمرة واحدة مسبوقة بـ #:
Your verification code is 123456.
@example.com #123456
يجب فرض قواعد التنسيق التالية على الرسائل القصيرة الصادرة:
- تضمين نص يمكن لشخص عادي قراءته أولاً: ابدأ دائمًا رسالة SMS بجملة واضحة يمكن لشخص عادي قراءتها وتحتوي على الرمز ليتمكّن المستخدمون من قراءتها بسهولة عند كتابتها يدويًا على جهاز آخر.
- وضع الرمز
@<domain> #<otp>في السطر الأخير: يجب أن يكون الرمز@هو الحرف الأول من السطر الأخير، بدون أي أسطر لاحقة أو نص توقيع بعده. - استخدِم نطاق المضيف فقط: لا تُدرِج مخطّط URL (
https://أوhttp://) أو منفذًا (:8080) أو مسارًا (/verify) في الرمز المميّز@domain. - افصل بين الرموز المميزة بمسافة واحدة: ضع مسافة واحدة بالضبط بين
@<domain>و#<otp>، ولا تُدرِج مطلقًا مسافة بيضاء داخل النطاق أو بين#والرمز.
دمج WebOTP API في عملية التحقّق
تتطلّب واجهة WebOTP API (OTPCredential) سياقًا آمنًا (https:// في مرحلة الإنتاج أو http://localhost أثناء التطوير المحلي). بدلاً من لصق نص برمجي DOMContentLoaded غير مرتبط يرسل طلبات إلى نموذج العناصر في المستند (DOM) ويستدعي form.submit()، يمكنك دمج navigator.credentials.get() مباشرةً في مراحل النشاط ومعالج الإرسال لواجهة مستخدم التحقّق:
- ربط مراحل النشاط الخاصة بالطلب بخطوة "كلمة المرور الصالحة لمرة واحدة" النشطة: ابدأ الاستماع إلى "كلمة المرور الصالحة لمرة واحدة" عندما يتم عرض حقل إدخال رمز التحقّق وتفعيله (على سبيل المثال، عندما تظهر خطوة "كلمة المرور الصالحة لمرة واحدة" بعد أن يطلب المستخدم رمزًا عبر رسالة قصيرة SMS، ومرة أخرى عندما ينقر المستخدم على زر "إعادة إرسال الرمز").
- إدارة
AbortControllerفي جميع مسارات الخروج: يمكنك تمريرAbortSignal(signal: ac.signal) إلىnavigator.credentials.get(). يمكنك الاتصال بـ "ac.abort()" في أي وقت:- يرسل المستخدم النموذج يدويًا (أو يرسل رمزًا تم ملؤه من خلال
autocomplete="one-time-code"). - يتم طلب رمز جديد عبر رسالة قصيرة ("إعادة إرسال الرمز")، ما يؤدي إلى إلغاء وحدة التحكّم السابقة قبل إنشاء وحدة جديدة.
- يتم إلغاء تحميل مكوّن التحقّق أو مربّع الحوار أو المسار.
- يرسل المستخدم النموذج يدويًا (أو يرسل رمزًا تم ملؤه من خلال
- تفعيل معالج إرسال الطلب (
form.requestSubmit()): عندما يتم حلّnavigator.credentials.get()باستخدامotp، اضبطotpInput.value = otp.codeواستدعِform.requestSubmit()(أو استدعِ دالة التحقّق مباشرةً). لا تستدعِform.submit()، لأنّform.submit()يتجاوز متتبِّعات أحداثsubmit(معالِجاتevent.preventDefault()/fetch()) ويتخطّى عملية التحقّق من القيود الأصلية.
const otpForm = document.getElementById('otp-form');
const otpInput = document.getElementById('otp-input');
let otpAbortController = null;
function stopSmsOtpListener() {
if (otpAbortController) {
otpAbortController.abort();
otpAbortController = null;
}
}
async function startSmsOtpListener() {
// Feature-detect WebOTP support
if (!otpInput || !('OTPCredential' in window)) {
return;
}
// Cancel any previous pending WebOTP request (e.g., when re-sending an SMS code)
stopSmsOtpListener();
const ac = new AbortController();
otpAbortController = ac;
try {
const otp = await navigator.credentials.get({
otp: { transport: ['sms'] },
signal: ac.signal,
});
if (!otp) return;
otpInput.value = otp.code;
// Trigger standard form submission so submit event listeners and validation run
if (otpForm) {
otpForm.requestSubmit();
}
} catch (err) {
// Expected when aborted by manual submission, resend, unmount, or user dismissal
if (err && err.name === 'AbortError') {
return;
}
console.error('WebOTP verification error:', err);
}
}
otpForm.addEventListener('submit', async (event) => {
// MANDATORY: Abort any in-flight WebOTP request when the form is submitted
stopSmsOtpListener();
// Application-specific submission logic (e.g., AJAX fetch or native POST)
event.preventDefault();
const formData = new FormData(otpForm);
// await fetch(otpForm.action, { method: 'POST', body: formData });
});
// Start listening when the OTP verification step becomes active
startSmsOtpListener();
التحقّق من إطارات iframe المتعددة المصادر
يتم تلقائيًا حظر تلقّي كلمة مرور صالحة لمرة واحدة (OTP) عبر رسالة قصيرة SMS داخل <iframe> متعدّد المصادر، ويتم استخدام هذه الميزة بشكل أساسي في مسارات تأكيد الدفع المضمّنة (مثل عملية التحقّق من بروتوكول 3D Secure حيث يضمّن shop.example طلب التحقّق من جهة الإصدار من bank.example). يتطلّب إتاحة WebOTP داخل <iframe> متعدّد المصادر ثلاثة إعدادات منسَّقة:
ربط المصدرَين في السطر الأخير من رسالة SMS: أدرِج نطاق التضمين ذي المستوى الأعلى أولاً (
@shop.example)، متبوعًا بـ#<otp>، متبوعًا بمسافة ونطاق iframe المضمَّن (@bank.example):Your verification code is 123456. @shop.example #123456 @bank.exampleإرسال عنوان استجابة HTTP
Permissions-Policy: يمكنك تفويضotp-credentialsإلى مصدر iframe المتعدّد المصادر في عناوين HTTP الخاصة بالمستند الرئيسي:Permissions-Policy: otp-credentials=(self "https://bank.example")إضافة
allow="otp-credentials"إلى العنصر<iframe>: يمكنك منح سياسة الأذونات صراحةً في علامة<iframe>في الصفحة المضمّنة وتضمين سمةtitleوصفية لبرامج قراءة الشاشة:
<iframe
src="https://bank.example/verify"
allow="otp-credentials"
title="Payment verification">
</iframe>
داخل مستند <iframe> الذي يعرضه https://bank.example، شغِّل ترميز <form> نفسه ومراحل نشاط التحقّق من navigator.credentials.get({ otp: { transport: ['sms'] }, signal: ac.signal }) الموضّحة أعلاه.
استراتيجيات احتياطية
الاحتياطي لـ WebOTP API
إذا كانت المتصفحات المستهدَفة لا تتوافق مع WebOTP API (OTPCredential)، لا تحمِّل polyfill لـ JavaScript من جهة خارجية، لأنّ قراءة الرسائل القصيرة الواردة تتطلّب التكامل على مستوى نظام التشغيل. بدلاً من ذلك، يمكنك الاعتماد على التحسين التدريجي المكوّن من ثلاثة مستويات والمضمّن في ترميز <input>:
- تجربة التحسين التدريجي: يمكنك حماية طلب
navigator.credentials.get({ otp: { transport: ['sms'] } })من خلالif ('OTPCredential' in window). عندما لا يتوفّر "OTPCredential":- إنّ المتصفّحات التي تتيح استخدام
autocomplete="one-time-code"(مثل Safari على نظام التشغيل iOS 14 والإصدارات الأحدث وiPadOS وmacOS مع ميزة "إعادة توجيه الرسائل القصيرة" في iCloud) تحلّل رسالة SMS التي تتضمّن@domain #codeوتعرض الرمز في شريط الملء التلقائي في لوحة مفاتيح النظام عند التركيز على<input>. - All other browsers fall back to manual entry in the single
<input type="text" inputmode="numeric" pattern="\d{6}" maxlength="6" required>, displaying a numeric keypad on mobile devices and submitting via the<button type="submit">.
- إنّ المتصفّحات التي تتيح استخدام
الاحتياطي لسياسة الأذونات
إذا كان المتصفّح لا يتيح تفويض otp-credentials من خلال Permissions-Policy و<iframe allow="otp-credentials">، سيتم تخفيض مستوى عملية التحقّق المضمّنة <form> داخل <iframe> المتعدّد المصادر بسلاسة إلى اقتراحات لوحة المفاتيح autocomplete="one-time-code" والإدخال الرقمي اليدوي بدون عرض استثناءات غير معالَجة.
إتاحة استخدام inputmode وAbortController
تتوافق كل من سمة HTML inputmode وAbortController (AbortSignal) مع المتصفّحات الحديثة ولا تتطلّب استخدام رمز polyfill أو رمز احتياطي.