03-7440020+1 917-277-5362+44 203 146-8524
כניסת לקוחות
פתחו חשבון חינם
מדריך למפתחים

OTP למפתחים: חבילות קוד ל-Node.js, Python ו-PHP

שולחים קוד אימות ב-SMS ובודקים אותו בכמה שורות קוד, בלי לבנות בעצמכם את יצירת הקוד, התוקף, מגבלת הניסיונות וזמן ההמתנה.

מאת צוות מסר 10·פורסם ·מצאתם טעות?

שליחת קוד אימות היא קריאה אחת ל-API (ההסבר המלא כאן). מה שמסביב לה הוא העבודה: ליצור קוד אקראי, לשמור אותו בצורה בטוחה, לבדוק שהוא עדיין בתוקף, לחסום ניחושים, ולא לשלוח עשרה מסרונים למי שלוחץ עשר פעמים על "שלח שוב". החבילות של מסר 10 עושות את כל זה, ונכתבו כך שיתנהגו אותו דבר בשלוש השפות.

מה החבילות עושות

התקנה

Node.js, מ-npm:

npm install @meser10/otp

Python, ישירות מ-GitHub:

pip install "git+https://github.com/meser10/meser10-otp#subdirectory=python"

PHP, עם Composer:

composer config repositories.meser10-otp vcs https://github.com/meser10/meser10-otp
composer require meser10/otp:^1.0

מה צריך מחשבון מסר 10: יתרת SMS, מפתח API (הגדרות משתמש, מפתח API) ושם שולח מאושר, עד 11 אותיות באנגלית או ספרות, או מספר נייד שאומת בחשבון. בנוסף צריך סוד אקראי של 16 תווים לפחות, זהה בכל השרתים, שאיתו נחתמים הקודים השמורים.

Node.js

const { Meser10Otp, Meser10Error } = require('@meser10/otp');

const otp = new Meser10Otp({
  apiKey: process.env.MESER10_API_KEY,
  sender: 'MyShop',
  secret: process.env.OTP_SECRET,
  message: 'Your MyShop code is {code}',
});

async function main(userInput) {
  await otp.sendCode('054-123-4567');
  try {
    await otp.verifyCode('054-123-4567', userInput); // true
  } catch (e) {
    if (e instanceof Meser10Error) console.log(e.code); // wrong_code, locked...
  }
}

ב-Next.js, route אחד שמטפל גם בשליחה וגם בבדיקה:

// app/api/otp/route.js
import { Meser10Otp, Meser10Error } from '@meser10/otp';
import { redisStore } from '@/lib/otp-store';

const otp = new Meser10Otp({ apiKey: process.env.MESER10_API_KEY, sender: 'MyShop', secret: process.env.OTP_SECRET, store: redisStore });

export async function POST(req) {
  const { phone, code } = await req.json();
  try {
    if (code) return Response.json({ ok: await otp.verifyCode(phone, code) });
    await otp.sendCode(phone);
    return Response.json({ sent: true });
  } catch (e) {
    if (e instanceof Meser10Error) return Response.json({ error: e.code }, { status: 400 });
    throw e;
  }
}

Python

import os
from meser10_otp import Meser10Otp, Meser10Error

otp = Meser10Otp(os.environ["MESER10_API_KEY"], sender="MyShop", secret=os.environ["OTP_SECRET"])

otp.send_code("054-123-4567")
try:
    otp.verify_code("054-123-4567", user_input)   # True
except Meser10Error as e:
    print(e.code)

PHP

use Meser10\Otp\Otp;
use Meser10\Otp\RedisStore;
use Meser10\Otp\Meser10Error;

$redis = new Redis();
$redis->connect('127.0.0.1');

$otp = new Otp([
    'apiKey' => getenv('MESER10_API_KEY'),
    'sender' => 'MyShop',
    'secret' => getenv('OTP_SECRET'),
    'store'  => new RedisStore($redis),
]);

$otp->sendCode('054-123-4567');
try {
    $otp->verifyCode('054-123-4567', $_POST['code'] ?? ''); // true
} catch (Meser10Error $e) {
    echo $e->errorCode;
}

