XV5HP // HAI PHONG // VIET NAM

The Rich Dad

// A personal blog transmitting thoughts over imperfect radio waves. Expect noise, fading, and low bandwidth—but always authentic signals.

Khỏi cần nhớ mật khẩu. Anh em mình xác thực bằng PasswordLess (Fido Key)

§ PART 7 OF 7 Lazyblog

Nay lên văn phòng, mở máy tính lên làm việc như thường lệ, mình mất một lúc lâu đứng trước màn hình đăng nhập để nghĩ. Mình không thể nhớ nổi mật khẩu của mình vừa đổi vào tuần trước. Một chuyên gia bảo mật vẫn khuyên mọi người phải đổi mật khẩu thường xuyên nhưng nay mới lâm vào cảnh "trải nghiệm người dùng".

Những năm trước, chuyện phải nhớ chục cái khác nhau vẫn thoải mái. Nhưng dạo này, mình chẳng còn nhớ nhiều được nữa. Nếu cuộc sống chẳng có nhiều thứ đáng phải lo nghĩ, toàn tâm toàn ý làm bảo mật thì việc nhớ mật khẩu trước giờ với mình chẳng vấn đề gì.

Mình sẽ đổi máy tính của mình sang dạng Passwordless. Tức là khỏi cần nhớ gì. Mỗi lần muốn đăng nhập thì cắm USB Token, nhập PIN CODE và chạm để unlock.

Lazyblog PasswordLess

Trung nghĩ sao nếu Blog của mình đăng nhập trang AdminWriter bằng USB Token (passwordless). Nó sẽ đậm chất cá nhân, khỏi nhớ mật khẩu và cũng an toàn hơn.

2026-06-29 11:52 Mình sẽ viết thêm chi tiết sau nhé, giờ note tạm thế này để đi ăn cơm đã. Đói quá rồi!

2026-06-30 00:38 LazyBlog sử dụng WebAuthn/FIDO2 cho passwordless admin login, thay thế hoàn toàn password truyền thống khi bật tính năng này. Cái hay ở chỗ, passwordless này các thiết bị như iPhone hoặc MacOS có thể tận dụng cơ chế xác thực sinh học như vân tay hoặc Face ID. Hẹ. Kiến trúc của chức năng này bao gồm các thành phần chính:

  • WebAuthn.php: Wrapper quanh thư viện lbuchs/webauthn, xử lý registration và login flow
  • WebAuthnCredential.php: DTO cho từng credential đã đăng ký
  • WebAuthnCredentialStore.php: Lưu credentials vào file JSON content/admin/webauthn-credentials.json
  • AdminSecurityController.php: HTTP endpoints cho registration, login, revoke

Để bật phương thức xác thực này, bạn phải pull code bản mới nhất (tính từ thời điểm bài này v1.20.2). Rồi đưa hai giá trị sau vào trong .env

# Chỗ này đổi hatrunghieu.com thành domain blog của bạn
WEBAUTHN_RP_ID="hatrunghieu.com"

# Giá trị True sẽ bắt LazyBlog phải xác thực PasswordLess. Còn nếu để false sẽ dùng mật khẩu như bình thường
WEBAUTHN="true"

Flow đăng ký thiết bị

  1. Operator click [ + ADD KEY ] để nhập tên thiết bị
  2. Browser sẽ gửi một POST /admin/webauthn/register/begin tới Server để tạo challenge (60s TTL), lưu vào session. Sau đó trả về PublicKey cho Browser
  3. Browser gọi navigator.credentials.create({ publicKey }) để kích hoạt Yubikey/Touch ID/Face ID được kích hoạt. Authenticator sẽ tạo keypair, server chỉ lưu public key còn private key do thiết bị quản lý.
  4. Browser trả về thông tin attestationObject + clientDataJSON
  5. Sau đó Browser gửi một request POST /admin/webauthn/register/complete để Server verify attestation với lbuchs/WebAuthn. Nếu mọi thứ OK thì sẽ lưu lưu publicKey + credential ID vào JSON file. Giá trị Counter bắt đầu từ 0.

Bên trong file content/admin/webauthn-credentials.json sẽ lưu như này.

