ภาพรวม
ภาพรวมระดับสูงของขั้นตอนสำคัญที่เกี่ยวข้องกับการลงทะเบียนพาสคีย์มีดังนี้
- กำหนดตัวเลือกเพื่อสร้างพาสคีย์ ส่งไปยังไคลเอ็นต์เพื่อให้ส่งผ่านไปยังการเรียกใช้การสร้างพาสคีย์ได้ ซึ่งได้แก่ การเรียก 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
: สตริงแบบสุ่มที่ไม่ซ้ำกันที่สร้างขึ้นเมื่อสร้างบัญชี ซึ่งควรเป็นชื่อผู้ใช้ถาวร ซึ่งต่างจากชื่อผู้ใช้ที่อาจแก้ไขได้ User ID จะระบุบัญชี แต่ไม่ควรมีข้อมูลส่วนบุคคลที่ระบุตัวบุคคลนั้นได้ (PII) คุณอาจมี User-ID อยู่ในระบบอยู่แล้ว แต่หากจำเป็น ให้สร้างรหัสเฉพาะสำหรับพาสคีย์เพื่อให้ไม่มี PIIexcludeCredentials
: รายการรหัสของข้อมูลเข้าสู่ระบบที่มีอยู่เพื่อป้องกันไม่ให้มีพาสคีย์ซ้ำจากผู้ให้บริการพาสคีย์ หากต้องการป้อนข้อมูลในช่องนี้ ให้ค้นหาในฐานข้อมูลที่มีอยู่สำหรับผู้ใช้รายนี้ ตรวจสอบรายละเอียดที่หัวข้อป้องกันการสร้างพาสคีย์ใหม่หากมีอยู่แล้วchallenge
: สำหรับการลงทะเบียนข้อมูลเข้าสู่ระบบ ภารกิจนี้จะไม่มีผล เว้นแต่คุณจะใช้เอกสารรับรอง ซึ่งเป็นเทคนิคขั้นสูงกว่าในการยืนยันตัวตนของผู้ให้บริการพาสคีย์และข้อมูลที่ส่ง อย่างไรก็ตาม แม้ว่าคุณจะไม่ได้ใช้เอกสารรับรอง การยืนยันตัวตนก็ยังคงเป็นช่องที่ต้องกรอก ในกรณีนี้ คุณสามารถตั้งค่าการทดสอบนี้เป็น0
รายการเดียวเพื่อให้เรียบง่าย วิธีการสร้างคำถามที่ปลอดภัยสำหรับการตรวจสอบสิทธิ์อยู่ในการตรวจสอบสิทธิ์ด้วยพาสคีย์ฝั่งเซิร์ฟเวอร์
การเข้ารหัสและถอดรหัส
PublicKeyCredentialCreationOptions
มีช่องที่เป็น ArrayBuffer
ดังนั้น JSON.stringify()
จึงไม่รองรับช่องเหล่านั้น ซึ่งหมายความว่าในขณะนี้ ในการส่ง PublicKeyCredentialCreationOptions
ผ่าน HTTPS จะต้องเข้ารหัสบางช่องด้วยตนเองบนเซิร์ฟเวอร์โดยใช้ base64URL
จากนั้นจึงถอดรหัสบนไคลเอ็นต์
- บนเซิร์ฟเวอร์ โดยทั่วไปแล้ว ไลบรารีฝั่งเซิร์ฟเวอร์ 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
ในตัวเลือกการลงทะเบียน เมื่อผู้ใช้พยายามตรวจสอบสิทธิ์ในภายหลัง Authenticator จะทำให้passkey_user_id
นี้พร้อมใช้งานในการตอบกลับการตรวจสอบสิทธิ์ในuserHandle
ขอแนะนำว่าอย่าตั้งค่าpasskey_user_id
เป็นคีย์หลัก คีย์หลักมักจะกลายเป็น PII ในระบบเนื่องจากเป็นการใช้งานอย่างแพร่หลาย
- ตารางข้อมูลเข้าสู่ระบบคีย์สาธารณะ
id
: รหัสข้อมูลเข้าสู่ระบบ ใช้คีย์นี้เป็นคีย์หลักสำหรับตารางข้อมูลเข้าสู่ระบบคีย์สาธารณะpublic_key
: คีย์สาธารณะของข้อมูลเข้าสู่ระบบpasskey_user_id
: ใช้คีย์นี้เป็นคีย์นอกเพื่อสร้างลิงก์ที่มีตารางผู้ใช้backed_up
: ระบบจะสำรองข้อมูลพาสคีย์ หากผู้ให้บริการพาสคีย์ซิงค์ข้อมูล การจัดเก็บสถานะการสำรองข้อมูลจะเป็นประโยชน์หากคุณต้องการพิจารณาทิ้งรหัสผ่านในอนาคตสำหรับผู้ใช้ที่มีพาสคีย์backed_up
รายการ คุณตรวจสอบได้ว่ามีการสำรองข้อมูลพาสคีย์หรือไม่ด้วยการตรวจสอบ Flag ในauthenticatorData
หรือใช้ฟีเจอร์คลังฝั่งเซิร์ฟเวอร์ของ FIDO ที่โดยทั่วไปแล้วให้บริการเพื่อให้คุณเข้าถึงข้อมูลนี้ได้อย่างง่ายดาย การจัดเก็บสิทธิ์การสำรองข้อมูลจะช่วยตอบคําถามของผู้ที่อาจเป็นผู้ใช้ได้name
: ชื่อที่แสดงของข้อมูลเข้าสู่ระบบ (ไม่บังคับ) เพื่ออนุญาตให้ผู้ใช้ตั้งชื่อที่กำหนดเองให้กับข้อมูลเข้าสู่ระบบtransports
: อาร์เรย์ของการขนส่ง การจัดเก็บการรับส่งข้อมูลมีประโยชน์ต่อประสบการณ์ของผู้ใช้การตรวจสอบสิทธิ์ เมื่อมีบริการขนส่ง เบราว์เซอร์จะทำงานตามนั้นและแสดง UI ที่ตรงกับการนำส่งที่ผู้ให้บริการพาสคีย์ใช้ในการสื่อสารกับลูกค้า โดยเฉพาะกรณีการใช้งานของการตรวจสอบสิทธิ์ซ้ำที่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 หรือเจาะลึกรายการการยืนยันทั้งหมดในข้อกำหนด