ارسال گذرنویسه یکبارمصرف (OTP) ازطریق پیامک، روشی رایج برای درستیسنجی شماره تلفن، بازیابی حساب، اصالتسنجی گامبهگام، و تأیید پرداخت است. جابهجایی بین مرورگر و برنامه پیامرسانی پیامک برای بهخاطر سپردن یا کپی و جایگذاری کردن کد چندرقمی باعث اصطکاک کاربر میشود، نرخ ریزش را افزایش میدهد، و کاربران را دربرابر طعمهگذاری در دامنههای جعلشده آسیبپذیر میکند.
ترکیب نشانهگذاری معنایی <form>، <input autocomplete="one-time-code" inputmode="numeric">، قالببندی پیامک محدود به مبدأ (@domain #code)، و WebOTP API (OTPCredential) یک جریان درستیسنجی لایهای و بهتدریج بهبودیافته ایجاد میکند:
- استخراج و ارسال خودکار: مرورگرهایی که از WebOTP API پشتیبانی میکنند، مبدأ را با خط
@domain #codeدر پیامک درستیسنجی میکنند،<input>را تکمیل میکنند، و درستیسنجی را با یک تکضرب فعال میکنند. - تکمیل خودکار صفحهکلید محدود به مبدأ: مرورگرها و سیستمعاملهایی که از
autocomplete="one-time-code"پشتیبانی میکنند (مانند Safari در iOS، iPadOS، و macOS) پیام@domain #codeرا تجزیه میکنند و کد را در نوار تکمیل خودکار صفحهکلید فقط زمانی پیشنهاد میدهند که دامنه مطابقت داشته باشد. - ورود دستی بدون اصطکاک: کاربرانی که کدی را از دستگاه دیگری وارد میکنند، صفحهکلید مجازی عددی را ازطریق
inputmode="numeric"دریافت میکنند، بدون اینکه با خطرات قابلیت استفاده<input type="number">مواجه شوند.
ملاحظات امنیتی
گذرواژه یکبارمصرف پیامکی مالکیت شماره تلفن را درستیسنجی میکند و درستیسنجی گامبهگام راحتی ارائه میدهد، اما انتقال پیامک و شماره تلفنها ممکن است درمعرض تعویض سیمکارت، بازیافت شماره، و رهگیری قرار بگیرند. همیشه تکمیل خودکار سمت کارخواه و WebOTP API را با قالب پیامک @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": به مرورگرها و سیستمعاملها دستور میدهد کدهای درستیسنجی پیامکی ورودی را در نوار تکمیل خودکار پیشنهاد دهند، وقتی<input>در کانون توجه است. - نکات آموزشی را بالای
<input>قرار دهید: بااستفاده ازforوid، یک<label>نمایان را مرتبط کنید و هرگونه دستورالعمل قالببندی (پیوندشده ازطریقaria-describedby) را بهصورت دیداری بالای<input>قرار دهید تا منوهای بالاپر تکمیل خودکار و صفحهکلیدهای مجازی تلفن همراه هرگز دستورالعملها را نپوشانند. - رقمها را در چند عنصر
<input>تقسیم نکنید: الگوهای گذرواژه یکبارمصرف چندجعبهای (شش ورودی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;
}
قالب پیامک محدود به مبدأ
برای اینکه هم WebOTP API و هم تکمیل خودکار autocomplete="one-time-code" محدود به دامنه Safari درستیسنجی کنند که کد متعلق به وبسایت شما است، سرویس پیامک باطن شما باید خط آخر پیامک را با دامنه مبدأ با پیشوند @ و گذرواژه یکبارمصرف با پیشوند # قالببندی کند:
Your verification code is 123456.
@example.com #123456
قوانین قالببندی زیر را بر پیامک خروجی اعمال کنید:
- ابتدا نوشتار قابلخواندن برای انسان را اضافه کنید: همیشه پیامک را با یک جمله واضح و قابلخواندن برای انسان که حاوی کد است شروع کنید تا کاربرانی که آن را بهصورت دستی در دستگاه دیگری تایپ میکنند بتوانند بهراحتی آن را بخوانند.
@<domain> #<otp>را در آخرین خط قرار دهید: نویسه@باید اولین نویسه خط آخر باشد و هیچ خط یا نوشتار امضایی بعداز آن وجود نداشته باشد.- فقط از دامنه میزبان استفاده کنید: طرحواره نشانی وب (
https://یاhttp://)، درگاه (:8080)، یا مسیر (/verify) را در کد@domainوارد نکنید. - جدا کردن توکنها با یک فاصله: دقیقاً یک فاصله بین
@<domain>و#<otp>قرار دهید و هرگز فاصله خالی در داخل دامنه یا بین#و کد قرار ندهید.
ادغام کردن WebOTP API در جریان درستیسنجی
WebOTP API (OTPCredential) به «بافت ایمن» (https:// در تولید، یا http://localhost درطول توسعه محلی) نیاز دارد. بهجای جایگذاری کردن یک اسکریپت DOMContentLoaded قطعشده که DOM را پرسمان میکند و form.submit() را فراخوانی میکند، navigator.credentials.get() را مستقیماً در چرخه حیات و کنترلکننده ارسال واسط کاربر درستیسنجیتان ادغام کنید:
- چرخه حیات درخواست را به مرحله فعال گذرواژه یکبارمصرف گره بزنید: وقتی ورودی درستیسنجی پرداز و فعال میشود، گوش دادن به گذرواژه یکبارمصرف را شروع کنید (برای مثال، وقتی مرحله گذرواژه یکبارمصرف پساز درخواست کاربر برای کد پیامکی سوار میشود، و دوباره هروقت کاربر روی دکمه «ارسال مجدد کد» کلیک میکند).
- مدیریت
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 میانمبدئی
دریافت گذرواژه یکبارمصرف پیامکی در <iframe> میانمبدئی بهطور پیشفرض محدود شده است و عمدتاً برای جریانهای تأیید پرداخت جاسازیشده (مانند درستیسنجی 3D Secure که در آن shop.example چالش صادرکننده را از bank.example جاسازی میکند) استفاده میشود. پشتیبانی از WebOTP در <iframe> میانمبدئی به سه پیکربندی هماهنگ نیاز دارد:
هر دو مبدأ را در خط آخر پیامک مقید کنید: ابتدا دامنه جاسازی سطح بالا (
@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) پشتیبانی نمیکنند، شبیهساز مرورگر جاوا اسکریپت شخص ثالث را بار نکنید، زیرا خواندن پیامکهای ورودی به ادغام در سطح سیستمعامل نیاز دارد. درعوض، به ارتقای پیشرو سهسطحی که در نشانهگذاری <input> تعبیه شده است تکیه کنید:
- تجربه ارتقای پیشرو: از تماس
navigator.credentials.get({ otp: { transport: ['sms'] } })در پشتif ('OTPCredential' in window)محافظت کنید. وقتیOTPCredentialدردسترس نیست:- مرورگرهایی که از
autocomplete="one-time-code"پشتیبانی میکنند (مانند Safari در iOS 14+، iPadOS، و macOS با iCloud SMS relay) پیامک@domain #codeرا تجزیه میکنند و وقتی<input>در کانون توجه قرار میگیرد، کد را در نوار تکمیل خودکار صفحهکلید سیستم ارائه میدهند. - همه مرورگرهای دیگر به ورودی دستی در
<input type="text" inputmode="numeric" pattern="\d{6}" maxlength="6" required>تکی برمیگردند، صفحهکلید عددی را در دستگاههای همراه نمایش میدهند، و ازطریق<button type="submit">ارسال میکنند.
- مرورگرهایی که از
جایگزین «خطمشی اجازهها»
اگر مرورگر از واگذاری otp-credentials ازطریق Permissions-Policy و <iframe allow="otp-credentials"> پشتیبانی نکند، درستیسنجی جاسازیشده <form> در <iframe> میانمبدئی بهطور مناسب به پیشنهادهای صفحهکلید autocomplete="one-time-code" و ورودی عددی دستی تنزل مییابد بدون اینکه استثناهای مدیریتنشده ایجاد کند.
پشتیبانی از Inputmode و AbortController
هم مشخصه HTML inputmode و هم AbortController (AbortSignal) در مرورگرهای نو پشتیبانی میشوند و به شبیهساز مرورگر یا کد جایگزین نیاز ندارند.