{
  "user_handle": "<32-byte base64url>",
  "credentials": [
    {
      "id": "...",
      "publicKey": "...",
      "counter": 0,
      "name": "Yubikey 5C primary",
      "transports": ["usb", "nfc"],
      "aaguid": "...",
      "created_at": "2026-06-30T...",
      "last_used_at": null
    }
  ]
}

Flow Login xác thực

  1. Admin click [ TAP YOUR SECURITY KEY ] để bắt đầu quá trình xác thực
  2. Browser sẽ gửi một HTTP Request POST /admin/webauthn/login/begin, Server trả về allowCredentials (list all registered keys). Đồng thời Challenge mới được tạo (60s TTL)
  3. Browser gọi navigator.credentials.get({ publicKey }) để kích hoạt thiết bị. Authenticator tìm credential ID phù hợp. Admin tap key hoặc Scan Face ID (với xác thực trên điện thoại) để authenticator ký assertion
  4. Browser trả về signature + authenticatorData + clientDataJSON
  5. Và gửi HTTP Request tới POST /admin/webauthn/login/complete để Server verify signature với publicKey đã lưu. Kiểm tra signature counter (monotonic - phải tăng). Nếu OK Auth::finalizeLogin() trong backend của LazyBlog sẽ set session cookie cho admin. Update counter + last_used_at trong JSON

Giá trị WEBAUTHN_RP_ID="hatrunghieu.com" phải được thiết lập trong file .env. Nhiệm vụ của nó để khi login, browser chỉ cho phép sử dụng thiết bị để sign vào đúng domain. Vì nếu Hacker có tạo ra site phishing như evil.com cố tình làm giống, cũng không thể thu thập thông tin về thiết bị. Mọi HTTP Request POST đều xác thực CSRF Token.

Ubuntu PasswordLess

Việc cấu hình thay đổi cơ chế xác thực có thể gây ra rủi ro không thể đăng nhập được vào hệ hống. Vì thế, luôn giữ một phiên hoặc một Tab Terminal chạy quyền đăng nhập Root. Còn mở một phiên khác ra mà chỉnh sửa

Cài đặt những thứ cần thiết

sudo apt install libpam-u2f pamu2fcfg -y

Tạo thông tin xác thực

sudo mkdir -p /etc/yubico
sudo touch /etc/yubico/credentials

Đăng ký thiết bị USB Token (device-bound) cho tài khoản của bạn. Hệ thống sẽ hỏi bạn một cái mã PIN, rồi bạn cần chạm vào chiếc USB để nó tạo ra một chuỗi token đủ mạnh thay cho mật khẩu của bạn.

pamu2fcfg -u$(whoami) -N | sudo tee -a /etc/yubico/credentials

Nếu có nhiều hơn hai cái USB Token, mình nghĩ bạn nên chạy thêm lệnh dưới đây để biến nó thành thiết bị backup. Nhỡ một cái bị mất thì vẫn còn cái đăng nhập được.

pamu2fcfg -u$(whoami) -N -n | sudo tee -a /etc/yubico/credentials

Thay đổi cơ chế đăng nhập

Tất cả các hoạt động đăng nhập trên Ubuntu đều được quản lý xác thực bởi PAM (Privielge Account Management). File cấu hình của nó năm ở đường dẫn /etc/pam.d/common-auth. Tất nhiên, bạn cần quyền root mới có thể chỉnh sửa được nó. Vì thế, hãy chỉnh sửa và thêm dòng dưới này vào phía trên cùng của file.

auth sufficient pam_u2f.so authfile=/etc/yubico/credentials cue

Việc này sẽ bắt hệ thống ưu tiên xác thực USB Token trước. File tổng quan sau khi sửa sẽ như này.

# As of pam 1.0.1-6, this file is managed by pam-auth-update by default.
# To take advantage of this, it is recommended that you configure any
# local modules either before or after the default block, and use
# pam-auth-update to manage selection of other modules.  See
# pam-auth-update(8) for details.

auth sufficient pam_u2f.so authfile=/etc/yubico/credentials cue
# here are the per-package modules (the "Primary" block)
auth	[success=2 default=ignore]	pam_unix.so nullok
auth	[success=1 default=ignore]	pam_sss.so use_first_pass