Ringkasan
Berikut adalah ringkasan umum langkah-langkah penting yang diperlukan dalam autentikasi kunci sandi:
- Tentukan verifikasi login dan opsi lain yang diperlukan untuk mengautentikasi dengan kunci sandi. Kirim kunci sandi ke klien agar Anda dapat meneruskannya ke panggilan autentikasi kunci sandi Anda (
navigator.credentials.get
di web). Setelah pengguna mengonfirmasi autentikasi kunci sandi, panggilan autentikasi kunci sandi akan diselesaikan dan menampilkan kredensial (PublicKeyCredential
). Kredensial berisi pernyataan autentikasi.
- Verifikasi pernyataan autentikasi.
- Jika pernyataan autentikasi valid, autentikasi pengguna.
Bagian berikut membahas detail setiap langkah.
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 tantangan yang sama tidak pernah digunakan lebih dari sekali. Buat tantangan baru setiap kali login. Hapus tantangan setelah setiap upaya masuk, baik berhasil maupun gagal. Buang juga tantangan setelah durasi tertentu. Jangan terima verifikasi login yang sama dalam satu respons lebih dari sekali.
- Pastikan tantangan ini aman secara kriptografis. Tantangan hampir tidak mungkin ditebak. Untuk membuat sisi server tantangan yang aman secara kriptografi, 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 secret di Python. Sesuai dengan spesifikasi, tantangan harus setidaknya sepanjang 16 byte agar dianggap aman.
Setelah Anda membuat tantangan, simpan di sesi pengguna untuk memverifikasinya nanti.
Opsi pembuatan permintaan kredensial
Buat opsi permintaan kredensial sebagai objek publicKeyCredentialRequestOptions
.
Untuk melakukannya, andalkan library sisi server FIDO. Biasanya alat ini akan menawarkan fungsi utilitas yang dapat membuat opsi tersebut untuk Anda. Penawaran SimpleWebAuthn, 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 dari publicKeyCredentialRequestOptions
' bidang bisa berupa konstanta. Yang lainnya harus ditentukan secara dinamis di server:
rpId
: ID RP mana yang Anda harapkan akan 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
selama pendaftaran kredensial.challenge
: Data yang akan ditandatangani oleh penyedia kunci sandi untuk membuktikan bahwa pengguna memiliki kunci sandi pada saat permintaan autentikasi. Tinjau detailnya 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 Mengambil tantangan dari server RP dan Pembahasan mendalam tentang kredensial yang dapat ditemukan untuk mengetahui detailnya.userVerification
: Menunjukkan apakah verifikasi pengguna yang menggunakan kunci layar perangkat "diperlukan", "lebih disukai" atau "tidak direkomendasikan". Tinjau Mengambil verifikasi login dari server RP.timeout
: Berapa lama waktu (dalam milidetik) yang dapat diperlukan pengguna untuk menyelesaikan autentikasi. Nilai ini harus cukup longgar, dan lebih singkat daripada masa aktifchallenge
. Nilai default yang direkomendasikan adalah 5 menit, tetapi Anda dapat meningkatkannya — hingga 10 menit, yang masih dalam rentang yang direkomendasikan. Waktu tunggu yang lama wajar jika Anda memperkirakan pengguna akan 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.
Contoh kode: opsi permintaan kredensial
Kami menggunakan library SimpleWebAuthn dalam contoh kami. Di sini, kita menyerahkan pembuatan opsi permintaan kredensial ke fungsi generateAuthenticationOptions
.
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 memproses login pengguna
Saat navigator.credentials.get
berhasil di-resolve di klien, objek PublicKeyCredential
akan ditampilkan.
response
adalah AuthenticatorAssertionResponse
. Hal ini merepresentasikan respons penyedia kunci sandi terhadap petunjuk klien untuk membuat hal yang diperlukan untuk mencoba dan 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 pada nilai ini.
Kirim objek PublicKeyCredential
ke server.
Di server, lakukan tindakan berikut:
- Kumpulkan informasi yang Anda perlukan untuk memverifikasi pernyataan dan mengautentikasi pengguna:
- Dapatkan verifikasi login yang diharapkan yang Anda simpan dalam sesi saat Anda membuat opsi autentikasi.
- Dapatkan origin dan ID RP yang diharapkan.
- Temukan di database Anda siapa penggunanya. 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, cari kredensialid
yang cocok dengan kredensialid
yang ada di objekPublicKeyCredential
. Kemudian, cari pengguna yang sesuai menggunakan kunci asingpasskey_user_id
untuk tabel Users 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 kredensial
id
yang cocok dengan kredensialid
yang ada di objekPublicKeyCredential
.
Verifikasi pernyataan autentikasi. Serahkan langkah verifikasi ini ke library sisi server FIDO Anda, yang biasanya akan menawarkan fungsi utilitas untuk tujuan ini. Penawaran SimpleWebAuthn, misalnya,
verifyAuthenticationResponse
. Pelajari apa yang terjadi di balik layar di Lampiran: verifikasi respons autentikasi.Menghapus tantangan apakah verifikasi berhasil atau tidak, untuk mencegah serangan replay.
Buat pengguna login. Jika verifikasi berhasil, perbarui informasi sesi untuk menandai pengguna sebagai login. Anda juga dapat menampilkan objek
user
ke klien, sehingga frontend dapat menggunakan informasi yang terkait dengan pengguna yang baru login.
Kode contoh: memverifikasi dan memproses login pengguna
Kami menggunakan library SimpleWebAuthn dalam contoh kami. Di sini, kami 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 asal permintaan cocok dengan asal login situs Anda. Untuk aplikasi Android, tinjau 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 diwajibkan untuk kunci sandi. - Verifikasi tanda tangan. Untuk memverifikasi tanda tangan, Anda memerlukan:
- Signature, yang merupakan tantangan bertanda tangan:
response.signature
- Kunci publik, yang digunakan untuk memverifikasi tanda tangan.
- Data asli yang ditandatangani. Ini adalah data yang tanda tangannya akan diverifikasi.
- Algoritma kriptografi yang digunakan untuk membuat tanda tangan.
- Signature, yang merupakan tantangan bertanda tangan:
Untuk mempelajari langkah-langkah ini lebih lanjut, periksa kode sumber SimpleWebAuthn untuk verifyAuthenticationResponse
atau pelajari daftar lengkap verifikasi di spesifikasi.