---
title: 'Khỏi cần nhớ mật khẩu. Anh em mình xác thực bằng PasswordLess (Fido Key)'
date: '2026-06-29T11:38:12+07:00'
author: XV5HP
tags:
  - feature
  - password-less
  - fido-key
draft: false
summary: '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'
series: 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 **Admin** và **Writer** 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](https://github.com/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

::: highlight
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 update && sudo apt upgrade -y
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
```
