Đang tải...

Ếch Trendy

Cùng chú ếch nhỏ khám phá thế giới Web đầy biến động. Cập nhật trending nhanh như cách ếch đớp mồi! ⌨️🌿


0

Passkeys Trong Web App: Đăng Nhập Không Mật Khẩu An Toàn Hơn Cho Người Dùng

Ếch Trendy
Ếch Trendy

4 tháng trước · 9 phút đọc

Password đang là điểm yếu lớn nhất của bạn

Mình từng nghĩ password mạnh là đủ. 16 ký tự, chữ hoa chữ thường, ký tự đặc biệt, đổi định kỳ - chuẩn chỉnh hết. Rồi một ngày nhận email thông báo breach từ HaveIBeenPwned. Toàn bộ công sức đó vô nghĩa chỉ vì server phía kia lưu plain text.

81% (Varonis (citing LastPass)) - con số này không còn là cảnh báo nữa, nó là thực tế hàng ngày.

Vấn đề không phải ở người dùng chọn password yếu. Vấn đề là password về bản chất là shared secret - bạn biết, server biết. Khi server bị compromise, bạn mất. Phishing giả trang login? Bạn mất. Keylogger trên máy? Bạn mất.

Passkeys sinh ra để giải quyết đúng vào gốc rễ đó.

344592 Password bị đánh cắp theo nhiều con đường - không phải lỗi người dùng


Passkeys hoạt động như thế nào?

Passkeys dựa trên chuẩn WebAuthn (Web Authentication API) - chuẩn mở do W3C và FIDO Alliance phát triển. Nghe có vẻ phức tạp, nhưng về mặt user thì đơn giản đến mức ngạc nhiên: bạn chỉ cần bấm nút và xác thực bằng vân tay hoặc Face ID.

Phía dưới, cơ chế hoạt động như sau:

Khi đăng ký (Registration):

  1. Thiết bị của người dùng tạo một cặp khóa mật mã (public key + private key)
  2. Private key được lưu an toàn trong thiết bị (Secure Enclave trên iPhone, TPM trên Windows) - không bao giờ rời khỏi máy
  3. Public key được gửi lên server và lưu lại

Khi đăng nhập (Authentication):

  1. Server gửi một chuỗi ngẫu nhiên (challenge) xuống
  2. Thiết bị yêu cầu người dùng xác thực bằng sinh trắc học
  3. Private key ký lên challenge đó tạo ra chữ ký số
  4. Server dùng public key để xác minh chữ ký - đúng thì cho vào

344593 Private key không bao giờ rời khỏi thiết bị - đây là điểm khác biệt cốt lõi

Tại sao an toàn hơn? Server chỉ lưu public key. Public key bị lộ cũng không làm được gì - không thể dùng nó để đăng nhập. Phishing cũng vô dụng vì passkey được bind với domain cụ thể - trang giả mạo khác domain sẽ không hoạt động.


Lợi ích UX: không chỉ là bảo mật

Bảo mật tốt hơn mà UX tệ hơn thì người dùng sẽ không dùng. Đây là bài toán cổ điển trong security. Passkeys giải quyết cả hai.

Đăng nhập nhanh hơn rõ rệt. Không cần gõ email, không cần nhớ password, không cần chờ OTP 30 giây. Chạm vân tay là xong - dưới 3 giây. 14.9 seconds vs 30.4 seconds - passkeys are about 2x faster (Corbado citing Google research / FIDO Alliance Passkey Index) so với luồng login truyền thống.

Không còn "Quên mật khẩu". Đây là điểm mình thích nhất từ góc độ product. Flow "Quên mật khẩu" là một trong những điểm drop-off lớn nhất trong funnel đăng nhập. Với passkeys, flow đó biến mất.

Cross-device sync. Passkeys được đồng bộ qua iCloud Keychain (Apple) hoặc Google Password Manager. Mua iPhone mới? Passkeys theo sang ngay, không cần setup lại.

344594 Luồng đăng nhập passkeys vs password truyền thống - chênh lệch rõ

Một điểm trade-off cần thừa nhận: nếu người dùng mất thiết bị và không có backup, việc khôi phục phức tạp hơn. Đây là lý do tại sao hầu hết app triển khai passkeys như một thêm vào bên cạnh password, chứ không phải thay thế hoàn toàn ngay lập tức.


Tương thích thiết bị và trình duyệt

Câu hỏi thực tế nhất: người dùng của mình có dùng được không?

WebAuthn has about 94% global support (usage-aligned) (SnapAuth). Tóm lại, coverage đã đủ tốt để production.

Nền tảng Hỗ trợ Passkeys
iOS 16+ / macOS Ventura+ ✅ iCloud Keychain
Android 9+ ✅ Google Password Manager
Windows 10/11 ✅ Windows Hello
Chrome 108+ ✅
Safari 16+ ✅
Firefox ⚠️ Hỗ trợ một phần

344595 Coverage đã đủ rộng để bắt đầu triển khai - Firefox là điểm cần lưu ý

Với người dùng dùng thiết bị cũ hoặc trình duyệt không hỗ trợ, bạn cần fallback về password hoặc magic link. Đây không phải hạn chế lớn - chỉ cần thiết kế luồng progressive enhancement: ai hỗ trợ passkeys thì dùng, ai không thì về luồng cũ.

Một điểm hay: passkeys có thể dùng cross-device authentication. Tức là bạn login trên laptop bằng cách quét QR code, rồi xác thực trên điện thoại. Server nhận kết quả từ điện thoại và cho phép laptop vào. Tất cả qua Bluetooth proximity.


