Giao diện
Phiên đăng nhập
Sau khi đăng nhập, framework giữ phiên trong một cookie httpOnly tên tasco_session. Trang này mô tả cookie chứa gì, sống bao lâu, và vì sao thiết kế như vậy.
Cookie chứa gì
ts
interface TascoSession {
user: AuthUser // chỉ id, name, email — roles/permissions để rỗng
accessToken?: string
expiresAt?: number // mốc hết hạn của access token, tính bằng ms
}Chuỗi cookie có dạng v1.<iv>.<ciphertext>.<tag>, mã hoá AES-256-GCM bằng khoá dẫn xuất từ NUXT_SESSION_SECRET. GCM cho cả hai đảm bảo:
- Bí mật — không đọc được access token, kể cả khi có file cookie.
- Toàn vẹn — sửa một byte là thẻ xác thực sai, phiên bị coi như không tồn tại.
| Thuộc tính cookie | Giá trị | Vì sao |
|---|---|---|
httpOnly | true | JavaScript trong trang không đọc được |
sameSite | lax | Chặn gửi kèm ở request bên thứ ba |
secure | true ngoài môi trường dev | Chỉ gửi qua HTTPS |
maxAge | 8 giờ | Giới hạn trên của một phiên làm việc |
Vì sao không có refresh token
Cookie bị trình duyệt giới hạn 4KB. Access token JWT của IAM đã khoảng 2.4KB; thêm refresh token là tràn. Danh sách quyền (hàng trăm URI) còn lớn hơn nữa.
Hệ quả, đã cân nhắc và chấp nhận:
- Không tự gia hạn. Token IAM sống 24 giờ; hết hạn thì người dùng đăng nhập lại.
- Quyền không nằm trong cookie.
GET /auth/sessiongọi lạiuserInfomỗi lần khởi tạo app, nên quyền luôn tươi — đổi quyền ở IAM thì lần tải trang sau đã có hiệu lực, không phải đăng xuất.
Đừng nhét thêm dữ liệu vào phiên (hồ sơ, cấu hình, danh sách đơn vị). Vượt 4KB thì trình duyệt âm thầm bỏ cookie và biểu hiện ra là "đăng nhập xong vẫn bị đá về trang login".
Vòng đời
Đăng nhập → seal cookie (8 giờ) + trả { user }
Tải lại trang → plugin 02.auth gọi GET /auth/session
├─ không có cookie → user = null
├─ cookie hợp lệ → gọi userInfo lấy hồ sơ + quyền tươi
└─ userInfo lỗi/hết hạn → user = null (coi như chưa đăng nhập)
Gọi /api/** → token còn hạn thì gắn Bearer; hết hạn thì bỏ, backend trả 401
Nhận 401 → handler lỗi đưa về /auth/login kèm redirect
Đăng xuất → GET /auth/logout xoá cookieĐọc thông tin người dùng
ts
const { user, isAuthenticated } = useAuth()
user.value?.name // tên hiển thị
user.value?.email
user.value?.roles // string[] — mã role từ IAM
user.value?.permissions // string[] — URI quyềnState nằm ở useState('tasco:auth:user'), dùng chung toàn app. Cần nạp lại thủ công:
ts
const { fetchUser } = useAuth()
await fetchUser() // gọi /auth/session và cập nhật stateTrong route Nitro của dự án
ts
export default defineEventHandler((event) => {
const session = getTascoSession(event) // auto-import từ layer
if (!session?.accessToken) {
throw createError({ statusCode: 401, statusMessage: 'Phiên đăng nhập đã hết hạn.' })
}
// session.user.id, session.accessToken, session.expiresAt
})getTascoSession trả null khi không có cookie, cookie hỏng, hoặc sai khoá — không ném lỗi. Đừng tự đọc và giải mã cookie.
Không log, không chuyển tiếp token
Không console.log access token, không đưa vào thông báo lỗi, không gửi sang hệ thống thứ ba. Khi chuyển tiếp ra ngoài chỉ gắn vào header Authorization của đúng backend đích.
NUXT_SESSION_SECRET
bash
openssl rand -base64 32- Production: bắt buộc. Thiếu thì mọi route chạm phiên trả 500 kèm thông báo nêu tên biến.
- Dev: để trống thì dùng khoá tạm cố định và in cảnh báo ra console — tiện nhưng không an toàn, đừng dùng cho môi trường nào có người dùng thật.
- Đổi khoá làm mọi phiên hiện có mất hiệu lực: người dùng phải đăng nhập lại. Nếu chạy nhiều instance thì tất cả phải dùng chung một khoá, nếu không phiên tạo ở instance này sẽ không đọc được ở instance kia.
Liên quan
- Cấu hình & biến môi trường — nơi đặt
NUXT_SESSION_SECRET. - BFF — token được gắn vào request ra ngoài thế nào.
- Mô hình phân quyền — quyền lấy ở đâu ra.