סיסמאות הן אחד מהדברים האלה שנורמלנו רק בגלל שחיינו איתן במשך שנים. משתמשים שוכחים אותם, עושים בהם שימוש חוזר, כותבים אותם היכן שהם לא צריכים. על הצוותים לנהל איפוסים, מדיניות, גיבוב, הדלפות, פישינג ותמיכה.
ה-passkey לא הופכים את האימות למושלם, אבל הם מסירים בעיה ענקית: השרת כבר לא צריך לשמור סוד משותף עם המשתמש.
מה באמת קורה
passkey הוא אישור המבוסס על WebAuthn. כאשר המשתמש יוצר אותו, המכשיר יוצר זוג מפתחות:
- מפתח פרטי, שנשאר במכשיר או במנהל הסיסמאות;
- מפתח ציבורי, שהשרת יכול לשמור.
בכניסה לשרת לא שואלים "תגיד לי את הסיסמה". שלח אתגר אקראי. המכשיר חותם עליו עם המפתח הפרטי. השרת מאמת את החתימה באמצעות המפתח הציבורי.
זה החלק הנחמד: אם מסד הנתונים נגנב, אין בפנים סיסמאות לפצח. ואם משתמש מגיע לדומיין מזויף, ה-passkey אינו תקף עבור אותו דומיין. זו לא רק נוחות, זו הגנה קונקרטית מפני פישינג.
הדפדפן פועל כגשר
בדפדפן שני API העיקריים הם:
navigator.credentials.create()כדי ליצור passkey;navigator.credentials.get()כדי להשתמש בו במהלך הכניסה.
אבל ההיגיון החשוב הוא בשרת. השרת חייב ליצור את האתגר, לשמור אותו באופן זמני, לאמת את התגובה, לבדוק את המקור ואת Relying Party ID, ואז ליצור את ההפעלה.
חלק הלקוח צריך להיות כמעט משעמם:
const options = await fetch('/api/passkeys/login/options').then((r) => r.json()); const credential = await navigator.credentials.get({ publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(options), }); await fetch('/api/passkeys/login/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credential?.toJSON()), });
אם אתה מוצא את עצמך מיישם הצפנה WebAuthn ביד, הפסק. השתמש בספרייה חזקה בצד השרת. השגיאות כאן אינן "באגים חמודים", אלא חורי אימות.
מה לשמור במסד הנתונים
אין צורך להציל חצי עולם. בדרך כלל מספיק:
- מזהה של האישור;
- מפתח ציבורי;
- משתמש מחובר;
- כל מונה אימות או מטא נתונים;
- הובלות, אם שימושי עבור UX;
- שם שנבחר על ידי המשתמש;
- תאריך יצירה ושימוש אחרון.
טבלה מינימלית עשויה להיראות כך:
create table passkey_credentials ( id uuid primary key default gen_random_uuid(), user_id uuid not null references users(id), credential_id text not null unique, public_key text not null, name text, created_at timestamptz not null default now(), last_used_at timestamptz );
אז הייתי מוסיף יומני ביקורת והתראות: אם מישהו יוצר passkey חדש בחשבון שלי, אני רוצה לדעת על זה.
UX חשוב יותר מהדגמה
ההדגמה של passkey תמיד יפה: אתה לוחץ, Face ID, אתה בפנים. המוצר בפועל מסובך יותר.
מישהו מחליף את הטלפון שלו. מישהו משתמש במחשב חברה נעול. יש אנשים שלא מבינים מדוע הדפדפן מציע passkey. מישהו מאבד גישה למכשיר שלו.
זו הסיבה שלא הייתי מתחיל ב"מהיום אין יותר סיסמאות לכולם". הייתי מתחיל ככה:
- passkey אופציונלי עבור משתמשים פנימיים;
- הצעה ליצור אחד לאחר כניסה מוצלחת;
- דף חשבון לשינוי שם ולהסרה של passkey;
- סתירה ברורה;
- השקה הדרגתית בכניסה הראשית.
הטקסט בממשק צריך להיות פשוט. "השתמש במסך הנעילה של המכשיר שלך" עדיף על "אימות עם אישור תושב FIDO2".
טעויות שהייתי נמנע מהם
אל תייצר אתגרים על הלקוח. האתגר נוצר בשרת ויש לאמת אותו פעם אחת בלבד.
אל תסמוך רק על מזהה האישור. עליך לאמת חתימה, אתגר, מקור וRelying Party ID.
אל תמחק תקלות לפני שיש לך זרימת התאוששות טובה. חסר סיסמה לא חייב להפוך ל"אם אתה מאבד את הטלפון שלך אתה בחוץ לנצח".
אל תתייחס ל-passkey כאל כפתור חזיתי בלבד. הסתרת כפתור אינה אבטחה: האימות האמיתי הוא בצד השרת.
Passkey-ראשון או passkey ידידותי?
עבור מוצר חדש אתה יכול לחשוב passkey-קודם כל. עבור אפליקציה קיימת אני מעדיף passkey ידידותית: הוסף את passkey כשיטה מומלצת, מדוד הצלחה ובעיות, ואז הפחית בשלווה את משקל הסיסמה.
ההגירה האידיאלית אינה מורגשת. המשתמש מגלה שהכניסה קלה יותר, לא שהחברה שינתה את פרוטוקול האימות שלה.
מסקנה
ה-passkey מעניינים מכיוון שהם משפרים את האבטחה ואת ה-UX בו זמנית, וזה נדיר. הם אינם שרביט קסמים: שחזור, תאימות, תמיכה והפצה נותרו מתוכננים היטב.
אבל השינוי הבסיסי הוא חזק. הפסיקו לבקש ממשתמשים להמציא סודות ולהגן עליהם. אתה נותן למכשיר לחתום על הוכחה קריפטוגרפית הקשורה לדומיין שלך. פחות זיכרון אנושי, פחות דיוג, פחות איפוסי סיסמה. הייתי אומר ששווה לקחת אותם ברצינות.