Ringkasan
Berikut adalah ringkasan tingkat tinggi tentang langkah-langkah utama yang diperlukan dalam pendaftaran kunci sandi:
- Tentukan opsi untuk membuat kunci sandi. Kirim ekstensi tersebut ke klien agar Anda dapat meneruskannya ke panggilan pembuatan kunci sandi: panggilan API WebAuthn
navigator.credentials.create
di web, dancredentialManager.createCredential
di Android. Setelah pengguna mengonfirmasi pembuatan kunci sandi, panggilan pembuatan kunci sandi diselesaikan dan menampilkan kredensialPublicKeyCredential
. - Memverifikasi kredensial dan menyimpannya di server.
Bagian berikut akan membahas setiap langkah secara spesifik.
Membuat opsi pembuatan kredensial
Langkah pertama yang perlu Anda lakukan di server adalah membuat objek PublicKeyCredentialCreationOptions
.
Untuk melakukannya, andalkan library sisi server FIDO. Biasanya akan menawarkan fungsi utilitas yang dapat membuat opsi tersebut untuk Anda. SimpleWebAuthn menawarkan, misalnya, generateRegistrationOptions
.
PublicKeyCredentialCreationOptions
harus menyertakan semua yang diperlukan untuk pembuatan kunci sandi: informasi tentang pengguna, RP, dan konfigurasi untuk properti kredensial yang Anda buat. Setelah menentukan semua ini, teruskan sesuai kebutuhan ke fungsi di library sisi server FIDO yang bertanggung jawab untuk membuat objek PublicKeyCredentialCreationOptions
.
Beberapa kolom PublicKeyCredentialCreationOptions
dapat berupa konstanta. Atribut lainnya harus ditentukan secara dinamis di server:
rpId
: Untuk mengisi ID RP di server, gunakan fungsi atau variabel sisi server yang memberi Anda nama host aplikasi web, sepertiexample.com
.user.name
danuser.displayName
: Untuk mengisi kolom ini, gunakan informasi sesi pengguna yang Anda login (atau informasi akun pengguna baru, jika pengguna membuat kunci sandi saat mendaftar).user.name
biasanya merupakan alamat email, dan bersifat unik untuk RP.user.displayName
adalah nama yang mudah digunakan. Perhatikan bahwa tidak semua platform akan menggunakandisplayName
.user.id
: String unik acak yang dihasilkan saat pembuatan akun. Nama ini harus permanen, tidak seperti nama pengguna yang dapat diedit. ID pengguna mengidentifikasi akun, tetapi tidak boleh berisi informasi identitas pribadi (PII). Anda mungkin sudah memiliki ID pengguna di sistem, tetapi jika diperlukan, buat ID pengguna khusus untuk kunci sandi agar tetap bebas dari PII apa pun.excludeCredentials
: Daftar ID kredensial yang ada untuk mencegah duplikasi kunci sandi dari penyedia kunci sandi. Untuk mengisi kolom ini, cari kredensial yang ada di database Anda untuk pengguna ini. Tinjau detailnya di Mencegah pembuatan kunci sandi baru jika sudah ada kunci sandi baru.challenge
: Untuk pendaftaran kredensial, verifikasi ini tidak relevan kecuali jika Anda menggunakan pengesahan, teknik yang lebih canggih untuk memverifikasi identitas penyedia kunci sandi dan data yang dimunculkannya. Namun, meskipun Anda tidak menggunakan pengesahan, tantangan masih wajib diisi. Dalam hal ini, Anda dapat menetapkan tantangan ini ke satu0
agar lebih praktis. Petunjuk untuk membuat verifikasi login yang aman untuk autentikasi tersedia di Autentikasi kunci sandi sisi server.
Encoding dan decoding
PublicKeyCredentialCreationOptions
menyertakan kolom yang berupa ArrayBuffer
, sehingga tidak didukung oleh JSON.stringify()
. Artinya, pada saat ini, untuk menayangkan PublicKeyCredentialCreationOptions
melalui HTTPS, beberapa kolom harus dienkode secara manual di server menggunakan base64URL
, lalu didekode pada klien.
- Di server, encoding dan decoding biasanya ditangani oleh library sisi server FIDO.
- Pada klien, encoding dan decoding harus dilakukan secara manual pada saat itu. Ini akan menjadi lebih mudah di masa mendatang: metode untuk mengonversi opsi sebagai JSON menjadi
PublicKeyCredentialCreationOptions
akan tersedia. Lihat status penerapan di Chrome.
Kode contoh: membuat opsi pembuatan kredensial
Kami menggunakan library SimpleWebAuthn dalam contoh kami. Di sini, kita menyerahkan pembuatan opsi kredensial kunci publik ke fungsi generateRegistrationOptions
-nya.
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 });
}
});
Menyimpan kunci publik
Saat navigator.credentials.create
berhasil di-resolve di klien, artinya kunci sandi telah berhasil dibuat. Objek PublicKeyCredential
ditampilkan.
Objek PublicKeyCredential
berisi objek AuthenticatorAttestationResponse
, yang mewakili respons penyedia kunci sandi terhadap petunjuk klien untuk membuat kunci sandi. Isinya adalah informasi tentang kredensial baru yang Anda butuhkan sebagai RP untuk mengautentikasi pengguna nanti. Pelajari AuthenticatorAttestationResponse
lebih lanjut di Lampiran: AuthenticatorAttestationResponse
.
Kirim objek PublicKeyCredential
ke server. Setelah Anda menerimanya, verifikasi.
Serahkan langkah verifikasi ini ke koleksi sisi server FIDO. Ini biasanya akan menawarkan fungsi utilitas untuk tujuan ini. SimpleWebAuthn menawarkan, misalnya, verifyRegistrationResponse
. Pelajari apa yang terjadi di balik layar di Lampiran: verifikasi respons pendaftaran.
Setelah verifikasi berhasil, simpan informasi kredensial di database Anda agar pengguna dapat melakukan autentikasi dengan kunci sandi yang terkait dengan kredensial tersebut.
Gunakan tabel khusus untuk kredensial kunci publik yang terkait dengan kunci sandi. Pengguna hanya dapat memiliki satu sandi, tetapi dapat memiliki beberapa kunci sandi — misalnya, kunci sandi yang disinkronkan melalui Rantai Kunci iCloud Apple dan satu sandi melalui Pengelola Sandi Google.
Berikut adalah contoh skema yang dapat Anda gunakan untuk menyimpan informasi kredensial:
- Tabel Pengguna:
user_id
: ID pengguna utama. ID permanen, unik, dan acak untuk pengguna. Gunakan ini sebagai kunci utama untuk tabel Users Anda.username
. Nama pengguna yang ditetapkan pengguna, dapat diedit.passkey_user_id
: ID pengguna bebas PII spesifik kunci sandi, yang diwakili olehuser.id
di opsi pendaftaran Anda. Saat pengguna nanti mencoba mengautentikasi, pengautentikasi akan menyediakanpasskey_user_id
ini dalam respons autentikasinya diuserHandle
. Sebaiknya Anda tidak menetapkanpasskey_user_id
sebagai kunci utama. Kunci utama cenderung menjadi PII secara de facto dalam sistem, karena digunakan secara luas.
- Tabel Public key credentials:
id
: ID kredensial. Gunakan kunci ini sebagai kunci utama untuk tabel Public key credentials.public_key
: Kunci publik kredensial.passkey_user_id
: Gunakan ini sebagai kunci asing untuk membuat link dengan tabel Users.backed_up
: Kunci sandi dicadangkan jika disinkronkan oleh penyedia kunci sandi. Menyimpan status cadangan berguna jika Anda ingin mempertimbangkan untuk menghapus sandi di masa mendatang untuk pengguna yang memiliki kunci sandibacked_up
. Anda dapat memeriksa apakah kunci sandi dicadangkan dengan memeriksa tanda diauthenticatorData
, atau dengan menggunakan fitur library sisi server FIDO yang biasanya tersedia untuk memberi Anda akses mudah ke informasi ini. Menyimpan kelayakan pencadangan dapat membantu menjawab pertanyaan calon pengguna.name
: Secara opsional, nama tampilan untuk kredensial agar pengguna dapat memberikan nama kustom kredensial.transports
: Array transport. Menyimpan transport berguna untuk pengalaman otentikasi pengguna. Jika transport tersedia, browser dapat berperilaku sesuai dan menampilkan UI yang cocok dengan transport yang digunakan penyedia kunci sandi untuk berkomunikasi dengan klien—khususnya untuk kasus penggunaan autentikasi ulang jikaallowCredentials
tidak kosong.
Informasi lainnya dapat berguna untuk disimpan untuk tujuan pengalaman pengguna, termasuk item seperti penyedia kunci sandi, waktu pembuatan kredensial, dan waktu terakhir kali digunakan. Baca selengkapnya di Desain antarmuka pengguna Kunci sandi.
Kode contoh: simpan kredensial
Kami menggunakan library SimpleWebAuthn dalam contoh kami.
Di sini, kita menyerahkan verifikasi respons pendaftaran ke fungsi 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 });
}
});
Lampiran: AuthenticatorAttestationResponse
AuthenticatorAttestationResponse
berisi dua objek penting:
response.clientDataJSON
adalah versi JSON dari data klien, yang di web merupakan data seperti yang dilihat oleh browser. File ini berisi asal RP, tantangan, danandroidPackageName
jika klien adalah aplikasi Android. Sebagai RP, membacaclientDataJSON
memberi Anda akses ke informasi yang dilihat browser pada saat permintaancreate
.response.attestationObject
berisi dua informasi:attestationStatement
yang tidak relevan kecuali jika Anda menggunakan pengesahan.authenticatorData
adalah data seperti yang dilihat oleh penyedia kunci sandi. Sebagai RP, membacaauthenticatorData
memberi Anda akses ke data yang dilihat oleh penyedia kunci sandi dan ditampilkan pada saat permintaancreate
.
authenticatorData
berisi informasi penting tentang kredensial kunci publik yang terkait dengan kunci sandi yang baru dibuat:
- Kredensial kunci publik itu sendiri, dan ID kredensial unik untuk kunci tersebut.
- ID RP yang terkait dengan kredensial.
- Tanda yang mendeskripsikan status pengguna saat kunci sandi dibuat: apakah ada pengguna yang benar-benar ada, dan apakah pengguna berhasil diverifikasi (lihat
userVerification
). - AAGUID, yang mengidentifikasi penyedia kunci sandi. Menampilkan penyedia kunci sandi dapat bermanfaat bagi pengguna Anda, terutama jika mereka memiliki kunci sandi yang terdaftar untuk layanan Anda di beberapa penyedia kunci sandi.
Meskipun authenticatorData
disarangkan di dalam attestationObject
, informasi yang ada di dalamnya diperlukan untuk implementasi kunci sandi, terlepas dari Anda menggunakan pengesahan atau tidak. authenticatorData
dienkode dan berisi kolom yang dienkode dalam format biner. Library sisi server Anda biasanya akan menangani penguraian dan decoding. Jika Anda tidak menggunakan library sisi server, pertimbangkan untuk memanfaatkan getAuthenticatorData()
sisi klien agar Anda dapat menghemat beberapa waktu penguraian dan dekode sisi server.
Lampiran: verifikasi respons pendaftaran
Pada prinsipnya, verifikasi respons pendaftaran terdiri dari pemeriksaan berikut:
- Pastikan ID RP cocok dengan situs Anda.
- Pastikan origin permintaan adalah origin yang diharapkan untuk situs Anda (URL situs utama, aplikasi Android).
- Jika Anda mewajibkan verifikasi pengguna, pastikan tanda verifikasi pengguna
authenticatorData.uv
adalahtrue
. Periksa apakah tanda kehadiran penggunaauthenticatorData.up
adalahtrue
, karena kehadiran pengguna selalu wajib ada untuk kunci sandi. - Periksa apakah klien mampu memberikan tantangan yang Anda berikan. Jika Anda tidak menggunakan pengesahan, pemeriksaan ini tidak penting. Namun, menerapkan pemeriksaan ini adalah praktik terbaik: tindakan ini memastikan kode Anda siap jika Anda memutuskan untuk menggunakan pengesahan di masa mendatang.
- Pastikan ID kredensial belum terdaftar untuk setiap pengguna.
- Pastikan algoritma yang digunakan oleh penyedia kunci sandi untuk membuat kredensial adalah algoritma yang Anda cantumkan (di setiap kolom
alg
padapublicKeyCredentialCreationOptions.pubKeyCredParams
, yang biasanya ditentukan dalam library sisi server Anda dan tidak terlihat oleh Anda). Hal ini memastikan bahwa pengguna hanya dapat mendaftar dengan algoritme yang Anda izinkan.
Untuk mempelajari lebih lanjut, lihat kode sumber untuk verifyRegistrationResponse
SimpleWebAuthn atau pelajari daftar lengkap verifikasi di spesifikasi.