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

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

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

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

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

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

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

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

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

<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", אין צורך להשתמש בפתרון העקיף type="tel" שהוא שגוי מבחינה סמנטית.

autocomplete="one-time-code"

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

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

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

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

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

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

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

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

עיצוב הטקסט בהודעת ה-SMS

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

כלל הפורמט הבסיסי הוא כזה: בסוף הודעת ה-SMS צריך להוסיף את הדומיין של הנמען עם התחילית @, ואת הסיסמה החד-פעמית עם התחילית #.

לדוגמה:

Your OTP is 123456

@web-otp.glitch.me #123456

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

כללים מדויקים לעיצוב

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

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

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

  • הקוד החד-פעמי יהיה קשור לדומיין. אם המשתמש נמצא בדומיינים אחרים מאלה שצוינו בהודעת ה-SMS, ההצעה לקוד האימות לא תופיע. בנוסף, כך אפשר לצמצם את הסיכון למתקפות פישינג ולפריצה לחשבונות.
  • הדפדפן יוכל עכשיו לחלץ את ה-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 in action.

במאמר אימות מספרי טלפון באינטרנט באמצעות 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);
    });
  });
}