store משותף

בשרת אחד שרץ ברציפות אפשר להשתמש בברירת המחדל של Node ו-Python, ששומרת בזיכרון. בכל מצב אחר, כמה שרתים, כמה workers או serverless, מעבירים store שמשותף לכולם. ה-store צריך ארבע פעולות: get, set, delete ו-incr אטומי. ה-incr הוא מה ששומר על מגבלת הניסיונות ועל זמן ההמתנה גם כשמגיעות כמה בקשות באותו רגע. ב-README של כל חבילה יש store מוכן על Redis.

הגדרות

הגדרהברירת מחדלמה היא קובעת
codeLength6אורך הקוד, 4 עד 8 ספרות
ttlSeconds300כמה זמן הקוד בתוקף
maxAttempts5ניסיונות שגויים עד שהקוד ננעל
resendSeconds60זמן המתנה לפני קוד חדש לאותו מספר
messageYour verification code is {code}נוסח ההודעה. {code} מוחלף בקוד

ב-Python שמות ההגדרות באותיות קטנות עם קו תחתון, למשל code_length ו-ttl_seconds.

שגיאות

כל שגיאה מגיעה עם קוד קבוע, כך שאפשר להציג לגולש הודעה מתאימה:

קודמה קרה
invalid_phoneהמספר אינו נייד ישראלי. לא נשלח כלום
too_soonביקשו קוד חדש לפני שזמן ההמתנה נגמר
api_errorמסר 10 דחה את השליחה, למשל שם שולח שלא אושר. הסיבה בהודעת השגיאה
networkלא הייתה תקשורת עם מסר 10
no_codeאין קוד פעיל למספר הזה: לא נשלח קוד, הקוד כבר נוצל, או שתוקפו פג
expiredתוקף הקוד פג (בדרך כלל מגיע כ-no_code, כי הקוד כבר נמחק)
wrong_codeהקוד שגוי. ההודעה מציינת כמה ניסיונות נשארו
lockedיותר מדי ניסיונות שגויים. צריך לבקש קוד חדש
bad_responseתשובה לא צפויה מהשרת של מסר 10
configהגדרה שגויה, למשל secret קצר מ-16 תווים, חסר store ב-PHP, או ערך מחוץ לטווח

כדאי להוסיף מעל החבילה גם מגבלה לפי כתובת IP ולפי חשבון: כל קוד הוא מסרון בתשלום.

קוד פתוח

הקוד של שלוש החבילות נמצא ב-GitHub, ברישיון MIT, עם הבדיקות האוטומטיות:

https://github.com/meser10/meser10-otp

שאלות נפוצות

כמה עולות החבילות?
החבילות חינמיות ובקוד פתוח (רישיון MIT). כל קוד שנשלח הוא מסרון אחד מהיתרה בחשבון מסר 10, לפי מחירון המסרונים.
למה צריך store משותף?
הקוד, מונה הניסיונות וזמן ההמתנה צריכים להיות זמינים לכל השרתים שמטפלים בבקשות. ברירת המחדל ב-Node וב-Python שומרת בזיכרון של תהליך אחד, ומתאימה רק לשרת אחד שרץ ברציפות. ב-serverless או בכמה שרתים מעבירים store על Redis או על מסד הנתונים.
למה ב-PHP אין ברירת מחדל של session?
גולש יכול למחוק את העוגייה ולאפס את ה-session, ואיתו את המגבלות. לכן ב-PHP חובה להעביר store משותף, ויש RedisStore מוכן.
אפשר לשלוח למספרים מחוץ לישראל?
כברירת מחדל החבילות מקבלות רק מספרי סלולר ישראליים, כדי לא לשלוח בטעות לאדם זר. אפשר להעביר פונקציה משלכם לקריאת המספר.
יש תוסף לוורדפרס?
כן, Meser10 OTP, עם קוד בהרשמה, בקופה של WooCommerce ובכניסה לאתר.

עוד באותו נושא: OTP: קוד אימות חד-פעמי ב-SMS, תוסף OTP לוורדפרס וה-API למפתחים.