Tích hợp Passkeys vào web app thực tế

Phần thực chiến. Mình sẽ đi qua flow cơ bản với WebAuthn API thuần - không cần thư viện phức tạp để hiểu bản chất.

Bước 1: Đăng ký passkey (Registration)

// Gọi API backend để lấy challenge và options
const response = await fetch('/api/auth/passkey/register/begin', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ userId: currentUser.id })
});
const options = await response.json();

// Chuyển challenge từ base64 sang ArrayBuffer
options.challenge = base64ToArrayBuffer(options.challenge);
options.user.id = base64ToArrayBuffer(options.user.id);

// Gọi WebAuthn API - trình duyệt sẽ hiển thị dialog sinh trắc học
const credential = await navigator.credentials.create({
  publicKey: options
});

// Gửi kết quả về backend để lưu public key
await fetch('/api/auth/passkey/register/complete', {
  method: 'POST',
  body: JSON.stringify(serializeCredential(credential))
});

Bước 2: Đăng nhập với passkey (Authentication)

// Lấy challenge từ server
const response = await fetch('/api/auth/passkey/login/begin', {
  method: 'POST'
});
const options = await response.json();

options.challenge = base64ToArrayBuffer(options.challenge);

// Người dùng xác thực bằng sinh trắc học
const assertion = await navigator.credentials.get({
  publicKey: options
});

// Gửi chữ ký về backend để xác minh
const result = await fetch('/api/auth/passkey/login/complete', {
  method: 'POST',
  body: JSON.stringify(serializeAssertion(assertion))
});

344596 Flow registration và authentication - backend chỉ lưu public key, không bao giờ thấy private key

Phía backend (Node.js với SimpleWebAuthn)

Viết WebAuthn backend từ đầu khá phức tạp vì phải xử lý encoding, verification đúng spec. Dùng thư viện là lựa chọn thực tế hơn nhiều.

import { generateRegistrationOptions, verifyRegistrationResponse } from '@simplewebauthn/server';

// Tạo options để gửi xuống client
const options = await generateRegistrationOptions({
  rpName: 'My App',           // Tên app của bạn
  rpID: 'myapp.com',          // Domain của app
  userID: user.id,
  userName: user.email,
  attestationType: 'none',    // Không cần attestation cho production thông thường
  authenticatorSelection: {
    residentKey: 'required',  // Bắt buộc để có passkey thật sự
    userVerification: 'required'
  }
});

// Lưu challenge vào session (dùng để verify sau)
req.session.challenge = options.challenge;

// Xác minh khi client gửi kết quả registration về
const verification = await verifyRegistrationResponse({
  response: clientResponse,
  expectedChallenge: req.session.challenge,
  expectedOrigin: 'https://myapp.com',
  expectedRPID: 'myapp.com'
});

if (verification.verified) {
  // Lưu publicKey và credentialID vào database
  await db.passkeys.create({
    userId: user.id,
    credentialID: verification.registrationInfo.credentialID,
    publicKey: verification.registrationInfo.credentialPublicKey,
    counter: verification.registrationInfo.counter
  });
}

Thư viện SimpleWebAuthn handle toàn bộ phần crypto phức tạp. Bạn chỉ cần lo phần business logic.

Nếu bạn đang học về xác thực và bảo mật web từ đầu, khóa Node & ExpressJS có cover phần session, middleware và cách xây dựng authentication flow cơ bản - nền tảng cần thiết trước khi đi sâu vào passkeys.


Checklist trước khi triển khai

Passkeys không phức tạp về mặt kỹ thuật, nhưng có một số điểm cần chuẩn bị kỹ trước khi production.

Security:

  • Luôn dùng HTTPS - WebAuthn không hoạt động trên HTTP
  • rpID phải khớp chính xác với domain (không phải subdomain khác)
  • Lưu counter của authenticator và kiểm tra mỗi lần login để phát hiện clone credential
  • Xử lý đúng challenge expiry (thường 60-120 giây)

UX:

  • Đặt nút "Đăng nhập bằng Passkey" nổi bật, không ẩn trong settings
  • Luôn giữ fallback (password hoặc magic link) cho thiết bị không hỗ trợ
  • Giải thích ngắn gọn passkey là gì ngay lần đầu user thấy
  • Cho phép user quản lý (xem danh sách, xóa) passkeys đã đăng ký

Database:

  • Mỗi user có thể có nhiều passkeys (nhiều thiết bị)
  • Cần lưu: credentialID, publicKey, counter, deviceType, createdAt

344597 Checklist đủ - triển khai nhanh, không hối tiếc

Một lần mình thấy team bỏ qua phần counter vì nghĩ "không cần thiết". Đây là cơ chế phát hiện nếu ai đó clone credential vật lý của authenticator phần cứng - hiếm nhưng không phải không xảy ra. Tốn thêm một dòng code, không lý do gì bỏ.

Tải checklist đầy đủ tại đây: Tải checklist triển khai Passkeys

Giờ bạn đã có đủ để bắt đầu. Bước tiếp:

  1. Chạy thử demo SimpleWebAuthn trên localhost
  2. Kiểm tra thiết bị team đang dùng có hỗ trợ không
  3. Thiết kế fallback flow cho người dùng thiết bị cũ
  4. Deploy với feature flag - rollout từng phần

Gặp vấn đề gì trong quá trình tích hợp, drop comment - mình đọc hết.

Trang web dùng cookie để hoạt động và đo hiệu quả quảng cáo. Xem chính sách bảo mật