שיטות מומלצות לטופס OTP באמצעות SMS

בדרך כלל, כדי לאמת את זהות המשתמש, מבקשים ממנו להזין סיסמה חד-פעמית (OTP) שנשלחת אליו ב-SMS. תרחישים לדוגמה לשימוש ב-OTP ב-SMS:

  • אימות דו-שלבי. בנוסף לשם המשתמש והסיסמה, אפשר להשתמש ב-OTP ב-SMS כאות חזק לכך שהחשבון נמצא בבעלות האדם שקיבל את ה-OTP ב-SMS.
  • אימות מספר הטלפון. בחלק מהשירותים, מספר הטלפון משמש כמזהה הראשי של המשתמש. בשירותים כאלה, המשתמשים יכולים להזין את מספר הטלפון שלהם ואת ה-OTP שהם מקבלים ב-SMS כדי להוכיח את הזהות שלהם. לפעמים משלבים אותו עם קוד אימות כדי ליצור אימות דו-שלבי.
  • שחזור החשבון. אם משתמש מאבד את הגישה לחשבון שלו, צריכה להיות דרך לשחזר אותו. שיטות נפוצות לשחזור חשבון הן שליחת אימייל לכתובת האימייל הרשומה או שליחת סיסמה חד-פעמית (OTP) ב-SMS למספר הטלפון.
  • אישור תשלום במערכות תשלומים, חלק מהבנקים או ממנפיקי כרטיסי האשראי מבקשים אימות נוסף מהמשלם מטעמי אבטחה. בדרך כלל משתמשים ב-OTP ב-SMS למטרה הזו.

בהמשך מפורטות שיטות מומלצות ליצירת טפסים של סיסמאות חד-פעמיות ב-SMS לתרחישים האלה.

רשימת המשימות

כדי לספק את חוויית המשתמש הטובה ביותר עם סיסמה חד-פעמית ב-SMS, צריך לפעול לפי השלבים הבאים:

  • משתמשים ברכיב <input> עם:
    • type="text"
    • inputmode="numeric"
    • autocomplete="one-time-code"
  • השתמשו ב-@BOUND_DOMAIN #OTP_CODE בתור השורה האחרונה בהודעת ה-SMS עם הסיסמה החד-פעמית.
  • משתמשים ב-WebOTP API.

שימוש ברכיב <input>

השיטה הכי חשובה היא להשתמש בטופס עם רכיב <input>, כי היא פועלת בכל הדפדפנים. גם אם הצעות אחרות מהפוסט הזה לא פועלות בדפדפן מסוים, המשתמש עדיין יוכל להזין ולשלוח את ה-OTP באופן ידני.

<form action="/verify-otp" method="POST">
  <input type="text"
      inputmode="numeric"
      autocomplete="one-time-code"
      pattern="\d{6}"
      required>
</form>

הנה כמה רעיונות שיעזרו לכם לוודא שהשדה להזנת קלט מפיק את המקסימום מהפונקציונליות של הדפדפן.

type="text"

מכיוון שסיסמאות חד-פעמיות הן בדרך כלל מספרים בני חמש או שש ספרות, השימוש ב-type="number" בשדה להזנת קלט נראה אינטואיטיבי כי הוא משנה את המקלדת בנייד למספרים בלבד. לא מומלץ להשתמש באפשרות הזו כי הדפדפן מצפה ששדה להזנת קלט יהיה מספר שניתן לספור ולא רצף של כמה מספרים, מה שעלול לגרום להתנהגות בלתי צפויה. אם משתמשים ב-type="number", כפתורי למעלה ולמטה מוצגים לצד השדה להזנת קלט. לחיצה על הכפתורים האלה מגדילה או מקטינה את המספר, ויכולה להסיר אפסים בתחילת המספר.

במקום זאת, אתם צריכים להשתמש ב-type="text". הפעולה הזו לא תהפוך את המקלדת לנייד למקלדת של מספרים בלבד, אבל זה בסדר כי הטיפ הבא לשימוש ב-inputmode="numeric" עושה את זה.

inputmode="numeric"

משתמשים ב-inputmode="numeric" כדי לשנות את המקלדת בנייד למספרים בלבד.

