Xác thực không mật khẩu với Laravel WebAuthn và FIDO2
Passkey là công nghệ xác thực dựa trên chuẩn WebAuthn (Web Authentication API) và FIDO2. Passkey cho phép người dùng đăng nhập bằng sinh trắc học (vân tay, Face ID) hoặc mã PIN thiết bị thay vì mật khẩu truyền thống.
| Thành phần | Mô tả |
|---|---|
| Authenticator | Thiết bị tạo và lưu credential (điện thoại, máy tính, security key) |
| Relying Party (RP) | Website/ứng dụng yêu cầu xác thực (server Laravel) |
| Client | Trình duyệt web thực hiện giao tiếp giữa Authenticator và RP |
| Public/Private Key | Cặp khóa mật mã được tạo cho mỗi credential |
User nhấn "Thêm Passkey". Frontend gọi
/webauthn/register/options.
Server tạo challenge ngẫu nhiên (16 bytes), trả về RP info, user info.
Private Key lưu an toàn trong Secure Enclave/TPM.
# Cài đặt Laragear WebAuthn
composer require laragear/webauthn
# Publish cấu hình và migration
php artisan vendor:publish --provider="Laragear\WebAuthn\WebAuthnServiceProvider"
# Chạy migration
php artisan migrate
<?php
namespace App\Models;
use Laragear\WebAuthn\Contracts\WebAuthnAuthenticatable;
use Laragear\WebAuthn\WebAuthnAuthentication;
class User extends Authenticatable implements WebAuthnAuthenticatable
{
use WebAuthnAuthentication;
// ... existing code
}
// routes/web.php
use Laragear\WebAuthn\Http\Routes as WebAuthnRoutes;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
// Đăng ký WebAuthn routes
WebAuthnRoutes::register()->withoutMiddleware(VerifyCsrfToken::class);
// Đăng ký Passkey sau khi đã login
document.getElementById('passkey-register').addEventListener('click', async function() {
try {
const { credential, success, error } = await Webpass.attest(
'/webauthn/register/options', // Endpoint lấy options
'/webauthn/register' // Endpoint gửi credential
);
if (success) {
alert('Passkey đã được thêm thành công!');
} else {
alert(error || 'Không thể thêm Passkey.');
}
} catch (err) {
alert(err.message);
}
});
document.getElementById('passkey-login').addEventListener('click', async function() {
try {
const email = document.getElementById('login-email').value;
const optionsUrl = '/webauthn/login/options' +
(email ? `?email=${encodeURIComponent(email)}` : '');
const { user, success, error } = await Webpass.assert(
optionsUrl,
'/webauthn/login'
);
if (success) {
window.location.href = '/dashboard';
} else {
alert(error || 'Đăng nhập thất bại.');
}
} catch (err) {
alert(err.message);
}
});
| Method | Endpoint | Mô tả | Auth? |
|---|---|---|---|
POST |
/webauthn/register/options |
Lấy options để đăng ký credential | ✅ Yes |
POST |
/webauthn/register |
Gửi attestation để lưu credential | ✅ Yes |
POST |
/webauthn/login/options |
Lấy options để đăng nhập | ❌ Guest |
POST |
/webauthn/login |
Gửi assertion để xác thực | ❌ Guest |
Bảng webauthn_credentials lưu trữ:
| Column | Mô tả |
|---|---|
id |
Credential ID (unique identifier) |
user_id |
Foreign key đến bảng users |
public_key |
Public Key của credential (CBOR encoded) |
counter |
Sign counter để phát hiện credential bị clone |
• Laragear/WebAuthn on GitHub
• WebAuthn.io - Demo &
Testing
• W3C
WebAuthn Specification
• → Full-text Search Guide