ภาพรวม
ต่อไปนี้เป็นภาพรวมระดับสูงของขั้นตอนสำคัญที่เกี่ยวข้องกับการลงทะเบียนพาสคีย์
- กำหนดตัวเลือกเพื่อสร้างพาสคีย์ ส่งไปยังไคลเอ็นต์ เพื่อให้คุณส่งพาสคีย์ไปยังการเรียกใช้การสร้างพาสคีย์ได้ ซึ่งได้แก่ การเรียก WebAuthn API
navigator.credentials.create
ในเว็บ และcredentialManager.createCredential
ใน Android หลังจากผู้ใช้ยืนยันการสร้างพาสคีย์แล้ว การเรียกการสร้างพาสคีย์จะได้รับการแก้ไขและแสดงผลข้อมูลเข้าสู่ระบบPublicKeyCredential
- โปรดยืนยันข้อมูลเข้าสู่ระบบและจัดเก็บไว้ในเซิร์ฟเวอร์
ส่วนต่อไปนี้จะเจาะลึกรายละเอียดของแต่ละขั้นตอน
สร้างตัวเลือกการสร้างข้อมูลเข้าสู่ระบบ
ขั้นตอนแรกที่คุณต้องทำบนเซิร์ฟเวอร์คือการสร้างออบเจ็กต์ PublicKeyCredentialCreationOptions
ในการทำเช่นนี้ ให้ใช้ไลบรารีฝั่งเซิร์ฟเวอร์ของ FIDO โดยปกติจะมีฟังก์ชันยูทิลิตีที่สามารถสร้างตัวเลือกเหล่านี้ให้คุณได้ SimpleWebAuthn ให้บริการ ตัวอย่างเช่น generateRegistrationOptions
PublicKeyCredentialCreationOptions
ควรมีข้อมูลที่จำเป็นทั้งหมดในการสร้างพาสคีย์ ได้แก่ ข้อมูลเกี่ยวกับผู้ใช้ ข้อมูลเกี่ยวกับ RP และการกำหนดค่าสำหรับพร็อพเพอร์ตี้ของข้อมูลเข้าสู่ระบบที่คุณกำลังสร้าง เมื่อกำหนดค่าทั้งหมดแล้ว ให้ส่งต่อตามที่ต้องการไปยังฟังก์ชันในไลบรารีฝั่งเซิร์ฟเวอร์ของ FIDO ที่มีหน้าที่สร้างออบเจ็กต์ PublicKeyCredentialCreationOptions
บางส่วนของ PublicKeyCredentialCreationOptions
' เป็นค่าคงที่ได้ รายการอื่นๆ ควรได้รับการกำหนดแบบไดนามิกบนเซิร์ฟเวอร์:
rpId
: หากต้องการป้อนข้อมูลรหัส RP บนเซิร์ฟเวอร์ ให้ใช้ฟังก์ชันหรือตัวแปรฝั่งเซิร์ฟเวอร์ซึ่งกำหนดชื่อโฮสต์ของเว็บแอปพลิเคชัน เช่นexample.com
user.name
และuser.displayName
: หากต้องการป้อนข้อมูลในช่องเหล่านี้ ให้ใช้ข้อมูลเซสชันของผู้ใช้ที่ลงชื่อเข้าใช้ (หรือข้อมูลบัญชีผู้ใช้ใหม่หากผู้ใช้สร้างพาสคีย์ในการลงชื่อสมัครใช้)user.name
มักเป็นที่อยู่อีเมลและเป็นที่อยู่อีเมลเฉพาะสำหรับ RPuser.displayName
เป็นชื่อที่เข้าใจง่าย โปรดทราบว่าบางแพลตฟอร์มจะใช้displayName
ไม่ได้user.id
: สตริงแบบสุ่มที่ไม่ซ้ำกันซึ่งสร้างขึ้นเมื่อสร้างบัญชี ชื่อนี้ควรเป็นแบบถาวร ซึ่งต่างจากชื่อผู้ใช้ที่อาจแก้ไขได้ รหัสผู้ใช้จะระบุบัญชี แต่ไม่ควรมีข้อมูลส่วนบุคคลที่ระบุตัวบุคคลนั้นได้ (PII) คุณอาจมี User-ID ในระบบอยู่แล้ว แต่หากจำเป็น ให้สร้างรหัสสำหรับพาสคีย์โดยเฉพาะเพื่อป้องกันไม่ให้รหัสดังกล่าวไม่มี PIIexcludeCredentials
: รายการข้อมูลเข้าสู่ระบบที่มีอยู่ รหัสเพื่อป้องกันการทำซ้ำพาสคีย์จากผู้ให้บริการพาสคีย์ หากต้องการเติมข้อมูลในช่องนี้ ให้ค้นหาข้อมูลเข้าสู่ระบบที่มีอยู่สำหรับผู้ใช้รายนี้ในฐานข้อมูล ดูรายละเอียดที่หัวข้อป้องกันการสร้างพาสคีย์ใหม่หากมีอยู่แล้วchallenge
: สำหรับการลงทะเบียนข้อมูลเข้าสู่ระบบ ภารกิจจะไม่มีความเกี่ยวข้องเว้นแต่คุณจะใช้เอกสารรับรอง ซึ่งเป็นเทคนิคขั้นสูงกว่าในการยืนยันตัวตนของผู้ให้บริการพาสคีย์และข้อมูลที่ปล่อยออกมา อย่างไรก็ตาม แม้ว่าคุณจะไม่ได้ใช้เอกสารรับรอง แต่ยังคงเป็นช่องที่ต้องกรอก ในกรณีนี้ คุณสามารถตั้งค่าการทดสอบนี้เป็น0
รายการเดียวเพื่อความง่าย ดูวิธีสร้างคำถามเพื่อความปลอดภัยเพื่อการตรวจสอบสิทธิ์ได้ในการตรวจสอบสิทธิ์พาสคีย์ฝั่งเซิร์ฟเวอร์
การเข้ารหัสและถอดรหัส
PublicKeyCredentialCreationOptions
มีช่องที่เป็น ArrayBuffer
ดังนั้น JSON.stringify()
จึงไม่รองรับช่องดังกล่าว ซึ่งหมายความว่าขณะนี้ช่องบางช่องจะต้องเข้ารหัสด้วยตนเองบนเซิร์ฟเวอร์โดยใช้ base64URL
แล้วถอดรหัสบนไคลเอ็นต์เพื่อส่ง PublicKeyCredentialCreationOptions
ผ่าน HTTPS
- บนเซิร์ฟเวอร์ โดยทั่วไปไลบรารีฝั่งเซิร์ฟเวอร์ FIDO จะจัดการการเข้ารหัสและการถอดรหัส
- ในไคลเอ็นต์ ปัจจุบันคุณต้องเข้ารหัสและถอดรหัสด้วยตนเอง การแปลงนี้จะง่ายขึ้นในอนาคต กล่าวคือ เมธอดในการแปลงตัวเลือกเป็น JSON เป็น
PublicKeyCredentialCreationOptions
จะพร้อมใช้งาน ตรวจสอบสถานะการใช้งานใน Chrome
โค้ดตัวอย่าง: สร้างตัวเลือกการสร้างข้อมูลเข้าสู่ระบบ
เราใช้ไลบรารี SimpleWebAuthn ในตัวอย่างของเรา ในส่วนนี้ เราส่งต่อการสร้างตัวเลือกข้อมูลเข้าสู่ระบบคีย์สาธารณะไปยังฟังก์ชัน generateRegistrationOptions
แล้ว
import {
generateRegistrationOptions,
verifyRegistrationResponse,
generateAuthenticationOptions,
verifyAuthenticationResponse
} from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers';
router.post('/registerRequest', csrfCheck, sessionCheck, async (req, res) => {
const { user } = res.locals;
// Ensure you nest verification function calls in try/catch blocks.
// If something fails, throw an error with a descriptive error message.
// Return that message with an appropriate error code to the client.
try {
// `excludeCredentials` prevents users from re-registering existing
// credentials for a given passkey provider
const excludeCredentials = [];
const credentials = Credentials.findByUserId(user.id);
if (credentials.length > 0) {
for (const cred of credentials) {
excludeCredentials.push({
id: isoBase64URL.toBuffer(cred.id),
type: 'public-key',
transports: cred.transports,
});
}
}
// Generate registration options for WebAuthn create
const options = generateRegistrationOptions({
rpName: process.env.RP_NAME,
rpID: process.env.HOSTNAME,
userID: user.id,
userName: user.username,
userDisplayName: user.displayName || '',
attestationType: 'none',
excludeCredentials,
authenticatorSelection: {
authenticatorAttachment: 'platform',
requireResidentKey: true
},
});
// Keep the challenge in the session
req.session.challenge = options.challenge;
return res.json(options);
} catch (e) {
console.error(e);
return res.status(400).send({ error: e.message });
}
});
จัดเก็บคีย์สาธารณะ
เมื่อ navigator.credentials.create
แก้ไขไคลเอ็นต์สำเร็จ แสดงว่าสร้างพาสคีย์สำเร็จแล้ว ระบบแสดงผลออบเจ็กต์ PublicKeyCredential
ออบเจ็กต์ PublicKeyCredential
มีออบเจ็กต์ AuthenticatorAttestationResponse
ซึ่งแสดงการตอบสนองของผู้ให้บริการพาสคีย์ต่อวิธีการสร้างพาสคีย์ของไคลเอ็นต์ โดยมีข้อมูลเกี่ยวกับข้อมูลเข้าสู่ระบบใหม่ที่คุณจำเป็นต้องใช้ในฐานะ RP เพื่อตรวจสอบสิทธิ์ผู้ใช้ในภายหลัง ดูข้อมูลเพิ่มเติมเกี่ยวกับ AuthenticatorAttestationResponse
ในภาคผนวก: AuthenticatorAttestationResponse
ส่งออบเจ็กต์ PublicKeyCredential
ไปยังเซิร์ฟเวอร์ เมื่อได้รับแล้ว โปรดยืนยัน
ส่งขั้นตอนการยืนยันนี้ไปยังไลบรารีฝั่งเซิร์ฟเวอร์ของ FIDO โดยทั่วไปจะมีฟังก์ชันยูทิลิตีสำหรับวัตถุประสงค์นี้ SimpleWebAuthn ให้บริการ ตัวอย่างเช่น verifyRegistrationResponse
ดูว่าเกิดอะไรขึ้นในภาคผนวก: การยืนยันการตอบกลับการลงทะเบียน
เมื่อการยืนยันเสร็จสมบูรณ์ ให้จัดเก็บข้อมูลเข้าสู่ระบบในฐานข้อมูลของคุณ เพื่อให้ผู้ใช้สามารถตรวจสอบสิทธิ์ในภายหลังด้วยพาสคีย์ที่เชื่อมโยงกับข้อมูลเข้าสู่ระบบนั้น
ใช้ตารางสำหรับข้อมูลเข้าสู่ระบบคีย์สาธารณะที่เชื่อมโยงกับพาสคีย์โดยเฉพาะ ผู้ใช้จะมีรหัสผ่านได้เพียงรหัสผ่านเดียว แต่สามารถมีพาสคีย์ได้หลายรายการ เช่น พาสคีย์ที่ซิงค์ผ่านพวงกุญแจ iCloud ของ Apple และพาสคีย์ผ่านเครื่องมือจัดการรหัสผ่านบน Google
ต่อไปนี้คือตัวอย่างสคีมาที่คุณสามารถใช้เพื่อจัดเก็บข้อมูลเข้าสู่ระบบ
- ตารางผู้ใช้:
user_id
: รหัสผู้ใช้หลัก รหัสถาวรแบบสุ่มที่ไม่ซ้ำกันสำหรับผู้ใช้ ใช้เป็นคีย์หลักสำหรับตารางผู้ใช้username
ชื่อผู้ใช้ที่กำหนดโดยผู้ใช้ ซึ่งแก้ไขได้passkey_user_id
: รหัสผู้ใช้ที่ไม่มี PII สำหรับพาสคีย์เฉพาะพาสคีย์ ซึ่งแสดงด้วยuser.id
ในตัวเลือกการลงทะเบียน เมื่อผู้ใช้พยายามตรวจสอบสิทธิ์ในภายหลัง ตัวตรวจสอบสิทธิ์จะทำให้passkey_user_id
นี้พร้อมใช้งานในการตอบกลับการตรวจสอบสิทธิ์ในuserHandle
เราขอแนะนำว่าอย่าตั้งค่าpasskey_user_id
เป็นคีย์หลัก คีย์หลักมักจะกลายเป็น PII โดยแท้จริงเพราะระบบมีการใช้คีย์นี้อย่างแพร่หลาย
- ตารางข้อมูลเข้าสู่ระบบคีย์สาธารณะ:
id
: รหัสการรับรอง โปรดใช้คีย์นี้เป็นคีย์หลักสำหรับตารางข้อมูลเข้าสู่ระบบคีย์สาธารณะpublic_key
: คีย์สาธารณะของข้อมูลเข้าสู่ระบบpasskey_user_id
: ใช้คีย์นี้เป็นคีย์นอกเพื่อสร้างลิงก์ด้วยตารางผู้ใช้backed_up
: ระบบจะสำรองข้อมูลพาสคีย์หากผู้ให้บริการพาสคีย์ซิงค์ข้อมูลไว้ การจัดเก็บสถานะการสำรองข้อมูลจะมีประโยชน์ในกรณีที่ต้องการจะลืมรหัสผ่านในอนาคตสําหรับผู้ใช้ที่มีพาสคีย์backed_up
รายการ คุณสามารถตรวจสอบว่ามีการสำรองข้อมูลพาสคีย์หรือไม่โดยตรวจสอบธงในauthenticatorData
หรือใช้ฟีเจอร์ไลบรารีฝั่งเซิร์ฟเวอร์ของ FIDO ที่โดยปกติจะพร้อมใช้งานเพื่อช่วยให้คุณเข้าถึงข้อมูลนี้ได้อย่างง่ายดาย การจัดเก็บการมีสิทธิ์สํารองข้อมูลอาจมีประโยชน์ในการตอบคำถามที่มีโอกาสเป็นผู้ใช้name
: (ไม่บังคับ) ชื่อที่แสดงสำหรับข้อมูลเข้าสู่ระบบเพื่อให้ผู้ใช้ตั้งชื่อข้อมูลเข้าสู่ระบบที่กำหนดเองได้transports
: อาร์เรย์ของการขนส่ง การจัดเก็บการส่งมีประโยชน์สำหรับประสบการณ์ของผู้ใช้ในการตรวจสอบสิทธิ์ เมื่อมีการรับส่งข้อมูลพร้อมใช้งาน เบราว์เซอร์จะทำงานตามนั้นและแสดง UI ที่ตรงกับการรับส่งข้อมูลที่ผู้ให้บริการพาสคีย์ใช้ในการสื่อสารกับไคลเอ็นต์ โดยเฉพาะอย่างยิ่งสำหรับ Use Case การตรวจสอบสิทธิ์อีกครั้งโดยที่allowCredentials
ไม่ว่างเปล่า
ข้อมูลอื่นๆ อาจมีประโยชน์ในการเก็บข้อมูลเพื่อวัตถุประสงค์ด้านประสบการณ์ของผู้ใช้ รวมถึงรายการต่างๆ เช่น ผู้ให้บริการพาสคีย์ เวลาที่สร้างข้อมูลเข้าสู่ระบบ และเวลาที่ใช้ล่าสุด อ่านเพิ่มเติมได้ในการออกแบบอินเทอร์เฟซผู้ใช้ของพาสคีย์
โค้ดตัวอย่าง: จัดเก็บข้อมูลเข้าสู่ระบบ
เราใช้ไลบรารี SimpleWebAuthn ในตัวอย่างของเรา
ในส่วนนี้ เราจะส่งต่อการยืนยันการตอบกลับของการลงทะเบียนไปยังฟังก์ชัน verifyRegistrationResponse
ของบริการดังกล่าว
import { isoBase64URL } from '@simplewebauthn/server/helpers';
router.post('/registerResponse', csrfCheck, sessionCheck, async (req, res) => {
const expectedChallenge = req.session.challenge;
const expectedOrigin = getOrigin(req.get('User-Agent'));
const expectedRPID = process.env.HOSTNAME;
const response = req.body;
// This sample code is for registering a passkey for an existing,
// signed-in user
// Ensure you nest verification function calls in try/catch blocks.
// If something fails, throw an error with a descriptive error message.
// Return that message with an appropriate error code to the client.
try {
// Verify the credential
const { verified, registrationInfo } = await verifyRegistrationResponse({
response,
expectedChallenge,
expectedOrigin,
expectedRPID,
requireUserVerification: false,
});
if (!verified) {
throw new Error('Verification failed.');
}
const { credentialPublicKey, credentialID } = registrationInfo;
// Existing, signed-in user
const { user } = res.locals;
// Save the credential
await Credentials.update({
id: base64CredentialID,
publicKey: base64PublicKey,
// Optional: set the platform as a default name for the credential
// (example: "Pixel 7")
name: req.useragent.platform,
transports: response.response.transports,
passkey_user_id: user.passkey_user_id,
backed_up: registrationInfo.credentialBackedUp
});
// Kill the challenge for this session
delete req.session.challenge;
return res.json(user);
} catch (e) {
delete req.session.challenge;
console.error(e);
return res.status(400).send({ error: e.message });
}
});
ภาคผนวก: AuthenticatorAttestationResponse
AuthenticatorAttestationResponse
มีออบเจ็กต์สำคัญ 2 รายการ ได้แก่
response.clientDataJSON
เป็นข้อมูลไคลเอ็นต์เวอร์ชัน JSON ซึ่งบนเว็บเป็นข้อมูลตามที่เบราว์เซอร์เห็น โดยมีต้นทางของ RP, ชาเลนจ์ และandroidPackageName
หากลูกค้าเป็นแอป Android ในฐานะ RP การอ่านclientDataJSON
ทำให้คุณสามารถเข้าถึงข้อมูลที่เบราว์เซอร์เห็นในขณะที่ส่งคำขอcreate
response.attestationObject
ประกอบด้วยข้อมูล 2 ส่วน ได้แก่attestationStatement
ซึ่งไม่เกี่ยวข้อง เว้นแต่คุณจะใช้เอกสารรับรองauthenticatorData
คือข้อมูลตามที่ผู้ให้บริการพาสคีย์มองเห็น ในฐานะ RP การอ่านauthenticatorData
ช่วยให้คุณเข้าถึงข้อมูลที่ผู้ให้บริการพาสคีย์เห็นและส่งคืน ณ เวลาที่ส่งคำขอcreate
authenticatorData
มีข้อมูลสำคัญเกี่ยวกับข้อมูลเข้าสู่ระบบคีย์สาธารณะที่เชื่อมโยงกับพาสคีย์ที่สร้างขึ้นใหม่
- ข้อมูลเข้าสู่ระบบคีย์สาธารณะนั้นและรหัสข้อมูลเข้าสู่ระบบที่ไม่ซ้ำกัน
- รหัส RP ที่เชื่อมโยงกับข้อมูลเข้าสู่ระบบ
- การแจ้งที่อธิบายสถานะผู้ใช้เมื่อมีการสร้างพาสคีย์ ระบุว่าผู้ใช้ใช้งานจริงหรือไม่ และยืนยันผู้ใช้สำเร็จหรือไม่ (ดู
userVerification
) - AAGUID ซึ่งระบุผู้ให้บริการพาสคีย์ การแสดงผู้ให้บริการพาสคีย์อาจเป็นประโยชน์สำหรับผู้ใช้ โดยเฉพาะหากผู้ใช้ได้ลงทะเบียนพาสคีย์สำหรับบริการของคุณไว้ในผู้ให้บริการพาสคีย์หลายราย
แม้ว่า authenticatorData
จะฝังอยู่ใน attestationObject
แต่ข้อมูลที่อยู่ในนั้นก็จำเป็นสำหรับการใช้งานพาสคีย์ ไม่ว่าคุณจะใช้เอกสารรับรองหรือไม่ก็ตาม authenticatorData
มีการเข้ารหัสและมีช่องที่เข้ารหัสในรูปแบบไบนารี ปกติแล้วไลบรารีฝั่งเซิร์ฟเวอร์จะจัดการการแยกวิเคราะห์และถอดรหัส หากคุณไม่ได้ใช้ไลบรารีฝั่งเซิร์ฟเวอร์ ลองใช้ฝั่งไคลเอ็นต์ getAuthenticatorData()
เพื่อประหยัดค่าใช้จ่ายในการแยกวิเคราะห์และถอดรหัสฝั่งเซิร์ฟเวอร์ของงาน
ภาคผนวก: การยืนยันการตอบกลับการลงทะเบียน
เบื้องหลังการทำงาน การยืนยันการตอบกลับการลงทะเบียนประกอบด้วยการตรวจสอบดังต่อไปนี้
- ตรวจสอบว่ารหัส RP ตรงกับเว็บไซต์
- ตรวจสอบว่าต้นทางของคำขอเป็นต้นทางที่คาดไว้สำหรับเว็บไซต์ (URL หลักของเว็บไซต์และแอป Android)
- หากต้องการยืนยันผู้ใช้ โปรดตรวจสอบว่าการแจ้งการยืนยันผู้ใช้
authenticatorData.uv
เป็นtrue
ตรวจสอบว่าการแจ้งสถานะการตรวจหาบุคคลในบ้านauthenticatorData.up
คือtrue
เนื่องจากระบบกำหนดให้ใช้พาสคีย์เสมอ - ตรวจสอบว่าลูกค้าสร้างชาเลนจ์ที่คุณระบุได้ หากคุณไม่ใช้เอกสารรับรอง การตรวจสอบนี้ก็ไม่สำคัญ อย่างไรก็ตาม การใช้การตรวจสอบนี้เป็นแนวทางปฏิบัติที่ดีที่สุด เพราะจะช่วยให้มั่นใจได้ว่าโค้ดของคุณจะพร้อมใช้งาน หากคุณตัดสินใจที่จะใช้เอกสารรับรองในอนาคต
- ตรวจสอบว่ายังไม่ได้ลงทะเบียนรหัสข้อมูลเข้าสู่ระบบสำหรับผู้ใช้
- ตรวจสอบว่าอัลกอริทึมที่ผู้ให้บริการพาสคีย์ใช้ในการสร้างข้อมูลเข้าสู่ระบบเป็นอัลกอริทึมที่คุณระบุไว้ (ในช่อง
alg
แต่ละช่องของpublicKeyCredentialCreationOptions.pubKeyCredParams
ซึ่งปกติจะกำหนดไว้ในไลบรารีฝั่งเซิร์ฟเวอร์และไม่ปรากฏจากคุณ) การทำเช่นนี้จะช่วยให้ผู้ใช้ลงทะเบียนได้เฉพาะกับอัลกอริทึมที่คุณเลือกอนุญาตเท่านั้น
หากต้องการดูข้อมูลเพิ่มเติม โปรดดูซอร์สโค้ดสำหรับ verifyRegistrationResponse
ของ SimpleWebAuthn หรือเจาะลึกรายการการยืนยันทั้งหมดในข้อกำหนด