בחלק מהאתרים משתמשים ב-type="tel" בשדות להזנת קלט של OTP, כי הוא גם משנה את המקלדת בנייד למספרים בלבד (כולל * ו-#) כשהיא ממוקדת. השתמשו בטריק הזה בעבר, כשלא הייתה תמיכה נרחבת ב-inputmode="numeric". מאז ש-Firefox התחיל לתמוך ב-inputmode="numeric", אין צורך להשתמש ב-hack של type="tel", שהוא לא נכון מבחינה סמנטית.

autocomplete="one-time-code"

autocomplete מאפשר למפתחים לציין איזו הרשאה יש לדפדפן לספק סיוע בהשלמה אוטומטית, ומיידע את הדפדפן לגבי סוג המידע שצפוי בשדה.

ב-autocomplete="one-time-code", בכל פעם שמשתמש מקבל הודעת SMS בזמן שטופס פתוח, מערכת ההפעלה מנתחת את ה-OTP ב-SMS באופן היוריסטי והמקלדת מציעה את ה-OTP כדי שהמשתמש יזין אותו. הוא פועל רק ב-Safari 12 ואילך ב-iOS, ב-iPadOS וב-macOS, אבל מומלץ מאוד להשתמש בו, כי זו דרך טובה יותר לשפר את חוויית ה-SMS OTP בפלטפורמות האלה.

‫autocomplete="one-time-code" בפעולה.

‫autocomplete="one-time-code" משפר את חוויית המשתמש, אבל אתם יכולים לעשות עוד משהו: לוודא שהודעת ה-SMS תואמת לפורמט ההודעה שקשור למקור.

מאפיינים אופציונליים

מאפיינים אופציונליים:

  • pattern מציין את הפורמט שהסיסמה החד-פעמית שהוזנה צריכה להתאים לו. צריך להשתמש בביטויים רגולריים כדי לציין את התבנית התואמת. לדוגמה, \d{6} מגביל את ה-OTP למחרוזת של שש ספרות. שימוש ב-JavaScript לאימות מורכב יותר בזמן אמת למידע נוסף על pattern.
  • required מציין שהמשתמש חייב למלא את השדה.

כדאי לקרוא את השיטות המומלצות לטופסי כניסה כדי לקבל טיפים נוספים.

עיצוב הטקסט של ה-SMS

כדי לשפר את חוויית השימוש בהזנת OTP, כדאי לפעול בהתאם למפרט של קודי אימות חד-פעמיים שמוגבלים למקור ונשלחים ב-SMS.

הכלל הבסיסי של הפורמט הוא: מסיימים את הודעת ה-SMS עם הדומיין של המקבל, שלפניו מופיע @, וה-OTP, שלפניו מופיע #.

לדוגמה:

Your OTP is 123456

@web-otp.example.com #123456

הפורמט הסטנדרטי של הודעות OTP מאפשר לחלץ את הקוד בקלות ובאמינות. שיוך של קודי OTP לאתרים מקשה על הניסיונות להטעות משתמשים ולגרום להם לספק קוד לאתרים זדוניים.

כללי פורמט מדויקים

הכללים המדויקים הם:

  • ההודעה מתחילה בטקסט קריא לאנשים (אופציונלי) שמכיל מחרוזת אלפאנומרית באורך של 4 עד 10 תווים עם מספר אחד לפחות, והשורה האחרונה מיועדת לכתובת ה-URL ול-OTP.
  • לפני החלק של הדומיין בכתובת ה-URL של האתר שהפעיל את ה-API, צריך להופיע @.
  • כתובת ה-URL חייבת להכיל #, ואחריו את ה-OTP. האורך המקסימלי הוא 140 תווים.

לשימוש בפורמט הזה יש כמה יתרונות:

  • הסיסמה החד-פעמית תהיה קשורה לדומיין. אם המשתמש נמצא בדומיינים אחרים, ולא בדומיין שצוין בהודעת ה-SMS, ההצעה ל-OTP לא תופיע. כך גם פוחת הסיכון להתקפות פישינג ולפריצות פוטנציאליות לחשבון.
  • עכשיו הדפדפן יוכל לחלץ את ה-OTP באופן מהימן בלי להסתמך על היוריסטיקות מסתוריות ולא יציבות.

כשאתר משתמש ב-autocomplete="one-time-code", דפדפן Safari עם iOS מגרסה 14 ואילך יציע את ה-OTP לפי הכללים האלה.

הפורמט הזה של הודעות SMS מועיל גם לדפדפנים אחרים, לא רק ל-Safari. גם Chrome,‏ Opera ו-Vivaldi ב-Android תומכים בכלל של קודים חד-פעמיים שמוגבלים למקור באמצעות WebOTP API, אבל לא דרך autocomplete="one-time-code".

שימוש ב-WebOTP API

WebOTP API מספק גישה ל-OTP שהתקבל בהודעת SMS. כשמפעילים את navigator.credentials.get() עם הסוג otp (OTPCredential) כאשר transport כולל את sms, האתר ימתין להודעת SMS שתואמת לקודים החד-פעמיים שמוגבלים למקור, כדי שהמשתמש יקבל גישה. אחרי שהסיסמה החד-פעמית מועברת ל-JavaScript, האתר יכול להשתמש בה בטופס או לשלוח אותה ישירות לשרת.

navigator.credentials.get({
  otp: {transport:['sms']}
})
.then(otp => input.value = otp.code);
WebOTP API בפעולה.

במאמר אימות מספרי טלפון באינטרנט באמצעות WebOTP API מוסבר בפירוט איך להשתמש ב-WebOTP API. אפשר גם להעתיק ולהדביק את קטע הקוד הבא. חשוב להגדיר מאפיין action ומאפיין method ב-<form>.

// Feature detection
if ('OTPCredential' in window) {
  window.addEventListener('DOMContentLoaded', e => {
    const input = document.querySelector('input[autocomplete="one-time-code"]');
    if (!input) return;
    // Cancel the WebOTP API if the form is submitted manually.
    const ac = new AbortController();
    const form = input.closest('form');
    if (form) {
      form.addEventListener('submit', e => {
        // Cancel the WebOTP API.
        ac.abort();
      });
    }
    // Invoke the WebOTP API
    navigator.credentials.get({
      otp: { transport:['sms'] },
      signal: ac.signal
    }).then(otp => {
      input.value = otp.code;
      // Automatically submit the form when an OTP is obtained.
      if (form) form.submit();
    }).catch(err => {
      console.log(err);
    });
  });
}