Ringkasan
Berikut adalah ringkasan tingkat tinggi langkah-langkah utama yang terlibat dalam autentikasi kunci sandi:
- Tentukan verifikasi login dan opsi lain yang diperlukan untuk melakukan autentikasi dengan kunci sandi. Kirim kunci tersebut ke klien agar Anda dapat meneruskannya ke panggilan autentikasi kunci sandi (
navigator.credentials.get
di web). Setelah pengguna mengonfirmasi autentikasi kunci sandi, panggilan autentikasi kunci sandi akan diselesaikan dan menampilkan kredensial (PublicKeyCredential
). Kredensial tersebut berisi pernyataan autentikasi.
- Verifikasi pernyataan autentikasi.
- Jika pernyataan otentikasi valid, lakukan autentikasi pengguna.
Bagian berikut akan membahas setiap langkah secara spesifik.
Membuat tantangan
Dalam praktiknya, tantangan adalah array byte acak yang direpresentasikan sebagai objek ArrayBuffer
.
// Example challenge, base64URL-encoded
weMLPOSx1VfSnMV6uPwDKbjGdKRMaUDGxeDEUTT5VN8
Untuk memastikan tantangan memenuhi tujuannya, Anda harus:
- Pastikan verifikasi login yang sama tidak pernah digunakan lebih dari sekali. Buat tantangan baru pada setiap upaya login. Hapus tantangan setelah setiap upaya login, baik berhasil maupun gagal. Buang juga tantangan setelah durasi tertentu. Jangan terima verifikasi login yang sama dalam satu respons lebih dari sekali.
- Pastikan verifikasi login aman secara kriptografis. Tantangan seharusnya sulit ditebak. Untuk membuat tantangan sisi server yang aman secara kriptografis, sebaiknya andalkan library sisi server FIDO yang Anda percayai. Jika Anda membuat tantangan sendiri, gunakan fungsi kriptografi bawaan yang tersedia di tech stack Anda, atau cari library yang didesain untuk kasus penggunaan kriptografi. Contohnya mencakup iso-crypto di Node.js, atau secrets di Python. Sesuai dengan spesifikasi, tantangan harus memiliki panjang minimal 16 byte agar dianggap aman.
Setelah Anda membuat tantangan, simpan di sesi pengguna untuk memverifikasinya nanti.
Membuat opsi permintaan kredensial
Buat opsi permintaan kredensial sebagai objek publicKeyCredentialRequestOptions
.
Untuk melakukannya, andalkan library sisi server FIDO. Biasanya akan menawarkan fungsi utilitas yang dapat membuat opsi tersebut untuk Anda. SimpleWebAuthn menawarkan, misalnya, generateAuthenticationOptions
.
publicKeyCredentialRequestOptions
harus berisi semua informasi yang diperlukan untuk autentikasi kunci sandi. Teruskan informasi ini ke fungsi di library sisi server FIDO yang bertanggung jawab untuk membuat objek publicKeyCredentialRequestOptions
.
Beberapa kolom publicKeyCredentialRequestOptions
dapat berupa konstanta. Atribut lainnya harus ditentukan secara dinamis di server:
rpId
: ID RP mana yang Anda harapkan untuk dikaitkan dengan kredensial, misalnyaexample.com
. Autentikasi hanya akan berhasil jika ID RP yang Anda berikan di sini cocok dengan ID RP yang terkait dengan kredensial. Untuk mengisi ID RP, gunakan nilai yang sama dengan ID RP yang Anda tetapkan dipublicKeyCredentialCreationOptions
saat pendaftaran kredensial.challenge
: Data yang akan ditandatangani oleh penyedia kunci sandi untuk membuktikan bahwa pengguna memegang kunci sandi pada saat permintaan autentikasi. Tinjau detail di Membuat tantangan.allowCredentials
: Array kredensial yang dapat diterima untuk autentikasi ini. Teruskan array kosong agar pengguna dapat memilih kunci sandi yang tersedia dari daftar yang ditampilkan oleh browser. Tinjau artikel Mengambil tantangan dari server RP dan Mempelajari kredensial yang dapat ditemukan untuk mengetahui detailnya.userVerification
: Menunjukkan apakah verifikasi pengguna yang menggunakan kunci layar perangkat "wajib", "lebih disarankan", atau "tidak direkomendasikan". Tinjau Mengambil tantangan dari server RP.timeout
: Berapa lama (dalam milidetik) waktu yang dapat dibutuhkan pengguna untuk menyelesaikan autentikasi. Nilai ini harus cukup murah, dan lebih singkat daripada masa pakaichallenge
. Nilai default yang direkomendasikan adalah 5 menit, tetapi Anda dapat meningkatkannya — hingga 10 menit, yang masih berada dalam rentang yang direkomendasikan. Waktu tunggu yang lama dapat diterima jika Anda mengharapkan pengguna menggunakan alur kerja campuran, yang biasanya memerlukan waktu sedikit lebih lama. Jika waktu operasi habis,NotAllowedError
akan ditampilkan.
Setelah Anda membuat publicKeyCredentialRequestOptions
, kirimkan ke klien.
Kode contoh: membuat opsi permintaan kredensial
Kami menggunakan library SimpleWebAuthn dalam contoh kami. Di sini, kita menyerahkan pembuatan opsi permintaan kredensial ke fungsi generateAuthenticationOptions
-nya.
import {
generateRegistrationOptions,
verifyRegistrationResponse,
generateAuthenticationOptions,
verifyAuthenticationResponse
} from '@simplewebauthn/server';
router.post('/signinRequest', csrfCheck, async (req, res) => {
// Ensure you nest 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 {
// Use the generateAuthenticationOptions function from SimpleWebAuthn
const options = await generateAuthenticationOptions({
rpID: process.env.HOSTNAME,
allowCredentials: [],
});
// Save the challenge in the user session
req.session.challenge = options.challenge;
return res.json(options);
} catch (e) {
console.error(e);
return res.status(400).json({ error: e.message });
}
});
Memverifikasi dan membuat pengguna login
Saat navigator.credentials.get
berhasil di-resolve di klien, objek PublicKeyCredential
akan ditampilkan.
response
adalah AuthenticatorAssertionResponse
. Kunci sandi mewakili respons penyedia kunci sandi terhadap petunjuk klien untuk membuat hal yang diperlukan untuk mencoba melakukan autentikasi dengan kunci sandi di RP. File tersebut berisi:
response.authenticatorData
danresponse.clientDataJSON
, seperti pada langkah pendaftaran kunci sandi.response.signature
yang berisi tanda tangan atas nilai ini.
Kirim objek PublicKeyCredential
ke server.
Di server, lakukan hal berikut:
- Kumpulkan informasi yang Anda perlukan untuk memverifikasi pernyataan dan mengautentikasi pengguna:
- Dapatkan verifikasi login yang Anda simpan di sesi saat membuat opsi autentikasi.
- Dapatkan origin dan ID RP yang diharapkan.
- Menemukan siapa pengguna di database Anda. Dalam kasus kredensial yang dapat ditemukan, Anda tidak tahu siapa pengguna yang membuat permintaan autentikasi. Untuk mengetahuinya, Anda memiliki dua opsi:
- Opsi 1: Gunakan
response.userHandle
di objekPublicKeyCredential
. Di tabel Pengguna, caripasskey_user_id
yang cocok denganuserHandle
. - Opsi 2: Gunakan kredensial
id
yang ada di objekPublicKeyCredential
. Di tabel Public key credentials, cariid
kredensial yang cocok denganid
kredensial yang ada di objekPublicKeyCredential
. Kemudian cari pengguna yang sesuai menggunakan kunci asingpasskey_user_id
untuk tabel Pengguna Anda.
- Opsi 1: Gunakan
- Temukan informasi kredensial kunci publik yang cocok dengan pernyataan autentikasi yang Anda terima di database Anda. Untuk melakukannya, di tabel Public key credentials, cari
id
kredensial yang cocok dengan kredensialid
yang ada di objekPublicKeyCredential
.
Verifikasi pernyataan autentikasi. Serahkan langkah verifikasi ini ke library sisi server FIDO, yang biasanya akan menawarkan fungsi utilitas untuk tujuan ini. SimpleWebAuthn menawarkan, misalnya,
verifyAuthenticationResponse
. Pelajari seluk-beluknya di Lampiran: verifikasi respons autentikasi.Menghapus tantangan apakah verifikasi berhasil atau tidak, untuk mencegah serangan replay.
Membuat pengguna login. Jika verifikasi berhasil, perbarui informasi sesi untuk menandai pengguna sebagai login. Sebaiknya tampilkan objek
user
ke klien juga agar frontend dapat menggunakan informasi yang terkait dengan pengguna yang baru login.
Kode contoh: memverifikasi dan membuat pengguna login
Kami menggunakan library SimpleWebAuthn dalam contoh kami. Di sini, kita menyerahkan verifikasi respons autentikasi ke fungsi verifyAuthenticationResponse
-nya.
import {
generateRegistrationOptions,
verifyRegistrationResponse,
generateAuthenticationOptions,
verifyAuthenticationResponse
} from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers';
router.post('/signinResponse', csrfCheck, async (req, res) => {
const response = req.body;
const expectedChallenge = req.session.challenge;
const expectedOrigin = getOrigin(req.get('User-Agent'));
const expectedRPID = process.env.HOSTNAME;
// 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 {
// Find the credential stored to the database by the credential ID
const cred = Credentials.findById(response.id);
if (!cred) {
throw new Error('Credential not found.');
}
// Find the user - Here alternatively we could look up the user directly
// in the Users table via userHandle
const user = Users.findByPasskeyUserId(cred.passkey_user_id);
if (!user) {
throw new Error('User not found.');
}
// Base64URL decode some values
const authenticator = {
credentialPublicKey: isoBase64URL.toBuffer(cred.publicKey),
credentialID: isoBase64URL.toBuffer(cred.id),
transports: cred.transports,
};
// Verify the credential
const { verified, authenticationInfo } = await verifyAuthenticationResponse({
response,
expectedChallenge,
expectedOrigin,
expectedRPID,
authenticator,
requireUserVerification: false,
});
if (!verified) {
throw new Error('User verification failed.');
}
// Kill the challenge for this session.
delete req.session.challenge;
req.session.username = user.username;
req.session['signed-in'] = 'yes';
return res.json(user);
} catch (e) {
delete req.session.challenge;
console.error(e);
return res.status(400).json({ error: e.message });
}
});
Lampiran: verifikasi respons autentikasi
Verifikasi respons autentikasi terdiri dari pemeriksaan berikut:
- Pastikan ID RP cocok dengan situs Anda.
- Pastikan origin permintaan cocok dengan asal login situs Anda. Untuk aplikasi Android, tinjau bagian Memverifikasi origin.
- Periksa apakah perangkat dapat memberikan tantangan yang Anda berikan.
- Verifikasi bahwa selama autentikasi, pengguna telah mengikuti persyaratan yang Anda mandatkan sebagai RP. Jika Anda mewajibkan verifikasi pengguna, pastikan tanda
uv
(diverifikasi pengguna) diauthenticatorData
adalahtrue
. Pastikan tandaup
(ada pengguna) diauthenticatorData
adalahtrue
, karena kehadiran pengguna selalu diperlukan untuk kunci sandi. - Memverifikasi tanda tangan. Untuk memverifikasi tanda tangan, Anda memerlukan:
- Tanda tangan, yang merupakan tantangan yang ditandatangani:
response.signature
- Kunci publik, untuk melakukan verifikasi tanda tangan.
- Data asli yang ditandatangani. Ini adalah data yang tanda tangannya akan diverifikasi.
- Algoritma kriptografi yang digunakan untuk membuat tanda tangan.
- Tanda tangan, yang merupakan tantangan yang ditandatangani:
Untuk mempelajari langkah-langkah ini lebih lanjut, periksa kode sumber untuk verifyAuthenticationResponse
SimpleWebAuthn atau pelajari daftar lengkap verifikasi di spesifikasi.