SMS ile tek kullanımlık şifre kodu (OTP) göndermek; telefon numarası doğrulama, hesap kurtarma, ek kimlik doğrulama ve ödeme onayı için yaygın olarak kullanılan bir yöntemdir. Çok haneli bir kodu ezberlemek veya kopyalayıp yapıştırmak için tarayıcı ile SMS mesajlaşma uygulaması arasında geçiş yapmak kullanıcı deneyimini olumsuz etkiler, ayrılma oranlarını artırır ve kullanıcıları sahte alan adlarında kimlik avına karşı savunmasız bırakır.
Anlamsal <form> işaretlemesi, <input autocomplete="one-time-code" inputmode="numeric">, kaynağa bağlı SMS biçimlendirmesi (@domain #code) ve WebOTP API'yi (OTPCredential) birleştirmek, katmanlı ve progresif geliştirilmiş bir doğrulama akışı oluşturur:
- Otomatik ayıklama ve gönderme: WebOTP API'yi destekleyen tarayıcılar, SMS'teki
@domain #codesatırına göre kaynağı doğrular,<input>alanını doldurur ve tek bir dokunuşla doğrulamayı tetikler. - Kaynağa bağlı klavye otomatik doldurma:
autocomplete="one-time-code"standardını destekleyen tarayıcılar ve işletim sistemleri (ör. iOS, iPadOS ve macOS'te Safari),@domain #codemesajını ayrıştırır ve yalnızca alan adı eşleştiğinde klavye otomatik doldurma çubuğunda kodu önerir. - Sorunsuz manuel giriş: Başka bir cihazdan kod giren kullanıcılar,
<input type="number">'nin kullanılabilirlik tehlikeleri olmadaninputmode="numeric"üzerinden sayısal bir sanal tuş takımı alır.
Güvenlikle ilgili olarak göz önünde bulundurulması gerekenler
SMS OTP, telefon numarasının size ait olduğunu doğrular ve kolay bir ek doğrulama adımı sunar. Ancak SMS aktarımı ve telefon numaraları; SIM takası, numara geri dönüşümü ve müdahaleye maruz kalabilir. Tarayıcı ve işletim sisteminin, kodu önermeden veya göndermeden önce hedef alanın eşleştiğini doğrulaması için istemci tarafı otomatik doldurma ve WebOTP API'yi her zaman kaynağa bağlı @domain #code SMS biçimiyle eşleştirin. Birincil kimlik avına dayanıklı kimlik doğrulama için SMS tabanlı kodlar yerine geçiş anahtarlarını tercih edin (Geçiş Anahtarları Oryantasyonu'na bakın).
Anlamsal form işaretlemesi
Doğrulama kontrolünü method="POST" ve açık bir <button type="submit"> içeren anlamsal bir <form> öğesiyle sarmalayın. Rakamları birden fazla kutuya bölmek yerine her zaman tek bir <input> öğesi kullanın.
type="text"'iinputmode="numeric"ile kullanın: OTP kodları için hiçbir zamantype="number"kullanmayın. Tek kullanımlık şifre kodu, sayılabilir bir miktar değil, başında sıfır (ör.012345) bulunabilen bir tanımlayıcı dizedir.type="number"kullanıldığında artırma/azaltma düğmeleri eklenir, baştaki sıfırlar kaldırılabilir ve kaydırma tekerleğiyle yanlışlıkla değer değişiklikleri yapılabilir. Ayrıca, tam telefon numaraları için kullanılan eski bir geçici çözüm olantype="tel"özelliğini kullanmaktan kaçının. Mobil cihazlarda yalnızca rakamlardan oluşan bir sanal klavye göstermek içintype="text"ileinputmode="numeric"öğesini eşleyin.autocomplete="one-time-code"ekleyin: Tarayıcılara ve işletim sistemlerine,<input>odaklandığında otomatik doldurma çubuğunda gelen SMS doğrulama kodlarını önermeleri talimatını verir.- Talimat ipuçlarını
<input>öğesinin üstüne yerleştirin:forveidkullanarak görünür bir<label>ilişkilendirin ve tüm biçim talimatlarını (aria-describedbyaracılığıyla bağlantılı) görsel olarak<input>öğesinin üstüne yerleştirin. Böylece otomatik doldurma popover'ları ve mobil sanal klavyeler talimatları hiçbir zaman kapatmaz. - Rakamları birden fazla
<input>öğesine bölmeyin: Çok kutulu OTP kalıpları (altı ayrımaxlength="1"girişi), birçok tarayıcıda yerelautocomplete="one-time-code"otomatik doldurma özelliğini bozar, kopyalama ve yapıştırmayı engeller ve ekran okuyucu gezinmesini zorlaştırır. Segmentlere ayrılmış bir görsel tasarım gerekiyorsaletter-spacingve sabit boşluklu yazı tipiyle tek bir<input>öğesini biçimlendirin.
<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;
}
Kaynağa bağlı SMS mesajı biçimi
Hem WebOTP API'nin hem de Safari'nin alana bağlı autocomplete="one-time-code" otomatik doldurma özelliğinin, kodun web sitenize ait olduğunu doğrulayabilmesi için arka uç SMS hizmetinizin, kısa mesajın son satırını, kaynak alanın önüne @ ve OTP'nin önüne # ekleyerek biçimlendirmesi gerekir:
Your verification code is 123456.
@example.com #123456
Giden SMS mesajında aşağıdaki biçimlendirme kurallarını uygulayın:
- Önce kullanıcıların okuyabileceği metni ekleyin: SMS'e her zaman kodu içeren, kullanıcıların okuyabileceği net bir cümleyle başlayın. Böylece, kodu başka bir cihaza manuel olarak yazan kullanıcılar kolayca okuyabilir.
@<domain> #<otp>karakterini en son satıra yerleştirin:@karakteri, son satırın ilk karakteri olmalı ve ardından gelen satır veya imza metni olmamalıdır.- Yalnızca ana makine alan adını kullanın:
@domainjetonuna URL şeması (https://veyahttp://), bağlantı noktası (:8080) ya da yol (/verify) eklemeyin. - Jetonları tek bir boşlukla ayırın:
@<domain>ve#<otp>arasına tam olarak bir boşluk bırakın ve alan adının içinde veya#ile kod arasında hiçbir zaman boşluk bırakmayın.
WebOTP API'yi doğrulama akışınıza entegre etme
WebOTP API (OTPCredential) için güvenli bir bağlam (üretimde https:// veya yerel geliştirme sırasında http://localhost) gerekir. DOM'u sorgulayan ve form.submit() yöntemini çağıran bağlantısız bir DOMContentLoaded komut dosyası yapıştırmak yerine, navigator.credentials.get() yöntemini doğrudan doğrulama kullanıcı arayüzünüzün yaşam döngüsüne ve gönderim işleyicisine entegre edin:
- İstek yaşam döngüsünü etkin OTP adımına bağlayın: Doğrulama girişi oluşturulup etkinleştirildiğinde OTP'yi dinlemeye başlayın (örneğin, kullanıcı bir SMS kodu istedikten sonra OTP adımı yüklendiğinde ve kullanıcı "Kodu yeniden gönder" düğmesini her tıkladığında).
- Tüm çıkış yollarında
AbortControlleryönetme:navigator.credentials.get()öğesine birAbortSignal(signal: ac.signal) iletin. Aşağıdaki durumlardaac.abort()numarasını arayın:- Kullanıcı, formu manuel olarak gönderir (veya
autocomplete="one-time-code"aracılığıyla doldurulan bir kodu gönderir). - Yeni bir SMS kodu istendiğinde ("Kodu yeniden gönder") yeni bir denetleyici oluşturulmadan önce önceki denetleyici iptal edilir.
- Doğrulama bileşeni, iletişim kutusu veya rota kaldırılır.
- Kullanıcı, formu manuel olarak gönderir (veya
- Uygulamanın gönderim işleyicisini (
form.requestSubmit()) tetikleyin:navigator.credentials.get(),otpile çözümlendiğindeotpInput.value = otp.codedeğerini ayarlayın veform.requestSubmit()işlevini çağırın (veya doğrulama işlevinizi doğrudan çağırın).form.submit(),submitetkinlik işleyicileri (event.preventDefault()/fetch()işleyicileri) atladığı ve yerel kısıtlama doğrulamasını atladığı içinform.submit()'yi ÇAĞIRMAYIN.
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();
Çapraz kaynak iframe doğrulaması
Çapraz kaynak <iframe> içinde SMS OTP alma varsayılan olarak kısıtlanmıştır ve öncelikle yerleşik ödeme onayı akışları için kullanılır (ör. shop.example, bank.example tarafından gönderilen bir sorgulamayı yerleştirdiğinde 3D Secure doğrulaması). Çapraz kaynak <iframe> içinde WebOTP'yi desteklemek için üç koordineli yapılandırma gerekir:
SMS'in son satırında her iki kaynağı da bağlayın: Önce üst düzey yerleştirme alanını (
@shop.example), ardından#<otp>, bir boşluk ve yerleştirilmiş iFrame alanını (@bank.example) listeleyin:Your verification code is 123456. @shop.example #123456 @bank.examplePermissions-PolicyHTTP yanıt başlığını gönderin: Üst dokümanın HTTP başlıklarındaotp-credentials'yi çapraz kaynak iframe kaynağına devredin:Permissions-Policy: otp-credentials=(self "https://bank.example")<iframe>öğesineallow="otp-credentials"ekleyin: Yerleştirme sayfasındaki<iframe>etiketinde izin politikasını açıkça verin ve ekran okuyucular için açıklayıcı birtitleözelliği ekleyin:
<iframe
src="https://bank.example/verify"
allow="otp-credentials"
title="Payment verification">
</iframe>
https://bank.example tarafından sunulan <iframe> dokümanında, yukarıda gösterilen <form> işaretleme ve navigator.credentials.get({ otp: { transport: ['sms'] }, signal: ac.signal }) doğrulama yaşam döngüsünü çalıştırın.
Yedek stratejiler
WebOTP API yedek seçeneği
Hedef tarayıcılarınız WebOTP API'yi (OTPCredential) desteklemiyorsa üçüncü taraf JavaScript polyfill yüklemeyin. Gelen SMS mesajlarını okumak için işletim sistemi düzeyinde entegrasyon gerekir. Bunun yerine, <input> işaretlemesinde yerleşik olarak bulunan üç katmanlı progresif geliştirmeyi kullanın:
- Progresif geliştirme deneyimi:
navigator.credentials.get({ otp: { transport: ['sms'] } })çağrısınıif ('OTPCredential' in window)ile koruyun.OTPCredentialkullanılamadığında:autocomplete="one-time-code"özelliğini destekleyen tarayıcılar (ör. iOS 14 ve sonraki sürümlerde, iPadOS'te ve iCloud SMS geçişi ile macOS'te Safari),@domain #codeSMS'ini ayrıştırır ve<input>odaklandığında kodu sistem klavyesi otomatik doldurma çubuğunda gösterir.- Diğer tüm tarayıcılar, tek bir
<input type="text" inputmode="numeric" pattern="\d{6}" maxlength="6" required>içinde manuel girişe geri döner, mobil cihazlarda sayısal tuş takımı gösterir ve<button type="submit">aracılığıyla gönderir.
İzin Politikası yedeklemesi
Tarayıcı, Permissions-Policy ve <iframe allow="otp-credentials"> aracılığıyla otp-credentials yetkilendirmesini desteklemiyorsa çapraz kaynak <iframe> içindeki yerleşik doğrulama <form>, işlenmeyen istisnalar oluşturmadan autocomplete="one-time-code" klavye önerilerine ve manuel sayısal girişe sorunsuz bir şekilde geçiş yapar.
Inputmode ve AbortController desteği
Hem HTML inputmode özelliği hem de AbortController (AbortSignal) modern tarayıcılarda desteklenir ve polyfill veya yedek kod gerektirmez.