1. לפני שמתחילים
השימוש במפתחות גישה במקום בסיסמאות הוא דרך מצוינת לאתרים להפוך את חשבונות המשתמשים שלהם לבטוחים יותר, פשוטים יותר וקלים יותר לשימוש. עם מפתח גישה, משתמש יכול להיכנס לאתר או לאפליקציה באמצעות התכונה לביטול נעילת המסך במכשיר, כמו טביעת אצבע, זיהוי פנים או קוד אימות במכשיר. כדי שמשתמש יוכל להיכנס באמצעות מפתח גישה, צריך ליצור אותו, לשייך אותו לחשבון משתמש ולאחסן את המפתח הציבורי שלו בשרת.
ב-codelab הזה תלמדו איך להפוך כניסה בסיסית באמצעות שם משתמש וסיסמה לכניסה באמצעות מפתחות גישה, ואיך לכלול את הדברים הבאים:
- לחצן ליצירת מפתח גישה אחרי שהמשתמש מתחבר.
- ממשק משתמש שבו מוצגת רשימה של מפתחות גישה רשומים.
- טופס הכניסה הקיים שמאפשר למשתמשים להיכנס באמצעות מפתח גישה רשום באמצעות מילוי אוטומטי של הטופס.
דרישות מוקדמות
- הבנה בסיסית של JavaScript
- הבנה בסיסית של מפתחות גישה
- הבנה בסיסית של Web Authentication API (WebAuthn)
מה תלמדו
- איך יוצרים מפתח גישה
- איך מאמתים משתמשים באמצעות מפתח גישה.
- איך מאפשרים לטופס להציע מפתח גישה כאפשרות כניסה.
מה נדרש
אחד משילובי המכשירים הבאים:
- Google Chrome עם מכשיר Android בגרסה 9 ואילך, רצוי עם חיישן ביומטרי.
- Chrome במכשיר Windows עם Windows מגרסה 10 ואילך.
- Safari 16 ואילך באייפון עם iOS 16 ואילך, או באייפד עם iPadOS 16 ואילך.
- Safari מגרסה 16 ומעלה או Chrome במכשיר שולחני של Apple עם macOS Ventura ומעלה.
2. להגדרה
ב-codelab הזה משתמשים בשירות שנקרא Glitch, שמאפשר לערוך קוד בצד הלקוח ובצד השרת באמצעות JavaScript, ולפרוס אותו רק מהדפדפן.
פותחים את הפרויקט.
- פותחים את הפרויקט ב-Glitch.
- לוחצים על Remix כדי ליצור עותק של פרויקט Glitch.
- בתפריט הניווט בחלק התחתון של Glitch, לוחצים על תצוגה מקדימה > תצוגה מקדימה בחלון חדש. תיפתח כרטיסייה נוספת בדפדפן.
בדיקת המצב ההתחלתי של האתר
- בכרטיסייה של התצוגה המקדימה, מזינים שם משתמש אקראי ולוחצים על הבא.
- מזינים סיסמה אקראית ולוחצים על כניסה. הסיסמה מתעלמת, אבל עדיין מתבצעת אימות ואתם מגיעים לדף הבית.
- אם רוצים לשנות את השם המוצג, משנים אותו. זה כל מה שאפשר לעשות במצב ההתחלתי.
- לוחצים על יציאה.
במצב הזה, המשתמשים צריכים להזין סיסמה בכל פעם שהם מתחברים. אתם מוסיפים תמיכה במפתחות גישה לטופס הזה כדי שהמשתמשים יוכלו להיכנס באמצעות הפונקציה של נעילת המסך במכשיר. אפשר לנסות את מצב הסיום בכתובת https://passkeys-codelab.glitch.me/.
מידע נוסף על אופן הפעולה של מפתחות גישה זמין במאמר איך פועלים מפתחות גישה?
3. הוספת אפשרות ליצירת מפתח גישה
כדי לאפשר למשתמשים לבצע אימות באמצעות מפתח גישה, צריך לתת להם את האפשרות ליצור ולרשום מפתח גישה, ולשמור את המפתח הציבורי שלו בשרת.
אתם רוצים לאפשר יצירה של מפתח גישה אחרי שהמשתמש מתחבר באמצעות סיסמה, ולהוסיף ממשק משתמש שמאפשר למשתמשים ליצור מפתח גישה ולראות רשימה של כל מפתחות הגישה הרשומים בדף /home
. בקטע הבא יוצרים פונקציה שיוצרת מפתח גישה ורושמת אותו.
יצירת פונקציית registerCredential()
- ב-Glitch, עוברים לקובץ
public/client.js
וגוללים לסוף. - אחרי התגובה הרלוונטית, מוסיפים את הפונקציה הבאה
registerCredential()
:
public/client. js
// TODO: Add an ability to create a passkey: Create the registerCredential() function.
export async function registerCredential() {
// TODO: Add an ability to create a passkey: Obtain the challenge and other options from the server endpoint.
// TODO: Add an ability to create a passkey: Create a credential.
// TODO: Add an ability to create a passkey: Register the credential to the server endpoint.
};
הפונקציה הזו יוצרת מפתח גישה בשרת ורושמת אותו.
קבלת האתגר ואפשרויות אחרות מנקודת הקצה של השרת
לפני שיוצרים מפתח גישה, צריך לבקש מהשרת פרמטרים להעברה ב-WebAuthn, כולל אתגר. WebAuthn הוא ממשק API של דפדפן שמאפשר למשתמש ליצור מפתח גישה ולאמת את המשתמש באמצעות מפתח הגישה. למזלכם, כבר יש לכם נקודת קצה בשרת שמגיבה עם פרמטרים כאלה ב-codelab הזה.
- כדי לקבל את האתגר ואפשרויות אחרות מנקודת הקצה של השרת, מוסיפים את הקוד הבא לגוף הפונקציה
registerCredential()
אחרי התגובה הרלוונטית:
public/client.js
// TODO: Add an ability to create a passkey: Obtain the challenge and other options from the server endpoint.
const options = await _fetch('/auth/registerRequest');
קטע הקוד הבא כולל אפשרויות לדוגמה שמתקבלות מהשרת:
{
challenge: *****,
rp: {
id: "example.com",
},
user: {
id: *****,
name: "john78",
displayName: "John",
},
pubKeyCredParams: [{
alg: -7, type: "public-key"
},{
alg: -257, type: "public-key"
}],
excludeCredentials: [{
id: *****,
type: 'public-key',
transports: ['internal', 'hybrid'],
}],
authenticatorSelection: {
authenticatorAttachment: "platform",
requireResidentKey: true,
}
}
הפרוטוקול בין השרת ללקוח לא נכלל במפרט WebAuthn. עם זאת, השרת של ה-codelab הזה נועד להחזיר קובץ JSON שדומה ככל האפשר למילון PublicKeyCredentialCreationOptions
שמועבר אל WebAuthn navigator.credentials.create()
API.
הטבלה הבאה לא כוללת את כל הפרמטרים, אבל היא מכילה את הפרמטרים החשובים במילון PublicKeyCredentialCreationOptions
:
פרמטרים | תיאורים |
אתגר שנוצר על ידי השרת באובייקט | |
מזהה ייחודי של משתמש. הערך הזה צריך להיות אובייקט | |
בשדה הזה צריך להיות מזהה ייחודי של החשבון שהמשתמש יכול לזהות, כמו כתובת האימייל או שם המשתמש שלו. הוא מוצג בבורר החשבונות. (אם משתמשים בשם משתמש, צריך להשתמש באותו ערך כמו באימות באמצעות סיסמה). | |
השדה הזה הוא אופציונלי, והוא מאפשר להזין שם ידידותי למשתמש לחשבון. השם לא צריך להיות ייחודי ויכול להיות השם שהמשתמש בחר. אם אין באתר ערך מתאים שאפשר לכלול כאן, מעבירים מחרוזת ריקה. יכול להיות שהאפשרות הזו תוצג בבורר החשבונות, בהתאם לדפדפן. | |
מזהה הצד המסתמך (RP) הוא דומיין. אתר יכול לציין את הדומיין שלו או סיומת שניתן לרשום. לדוגמה, אם המקור של ספק הזהויות הוא https://login.example.com:1337, מזהה ספק הזהויות יכול להיות | |
בשדה הזה מצוינים האלגוריתמים של המפתחות הציבוריים שנתמכים על ידי ה-RP. מומלץ להגדיר את הערך | |
הצגת רשימה של מזהי אמצעי אימות שכבר רשומים כדי למנוע רישום של אותו מכשיר פעמיים. אם מספקים את המאפיין | |
הגדרה לערך של | |
הגדרה לערך בוליאני | |
מגדירים ערך של |
יצירת פרטי כניסה
- בגוף הפונקציה
registerCredential()
אחרי התגובה הרלוונטית, ממירים בחזרה לבינארי פרמטרים מסוימים שמקודדים באמצעות Base64URL, במיוחד המחרוזותuser.id
ו-challenge
, ומופעים של המחרוזתid
שכלולים במערךexcludeCredentials
:
public/client.js
// TODO: Add an ability to create a passkey: Create a credential.
// Base64URL decode some values.
options.user.id = base64url.decode(options.user.id);
options.challenge = base64url.decode(options.challenge);
if (options.excludeCredentials) {
for (let cred of options.excludeCredentials) {
cred.id = base64url.decode(cred.id);
}
}
- בשורה הבאה, מגדירים את
authenticatorSelection.authenticatorAttachment
ל-"platform"
ואתauthenticatorSelection.requireResidentKey
ל-true
. ההגדרה הזו מאפשרת שימוש רק באמצעי אימות של הפלטפורמה (המכשיר עצמו) עם יכולת גילוי של פרטי הכניסה.
public/client.js
// Use platform authenticator and discoverable credential.
options.authenticatorSelection = {
authenticatorAttachment: 'platform',
requireResidentKey: true
}
- בשורה הבאה, קוראים ל-
navigator.credentials.create()
method כדי ליצור אישור.
public/client.js
// Invoke the WebAuthn create() method.
const cred = await navigator.credentials.create({
publicKey: options,
});
במהלך השיחה הזו, הדפדפן מנסה לאמת את זהות המשתמש באמצעות נעילת המסך של המכשיר.
רישום פרטי הכניסה לנקודת הקצה של השרת
אחרי שהמשתמש מאמת את הזהות שלו, נוצר מפתח גישה והוא נשמר. האתר מקבל אובייקט של פרטי כניסה שמכיל מפתח ציבורי שאפשר לשלוח לשרת כדי לרשום את מפתח הגישה.
קטע הקוד הבא מכיל אובייקט לדוגמה של פרטי כניסה:
{
"id": *****,
"rawId": *****,
"type": "public-key",
"response": {
"clientDataJSON": *****,
"attestationObject": *****,
"transports": ["internal", "hybrid"]
},
"authenticatorAttachment": "platform"
}
הטבלה הבאה לא כוללת את כל הפרמטרים, אבל היא מכילה את הפרמטרים החשובים באובייקט PublicKeyCredential
:
פרמטרים | תיאורים |
מזהה של מפתח הגישה שנוצר בקידוד Base64URL. המזהה הזה עוזר לדפדפן לקבוע אם יש במכשיר מפתח גישה תואם בזמן האימות. הערך הזה צריך להיות מאוחסן במסד הנתונים בקצה העורפי. | |
גרסת האובייקט | |
אובייקט | |
אובייקט אימות מקודד | |
רשימת פרוטוקולי התקשורת שהמכשיר תומך בהם: | |
הפונקציה מחזירה |
כדי לשלוח את אובייקט פרטי הכניסה לשרת, פועלים לפי השלבים הבאים:
- מקודדים את הפרמטרים הבינאריים של פרטי הכניסה בפורמט Base64URL כדי שאפשר יהיה להעביר אותם לשרת כמחרוזת:
public/client.js
// TODO: Add an ability to create a passkey: Register the credential to the server endpoint.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;
// The authenticatorAttachment string in the PublicKeyCredential object is a new addition in WebAuthn L3.
if (cred.authenticatorAttachment) {
credential.authenticatorAttachment = cred.authenticatorAttachment;
}
// Base64URL encode some values.
const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
const attestationObject = base64url.encode(cred.response.attestationObject);
// Obtain transports.
const transports = cred.response.getTransports ? cred.response.getTransports() : [];
credential.response = {
clientDataJSON,
attestationObject,
transports
};
- בשורה הבאה, שולחים את האובייקט לשרת:
public/client.js
return await _fetch('/auth/registerResponse', credential);
כשמריצים את התוכנית, השרת מחזיר HTTP code 200
, שמציין שפרטי הכניסה רשומים.
עכשיו יש לך את הפונקציה המלאה registerCredential()
.
בדיקת קוד הפתרון של הקטע הזה
public/client.js
// TODO: Add an ability to create a passkey: Create the registerCredential() function.
export async function registerCredential() {
// TODO: Add an ability to create a passkey: Obtain the challenge and other options from server endpoint.
const options = await _fetch('/auth/registerRequest');
// TODO: Add an ability to create a passkey: Create a credential.
// Base64URL decode some values.
options.user.id = base64url.decode(options.user.id);
options.challenge = base64url.decode(options.challenge);
if (options.excludeCredentials) {
for (let cred of options.excludeCredentials) {
cred.id = base64url.decode(cred.id);
}
}
// Use platform authenticator and discoverable credential.
options.authenticatorSelection = {
authenticatorAttachment: 'platform',
requireResidentKey: true
}
// Invoke the WebAuthn create() method.
const cred = await navigator.credentials.create({
publicKey: options,
});
// TODO: Add an ability to create a passkey: Register the credential to the server endpoint.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;
// The authenticatorAttachment string in the PublicKeyCredential object is a new addition in WebAuthn L3.
if (cred.authenticatorAttachment) {
credential.authenticatorAttachment = cred.authenticatorAttachment;
}
// Base64URL encode some values.
const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
const attestationObject =
base64url.encode(cred.response.attestationObject);
// Obtain transports.
const transports = cred.response.getTransports ?
cred.response.getTransports() : [];
credential.response = {
clientDataJSON,
attestationObject,
transports
};
return await _fetch('/auth/registerResponse', credential);
};
4. פיתוח ממשק משתמש לרישום ולניהול של פרטי כניסה באמצעות מפתח גישה
עכשיו שהפונקציה registerCredential()
זמינה, צריך כפתור כדי להפעיל אותה. בנוסף, צריך להציג רשימה של מפתחות גישה רשומים.
הוספת HTML של פלייסהולדר
- ב-Glitch, עוברים לקובץ
views/home.html
. - אחרי התגובה הרלוונטית, מוסיפים placeholder של ממשק משתמש שמציג לחצן לרישום מפתח גישה ורשימה של מפתחות גישה:
views/home.html
<!-- TODO: Add an ability to create a passkey: Add placeholder HTML. -->
<section>
<h3 class="mdc-typography mdc-typography--headline6"> Your registered
passkeys:</h3>
<div id="list"></div>
</section>
<p id="message" class="instructions"></p>
<mwc-button id="create-passkey" class="hidden" icon="fingerprint" raised>Create a passkey</mwc-button>
הרכיב div#list
הוא ה-placeholder של הרשימה.
בדיקה אם יש תמיכה במפתחות גישה
כדי להציג את האפשרות ליצור מפתח גישה רק למשתמשים עם מכשירים שתומכים במפתחות גישה, קודם צריך לבדוק אם WebAuthn זמין. אם כן, צריך להסיר את המחלקה hidden
כדי שהלחצן יצירת מפתח גישה יוצג.
כדי לבדוק אם סביבה תומכת במפתחות גישה, פועלים לפי השלבים הבאים:
- בסוף הקובץ
views/home.html
, אחרי ההערה הרלוונטית, כותבים תנאי שמופעל אםwindow.PublicKeyCredential
,PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable
ו-PublicKeyCredential.isConditionalMediationAvailable
הםtrue
.
views/home.html
// TODO: Add an ability to create a passkey: Check for passkey support.
const createPasskey = $('#create-passkey');
// Feature detections
if (window.PublicKeyCredential &&
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
PublicKeyCredential.isConditionalMediationAvailable) {
- בגוף התנאי, בודקים אם המכשיר יכול ליצור מפתח גישה ואז בודקים אם אפשר להציע את מפתח הגישה במילוי אוטומטי של טופס.
views/home.html
try {
const results = await Promise.all([
// Is platform authenticator available in this browser?
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),
// Is conditional UI available in this browser?
PublicKeyCredential.isConditionalMediationAvailable()
]);
- אם כל התנאים מתקיימים, מוצג לחצן ליצירת מפתח גישה. אחרת, מוצגת הודעת אזהרה.
views/home.html
if (results.every(r => r === true)) {
// If conditional UI is available, reveal the Create a passkey button.
createPasskey.classList.remove('hidden');
} else {
// If conditional UI isn't available, show a message.
$('#message').innerText = 'This device does not support passkeys.';
}
} catch (e) {
console.error(e);
}
} else {
// If WebAuthn isn't available, show a message.
$('#message').innerText = 'This device does not support passkeys.';
}
הצגת מפתחות גישה רשומים ברשימה
- מגדירים פונקציה
renderCredentials()
שמביאה מפתחות גישה רשומים מהשרת ומציגה אותם ברשימה. למזלכם, כבר יש לכם את נקודת הקצה של השרת/auth/getKeys
כדי לאחזר מפתחות גישה רשומים עבור המשתמש שמחובר לחשבון.
views/home.html
// TODO: Add an ability to create a passkey: Render registered passkeys in a list.
async function renderCredentials() {
const res = await _fetch('/auth/getKeys');
const list = $('#list');
const creds = html`${res.length > 0 ? html`
<mwc-list>
${res.map(cred => html`
<mwc-list-item>
<div class="list-item">
<div class="entity-name">
<span>${cred.name || 'Unnamed' }</span>
</div>
<div class="buttons">
<mwc-icon-button data-cred-id="${cred.id}"
data-name="${cred.name || 'Unnamed' }" @click="${rename}"
icon="edit"></mwc-icon-button>
<mwc-icon-button data-cred-id="${cred.id}" @click="${remove}"
icon="delete"></mwc-icon-button>
</div>
</div>
</mwc-list-item>`)}
</mwc-list>` : html`
<mwc-list>
<mwc-list-item>No credentials found.</mwc-list-item>
</mwc-list>`}`;
render(creds, list);
};
- בשורה הבאה, מפעילים את הפונקציה
renderCredentials()
כדי להציג את מפתחות הגישה הרשומים ברגע שהמשתמש מגיע לדף/home
כאתחול.
views/home.html
renderCredentials();
יצירה ורישום של מפתח גישה
כדי ליצור ולרשום מפתח גישה, צריך לקרוא לפונקציה registerCredential()
שהטמעתם קודם.
כדי להפעיל את הפונקציה registerCredential()
כשלוחצים על הלחצן יצירת מפתח גישה, פועלים לפי השלבים הבאים:
- בקובץ אחרי ה-placeholder HTML, מחפשים את ההצהרה הבאה
import
:
views/home.html
import {
$,
_fetch,
loading,
updateCredential,
unregisterCredential,
} from '/client.js';
- בסוף גוף ההצהרה
import
, מוסיפים את הפונקציהregisterCredential()
.
views/home.html
// TODO: Add an ability to create a passkey: Create and register a passkey.
import {
$,
_fetch,
loading,
updateCredential,
unregisterCredential,
registerCredential
} from '/client.js';
- בסוף הקובץ, אחרי התגובה הרלוונטית, מגדירים פונקציה
register()
שמפעילה את הפונקציהregisterCredential()
וממשק משתמש לטעינה, וקוראת לפונקציהrenderCredentials()
אחרי הרשמה. כך ברור שהדפדפן יוצר מפתח גישה ומציג הודעת שגיאה אם משהו משתבש.
views/home.html
// TODO: Add an ability to create a passkey: Create and register a passkey.
async function register() {
try {
// Start the loading UI.
loading.start();
// Start creating a passkey.
await registerCredential();
// Stop the loading UI.
loading.stop();
// Render the updated passkey list.
renderCredentials();
- בגוף הפונקציה
register()
, תופסים חריגים. השיטהnavigator.credentials.create()
מחזירה שגיאתInvalidStateError
אם מפתח גישה כבר קיים במכשיר. הבדיקה מתבצעת באמצעות המערךexcludeCredentials
. במקרה כזה, אתם מציגים למשתמש הודעה רלוונטית. היא גם מחזירה שגיאתNotAllowedError
אם המשתמש מבטל את תיבת הדו-שיח לאימות. במקרה כזה, המערכת מתעלמת ממנו בשקט.
views/home.html
} catch (e) {
// Stop the loading UI.
loading.stop();
// An InvalidStateError indicates that a passkey already exists on the device.
if (e.name === 'InvalidStateError') {
alert('A passkey already exists for this device.');
// A NotAllowedError indicates that the user canceled the operation.
} else if (e.name === 'NotAllowedError') {
Return;
// Show other errors in an alert.
} else {
alert(e.message);
console.error(e);
}
}
};
- בשורה שאחרי הפונקציה
register()
, מצרפים את הפונקציהregister()
לאירועclick
של הלחצן יצירת מפתח גישה.
views/home.html
createPasskey.addEventListener('click', register);
בדיקת קוד הפתרון של הקטע הזה
views/home.html
<!-- TODO: Add an ability to create a passkey: Add placeholder HTML. -->
<section>
<h3 class="mdc-typography mdc-typography--headline6"> Your registered
passkeys:</h3>
<div id="list"></div>
</section>
<p id="message" class="instructions"></p>
<mwc-button id="create-passkey" class="hidden" icon="fingerprint" raised>Create a passkey</mwc-button>
views/home.html
// TODO: Add an ability to create a passkey: Create and register a passkey.
import {
$,
_fetch,
loading,
updateCredential,
unregisterCredential,
registerCredential
} from '/client.js';
views/home.html
// TODO: Add an ability to create a passkey: Check for passkey support.
const createPasskey = $('#create-passkey');
// Feature detections
if (window.PublicKeyCredential &&
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
PublicKeyCredential.isConditionalMediationAvailable) {
try {
const results = await Promise.all([
// Is platform authenticator available in this browser?
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),
// Is conditional UI available in this browser?
PublicKeyCredential.isConditionalMediationAvailable()
]);
if (results.every(r => r === true)) {
// If conditional UI is available, reveal the Create a passkey button.
createPasskey.classList.remove('hidden');
} else {
// If conditional UI isn't available, show a message.
$('#message').innerText = 'This device does not support passkeys.';
}
} catch (e) {
console.error(e);
}
} else {
// If WebAuthn isn't available, show a message.
$('#message').innerText = 'This device does not support passkeys.';
}
// TODO: Add an ability to create a passkey: Render registered passkeys in a list.
async function renderCredentials() {
const res = await _fetch('/auth/getKeys');
const list = $('#list');
const creds = html`${res.length > 0 ? html`
<mwc-list>
${res.map(cred => html`
<mwc-list-item>
<div class="list-item">
<div class="entity-name">
<span>${cred.name || 'Unnamed' }</span>
</div>
<div class="buttons">
<mwc-icon-button data-cred-id="${cred.id}" data-name="${cred.name || 'Unnamed' }" @click="${rename}" icon="edit"></mwc-icon-button>
<mwc-icon-button data-cred-id="${cred.id}" @click="${remove}" icon="delete"></mwc-icon-button>
</div>
</div>
</mwc-list-item>`)}
</mwc-list>` : html`
<mwc-list>
<mwc-list-item>No credentials found.</mwc-list-item>
</mwc-list>`}`;
render(creds, list);
};
renderCredentials();
// TODO: Add an ability to create a passkey: Create and register a passkey.
async function register() {
try {
// Start the loading UI.
loading.start();
// Start creating a passkey.
await registerCredential();
// Stop the loading UI.
loading.stop();
// Render the updated passkey list.
renderCredentials();
} catch (e) {
// Stop the loading UI.
loading.stop();
// An InvalidStateError indicates that a passkey already exists on the device.
if (e.name === 'InvalidStateError') {
alert('A passkey already exists for this device.');
// A NotAllowedError indicates that the user canceled the operation.
} else if (e.name === 'NotAllowedError') {
Return;
// Show other errors in an alert.
} else {
alert(e.message);
console.error(e);
}
}
};
createPasskey.addEventListener('click', register);
רוצה לנסות?
אם ביצעתם את כל השלבים עד עכשיו, הטמעתם באתר את האפשרות ליצור, לרשום ולהציג מפתחות גישה.
כדי לנסות את התכונה, מבצעים את השלבים הבאים:
- בכרטיסיית התצוגה המקדימה, נכנסים באמצעות שם משתמש וסיסמה אקראיים.
- לוחצים על יצירת מפתח גישה.
- מאמתים את הזהות באמצעות השיטה לביטול הנעילה של המסך.
- מוודאים שמפתח הגישה רשום ומוצג בקטע מפתחות הגישה הרשומים שלך בדף האינטרנט.
שינוי שם והסרה של מפתחות גישה רשומים
אמורה להיות לכם אפשרות לשנות את השם של מפתחות הגישה הרשומים ברשימה או למחוק אותם. אפשר לבדוק איך זה עובד בקוד, כי הוא מגיע עם ה-codelab.
ב-Chrome, אפשר להסיר מפתחות גישה רשומים מהכתובת chrome://settings/passkeys במחשב או ממנהל הסיסמאות בהגדרות ב-Android.
כדי לקבל מידע על שינוי השם של מפתחות גישה רשומים והסרתם בפלטפורמות אחרות, אפשר לעיין בדפי התמיכה של הפלטפורמות הרלוונטיות.
5. הוספת אפשרות לאימות באמצעות מפתח גישה
המשתמשים יכולים ליצור ולרשום מפתח גישה, ומוכנים להשתמש בו כדרך בטוחה לאימות באתר. עכשיו צריך להוסיף לאתר יכולת אימות באמצעות מפתח גישה.
יצירת פונקציית authenticate()
- בקובץ
public/client.js
, אחרי ההערה הרלוונטית, יוצרים פונקציה בשםauthenticate()
שמאמתת את המשתמש באופן מקומי ואז מול השרת:
public/client.js
// TODO: Add an ability to authenticate with a passkey: Create the authenticate() function.
export async function authenticate() {
// TODO: Add an ability to authenticate with a passkey: Obtain the challenge and other options from the server endpoint.
// TODO: Add an ability to authenticate with a passkey: Locally verify the user and get a credential.
// TODO: Add an ability to authenticate with a passkey: Verify the credential.
};
קבלת האתגר ואפשרויות אחרות מנקודת הקצה של השרת
לפני שמבקשים מהמשתמש לבצע אימות, צריך לבקש מהשרת פרמטרים להעברה ב-WebAuthn, כולל אתגר.
- בגוף הפונקציה
authenticate()
, אחרי ההערה הרלוונטית, קוראים לפונקציה_fetch()
כדי לשלוח בקשתPOST
לשרת:
public/client.js
// TODO: Add an ability to authenticate with a passkey: Obtain the challenge and other options from the server endpoint.
const options = await _fetch('/auth/signinRequest');
השרת של ה-codelab הזה מתוכנן להחזיר JSON שדומה ככל האפשר למילון PublicKeyCredentialRequestOptions
שמועבר ל-WebAuthn navigator.credentials.get()
API. קטע הקוד הבא כולל אפשרויות לדוגמה שאתם אמורים לקבל:
{
"challenge": *****,
"rpId": "passkeys-codelab.glitch.me",
"allowCredentials": []
}
הטבלה הבאה לא כוללת את כל הפרמטרים, אבל היא מכילה את הפרמטרים החשובים במילון PublicKeyCredentialRequestOptions
:
פרמטרים | תיאורים |
אתגר שנוצר על ידי השרת באובייקט | |
מזהה RP הוא דומיין. אתר יכול לציין את הדומיין שלו או סיומת שניתן לרשום. הערך הזה חייב להיות זהה לפרמטר | |
המאפיין הזה משמש למציאת אמצעי אימות שעומדים בדרישות לאימות הזהות. כדי שהדפדפן יציג בורר חשבונות, מעבירים מערך ריק או משאירים את האפשרות לא מוגדרת. | |
מגדירים ערך של |
אימות מקומי של המשתמש וקבלת אישור
- בגוף הפונקציה
authenticate()
, אחרי ההערה הרלוונטית, ממירים את הפרמטרchallenge
בחזרה לבינארי:
public/client.js
// TODO: Add an ability to authenticate with a passkey: Locally verify the user and get a credential.
// Base64URL decode the challenge.
options.challenge = base64url.decode(options.challenge);
- מעבירים מערך ריק לפרמטר
allowCredentials
כדי לפתוח את בורר החשבונות כשמשתמש מאמת את עצמו:
public/client.js
// An empty allowCredentials array invokes an account selector by discoverable credentials.
options.allowCredentials = [];
הכלי לבחירת חשבון משתמש במידע של המשתמש ששמור עם מפתח הגישה.
- מפעילים את השיטה
navigator.credentials.get()
עם האפשרותmediation: 'conditional'
:
public/client.js
// Invoke the WebAuthn get() method.
const cred = await navigator.credentials.get({
publicKey: options,
// Request a conditional UI.
mediation: 'conditional'
});
האפשרות הזו מורה לדפדפן להציע מפתחות גישה באופן מותנה כחלק ממילוי אוטומטי של טופס.
אימות פרטי הכניסה
אחרי שהמשתמש מאמת את הזהות שלו באופן מקומי, אמור להתקבל אובייקט של פרטי כניסה שמכיל חתימה שאפשר לאמת בשרת.
קטע הקוד הבא כולל אובייקט לדוגמה PublicKeyCredential
:
{
"id": *****,
"rawId": *****,
"type": "public-key",
"response": {
"clientDataJSON": *****,
"authenticatorData": *****,
"signature": *****,
"userHandle": *****
},
authenticatorAttachment: "platform"
}
הטבלה הבאה לא כוללת את כל הפרמטרים, אבל היא מכילה את הפרמטרים החשובים באובייקט PublicKeyCredential
:
פרמטרים | תיאורים |
המזהה של פרטי הכניסה של מפתח הגישה המאומת בקידוד Base64URL. | |
גרסת האובייקט | |
אובייקט | |
אובייקט | |
אובייקט | |
אובייקט | |
מחזירה מחרוזת |
כדי לשלוח את אובייקט פרטי הכניסה לשרת, פועלים לפי השלבים הבאים:
- בגוף הפונקציה
authenticate()
, אחרי ההערה הרלוונטית, מקודדים את הפרמטרים הבינאריים של פרטי הכניסה כדי שאפשר יהיה להעביר אותם לשרת כמחרוזת:
public/client.js
// TODO: Add an ability to authenticate with a passkey: Verify the credential.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;
// Base64URL encode some values.
const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
const authenticatorData = base64url.encode(cred.response.authenticatorData);
const signature = base64url.encode(cred.response.signature);
const userHandle = base64url.encode(cred.response.userHandle);
credential.response = {
clientDataJSON,
authenticatorData,
signature,
userHandle,
};
- שולחים את האובייקט לשרת:
public/client.js
return await _fetch(`/auth/signinResponse`, credential);
כשמריצים את התוכנית, השרת מחזיר HTTP code 200
, שמציין שפרטי הכניסה אומתו.
עכשיו יש לכם את הפונקציה המלאה authentication()
.
בדיקת קוד הפתרון של הקטע הזה
public/client.js
// TODO: Add an ability to authenticate with a passkey: Create the authenticate() function.
export async function authenticate() {
// TODO: Add an ability to authenticate with a passkey: Obtain the
challenge and other options from the server endpoint.
const options = await _fetch('/auth/signinRequest');
// TODO: Add an ability to authenticate with a passkey: Locally verify
the user and get a credential.
// Base64URL decode the challenge.
options.challenge = base64url.decode(options.challenge);
// The empty allowCredentials array invokes an account selector
by discoverable credentials.
options.allowCredentials = [];
// Invoke the WebAuthn get() function.
const cred = await navigator.credentials.get({
publicKey: options,
// Request a conditional UI.
mediation: 'conditional'
});
// TODO: Add an ability to authenticate with a passkey: Verify the credential.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;
// Base64URL encode some values.
const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
const authenticatorData =
base64url.encode(cred.response.authenticatorData);
const signature = base64url.encode(cred.response.signature);
const userHandle = base64url.encode(cred.response.userHandle);
credential.response = {
clientDataJSON,
authenticatorData,
signature,
userHandle,
};
return await _fetch(`/auth/signinResponse`, credential);
};
6. הוספת מפתחות גישה למילוי אוטומטי בדפדפן
כשמשתמש חוזר, אתם רוצים שהוא יוכל להיכנס לחשבון בקלות ובצורה מאובטחת ככל האפשר. אם מוסיפים לחצן כניסה באמצעות מפתח גישה לדף הכניסה, המשתמש יכול ללחוץ על הלחצן, לבחור מפתח גישה בכלי לבחירת חשבון בדפדפן ולהשתמש בנעילת המסך כדי לאמת את הזהות.
עם זאת, המעבר מסיסמה למפתח גישה לא מתבצע אצל כל המשתמשים בבת אחת. כלומר, אי אפשר להיפטר מהסיסמאות עד שכל המשתמשים יעברו למפתחות גישה, ולכן צריך להשאיר את טופס הכניסה שמבוסס על סיסמה עד אז. אבל אם תשארו טופס סיסמה ולחצן של מפתח גישה, המשתמשים יצטרכו לבחור בין האפשרויות האלה כדי להיכנס. מומלץ שתהליך הכניסה יהיה פשוט וקל.
כאן נכנס לתמונה ממשק משתמש מותנה. ממשק משתמש מותנה הוא תכונה של WebAuthn שמאפשרת לכם ליצור שדה קלט בטופס כדי להציע מפתח גישה כחלק מפריטים של מילוי אוטומטי, בנוסף לסיסמאות. אם משתמש מקיש על מפתח גישה בהצעות למילוי אוטומטי, הוא מתבקש להשתמש בנעילת המסך של המכשיר כדי לאמת את הזהות שלו באופן מקומי. חוויית המשתמש חלקה כי פעולת המשתמש כמעט זהה לפעולה של כניסה שמבוססת על סיסמה.
הפעלת ממשק משתמש מותנה
כדי להפעיל ממשק משתמש מותנה, צריך רק להוסיף טוקן webauthn
במאפיין autocomplete
של שדה קלט. אחרי שמגדירים את קבוצת האסימונים, אפשר להפעיל את השיטה navigator.credentials.get()
עם המחרוזת mediation: 'conditional'
כדי להפעיל את ממשק המשתמש של נעילת המסך בתנאי.
- כדי להפעיל ממשק משתמש מותנה, מחליפים את שדות הקלט הקיימים של שם המשתמש בקוד ה-HTML הבא אחרי ההערה הרלוונטית בקובץ
view/index.html
:
view/index.html
<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. -->
<input
type="text"
id="username"
class="mdc-text-field__input"
aria-labelledby="username-label"
name="username"
autocomplete="username webauthn"
autofocus />
זיהוי תכונות, הפעלת WebAuthn והפעלת ממשק משתמש מותנה
- בקובץ
view/index.html
, אחרי התגובה הרלוונטית, מחליפים את ההצהרה הקיימתimport
בקוד הבא:
view/index.html
// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
import {
$,
_fetch,
loading,
authenticate
} from "/client.js";
הקוד הזה מייבא את הפונקציה authenticate()
שהטמעתם קודם.
- מוודאים שאובייקט
window.PulicKeyCredential
זמין ושהמתודהPublicKeyCredential.isConditionalMediationAvailable()
מחזירה ערךtrue
, ואז קוראים לפונקציהauthenticate()
:
view/index.html
// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
if (
window.PublicKeyCredential &&
PublicKeyCredential.isConditionalMediationAvailable
) {
try {
// Is conditional UI available in this browser?
const cma =
await PublicKeyCredential.isConditionalMediationAvailable();
if (cma) {
// If conditional UI is available, invoke the authenticate() function.
const user = await authenticate();
if (user) {
// Proceed only when authentication succeeds.
$("#username").value = user.username;
loading.start();
location.href = "/home";
} else {
throw new Error("User not found.");
}
}
} catch (e) {
loading.stop();
// A NotAllowedError indicates that the user canceled the operation.
if (e.name !== "NotAllowedError") {
console.error(e);
alert(e.message);
}
}
}
בדיקת קוד הפתרון של הקטע הזה
view/index.html
<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. -->
<input
type="text"
id="username"
class="mdc-text-field__input"
aria-labelledby="username-label"
name="username"
autocomplete="username webauthn"
autofocus
/>
view/index.html
// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
import {
$,
_fetch,
loading,
authenticate
} from '/client.js';
view/index.html
// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
// Is WebAuthn avaiable in this browser?
if (window.PublicKeyCredential &&
PublicKeyCredential.isConditionalMediationAvailable) {
try {
// Is a conditional UI available in this browser?
const cma= await PublicKeyCredential.isConditionalMediationAvailable();
if (cma) {
// If a conditional UI is available, invoke the authenticate() function.
const user = await authenticate();
if (user) {
// Proceed only when authentication succeeds.
$('#username').value = user.username;
loading.start();
location.href = '/home';
} else {
throw new Error('User not found.');
}
}
} catch (e) {
loading.stop();
// A NotAllowedError indicates that the user canceled the operation.
if (e.name !== 'NotAllowedError') {
console.error(e);
alert(e.message);
}
}
}
רוצה לנסות?
הטמעתם באתר שלכם את התהליכים ליצירה, לרישום, להצגה ולאימות של מפתחות גישה.
כדי לנסות את התכונה, מבצעים את השלבים הבאים:
- עוברים לכרטיסיית התצוגה המקדימה.
- אם צריך, יוצאים מהחשבון.
- לוחצים על תיבת הטקסט של שם המשתמש. תופיע תיבת דו-שיח.
- בוחרים את החשבון שאיתו רוצים להיכנס.
- מאמתים את הזהות באמצעות השיטה לביטול הנעילה של המסך. תועברו לדף
/home
ותיכנסו לחשבון.
7. מעולה!
סיימתם את ה-Codelab הזה! אם יש לכם שאלות, אתם יכולים לשאול אותן ברשימת התפוצה FIDO-DEV או ב-StackOverflow עם התג passkey
.