Các công cụ dòng lệnh (CLI), script tự động hoá hay AI agent thường cần gọi API của website EzyPlatform thay cho một tài khoản cụ thể. Thay vì bắt người dùng mở trình duyệt, sao chép access token rồi dán vào terminal, EzyLogin cung cấp cơ chế đăng nhập từ dòng lệnh: công cụ tự mở trình duyệt, người dùng đăng nhập như bình thường và bấm Phê duyệt, công cụ nhận về một access token riêng cho mình.
Bài viết này hướng dẫn quản trị viên bật tính năng, mô tả những gì người dùng và quản trị viên nhìn thấy khi đăng nhập từ dòng lệnh, và hướng dẫn lập trình viên tích hợp cơ chế này vào công cụ của mình.

Tổng quan về đăng nhập từ dòng lệnh

Đăng nhập từ dòng lệnh của EzyLogin dựa trên chuẩn OAuth 2.0 Authorization Code kết hợp PKCE (Proof Key for Code Exchange), cùng cách mà các công cụ như GitHub CLI hay Google Cloud CLI đang dùng. Cơ chế này áp dụng cho cả hai phía của EzyPlatform:
  • Người dùng đăng nhập vào website và nhận về access token của người dùng, dùng để gọi các API của website.
  • Quản trị viên đăng nhập vào trang quản trị và nhận về access token của quản trị viên, dùng để gọi các API quản trị với đúng quyền của tài khoản đó.
Toàn bộ quá trình diễn ra như sau:
  1. Công cụ dòng lệnh mở một cổng tạm trên chính máy tính đang chạy (127.0.0.1), tạo một mã bí mật ngẫu nhiên (code_verifier) và mở trình duyệt tới trang phê duyệt của EzyLogin.
  2. Người dùng đăng nhập nếu chưa đăng nhập (kể cả bước xác thực hai lớp nếu có), xem lại thông tin và bấm Phê duyệt.
  3. EzyLogin chuyển trình duyệt về cổng tạm trên máy tính kèm một mã uỷ quyền dùng một lần, có hiệu lực trong 2 phút.
  4. Công cụ gửi mã uỷ quyền cùng code_verifier lên EzyLogin để đổi lấy access token.
Vì mã uỷ quyền chỉ được gửi về địa chỉ trên chính máy tính đang chạy công cụ, và chỉ đổi được thành token khi có code_verifier mà chỉ công cụ biết, nên kể cả khi mã uỷ quyền bị lộ thì kẻ xấu cũng không thể dùng được.

Bật đăng nhập từ dòng lệnh trong trang quản trị

Tính năng này mặc định tắt. Quản trị viên bật riêng cho người dùng và cho quản trị viên, nên có thể chỉ mở cho một phía khi cần. Cả hai cài đặt đều nằm trong EzyLogin → Cài đặt thường của trang quản trị.
Khi một trong hai cài đặt đang tắt, cả trang phê duyệt lẫn API đổi token của phía tương ứng đều trả về lỗi 403 với nội dung {"cliLogin":"notAllowed"}. Việc tắt cài đặt chỉ chặn các lần đăng nhập mới, các phiên đã được phê duyệt trước đó vẫn hoạt động cho tới khi hết hạn hoặc bị đăng xuất.

Cho phép quản trị viên đăng nhập từ dòng lệnh

Cài đặt dành cho quản trị viên nằm ở đầu phần Cài đặt thường, ngay dưới cài đặt xác thực hai lớp của quản trị viên.
Cài đặt cho phép quản trị viên đăng nhập từ dòng lệnh
  1. Tick Cho phép quản trị viên đăng nhập từ dòng lệnh (CLI) để quản trị viên đăng nhập được vào trang quản trị từ công cụ dòng lệnh.

Cho phép người dùng đăng nhập từ dòng lệnh

Cài đặt dành cho người dùng nằm ở cuối phần Cài đặt thường, ngay dưới cài đặt xác thực hai lớp của người dùng.
Cài đặt cho phép người dùng đăng nhập từ dòng lệnh
  1. Tick Cho phép người dùng đăng nhập từ dòng lệnh (CLI) để người dùng của website đăng nhập được từ công cụ dòng lệnh.
  2. Nhấn Lưu (hoặc Ctrl + S / Command + S) để áp dụng cả hai cài đặt.

Người dùng và quản trị viên phê duyệt đăng nhập như thế nào

Sau khi tính năng được bật, mỗi lần chạy lệnh đăng nhập của một công cụ, trình duyệt sẽ mở ra trang phê duyệt của EzyLogin. Người dùng phê duyệt trên website, còn quản trị viên phê duyệt trên trang quản trị, nhưng hai trang có cùng bố cục.

Trang phê duyệt dành cho người dùng

Trang phê duyệt của người dùng nằm tại https://<website>/ezylogin/cli/authorize. Nếu người dùng chưa đăng nhập, EzyLogin hiển thị màn đăng nhập trước, sau đó mới tới màn phê duyệt dưới đây.
Trang phê duyệt đăng nhập từ dòng lệnh của người dùng
  1. Tên công cụ đang xin quyền truy cập, lấy từ tham số client_name mà công cụ gửi lên. Nếu công cụ không gửi tên, trang hiển thị "Một ứng dụng dòng lệnh".
  2. Quyền mà công cụ sẽ có: gọi API và quản lý dữ liệu thay người dùng, giống như khi đăng nhập trên trình duyệt.
  3. Cảnh báo an toàn: chỉ phê duyệt khi chính bạn vừa chạy lệnh đăng nhập trên máy tính của mình. Nếu bạn không chạy lệnh nào mà vẫn thấy trang này, hãy bấm Huỷ.
  4. Địa chỉ nhận kết quả: luôn là một địa chỉ trên chính máy tính đang chạy công cụ (127.0.0.1, localhost hoặc [::1]). EzyLogin từ chối mọi địa chỉ khác.
  5. Huỷ để quay lại mà không cấp quyền, hoặc Phê duyệt để cấp quyền cho công cụ.
Giao diện trang phê duyệt của người dùng có thể được tuỳ biến bằng page fragment cli_approval_screen (phần content), giống như các màn hình khác của EzyLogin trên website.

Trang phê duyệt dành cho quản trị viên

Trang phê duyệt của quản trị viên nằm trên tên miền trang quản trị, tại https://<trang-quan-tri>/ezylogin/cli/authorize, và hiển thị bên trong giao diện trang quản trị. Quản trị viên phải đăng nhập trang quản trị trước khi thấy màn hình này.
Trang phê duyệt đăng nhập từ dòng lệnh của quản trị viên
  1. Tên công cụ đang xin quyền truy cập vào tài khoản quản trị.
  2. Quyền mà công cụ sẽ có: gọi API quản trị và quản lý dữ liệu thay bạn, giống như khi bạn đăng nhập trang quản trị. Token nhận được mang đúng các vai trò và quyền của tài khoản quản trị viên đã phê duyệt, không hơn không kém.
  3. Cảnh báo an toàn: chỉ phê duyệt khi chính bạn vừa chạy lệnh đăng nhập trên máy tính của mình.
  4. Địa chỉ nhận kết quả trên chính máy tính đang chạy công cụ.
  5. Huỷ hoặc Phê duyệt.
Vì token của quản trị viên có quyền rất lớn, chỉ nên bật Cho phép quản trị viên đăng nhập từ dòng lệnh (CLI) khi thực sự cần, và nên bật xác thực hai lớp cho các tài khoản quản trị viên.

Tích hợp đăng nhập từ dòng lệnh vào công cụ của bạn

Phần này dành cho lập trình viên muốn viết công cụ dòng lệnh, script hoặc AI agent đăng nhập vào EzyPlatform qua EzyLogin. Luồng đăng nhập của người dùng và của quản trị viên giống hệt nhau, chỉ khác địa chỉ gốc và API đổi token:
Người dùngQuản trị viên
Địa chỉ gốcĐịa chỉ websiteĐịa chỉ trang quản trị
Trang phê duyệtGET /ezylogin/cli/authorizeGET /ezylogin/cli/authorize
API đổi tokenPOST /ezylogin/api/v1/cli/tokenPOST /ezylogin/api/v1/cli/me/token
Cài đặt cần bậtCho phép người dùng đăng nhập từ dòng lệnh (CLI)Cho phép quản trị viên đăng nhập từ dòng lệnh (CLI)

Bước 1: Chuẩn bị PKCE và cổng nhận kết quả

Trước khi mở trình duyệt, công cụ cần chuẩn bị:
  1. Một HTTP server tạm lắng nghe trên 127.0.0.1 với một cổng ngẫu nhiên, ví dụ http://127.0.0.1:53682/callback. Đây chính là redirect_uri.
  2. code_verifier: chuỗi ngẫu nhiên dài từ 43 đến 128 ký tự, chỉ gồm A-Z, a-z, 0-9 và -._~. Chuỗi này phải được giữ bí mật trong công cụ.
  3. code_challenge: BASE64URL(SHA256(code_verifier)), không có ký tự = ở cuối (luôn dài 43 ký tự).
  4. state: chuỗi ngẫu nhiên tối đa 256 ký tự, dùng để kiểm tra kết quả trả về đúng là của lần đăng nhập này.

Bước 2: Mở trình duyệt tới trang phê duyệt

Công cụ mở trình duyệt mặc định của máy tính tới địa chỉ:
<địa chỉ gốc>/ezylogin/cli/authorize?client_name=<tên công cụ>&redirect_uri=<redirect_uri>&state=<state>&code_challenge=<code_challenge>&code_challenge_method=S256
EzyLogin kiểm tra các tham số như sau, tham số không hợp lệ sẽ bị trả về lỗi 400:
  • client_name: tên hiển thị của công cụ trên trang phê duyệt, không bắt buộc, tối đa 64 ký tự.
  • redirect_uri: bắt buộc, tối đa 256 ký tự, phải dùng http, có host là 127.0.0.1, localhost hoặc [::1], có cổng rõ ràng, không chứa thông tin đăng nhập (user:pass@) và không chứa #.
  • state: bắt buộc, tối đa 256 ký tự.
  • code_challenge: bắt buộc, đúng 43 ký tự base64url.
  • code_challenge_method: chỉ chấp nhận S256.
Nên in địa chỉ này ra terminal để người dùng tự mở khi trình duyệt không mở được, và chờ đủ lâu (vài phút) để người dùng kịp đăng nhập và xác thực hai lớp.

Bước 3: Nhận mã uỷ quyền

Khi người dùng bấm Phê duyệt, trình duyệt được chuyển về:
<redirect_uri>?code=<mã uỷ quyền>&state=<state>
Công cụ cần so sánh state nhận được với state đã tạo ở bước 1 và bỏ qua kết quả nếu không khớp, sau đó trả về cho trình duyệt một trang ngắn báo người dùng có thể quay lại terminal. Mã uỷ quyền chỉ có hiệu lực trong 2 phút và chỉ dùng được một lần.

Bước 4: Đổi mã uỷ quyền lấy access token

Công cụ gửi mã uỷ quyền lên API đổi token tương ứng (/ezylogin/api/v1/cli/token cho người dùng, /ezylogin/api/v1/cli/me/token cho quản trị viên):
POST /ezylogin/api/v1/cli/token
Content-Type: application/json

{
  "code": "<mã uỷ quyền>",
  "codeVerifier": "<code_verifier>",
  "redirectUri": "<redirect_uri>"
}
redirectUri phải trùng khớp với redirect_uri đã gửi ở bước 2. Nếu hợp lệ, EzyLogin trả về:
{
  "accessToken": "<access token>",
  "accessTokenExpiredAt": 1767225600000,
  "accessTokenStatus": "ACTIVATED"
}
  1. accessToken: token dùng để gọi API.
  2. accessTokenExpiredAt: thời điểm token hết hạn (mili giây). Thời hạn này lấy theo cài đặt Thời gian token hết hạn của EzyPlatform, dành cho người dùng hoặc cho quản trị viên.
  3. accessTokenStatus: ACTIVATED, hoặc ACTIVATED_2FA nếu tài khoản đã bật xác thực hai lớp. Token này được coi là đã qua xác thực hai lớp vì người dùng đã xác thực trên trình duyệt trước khi phê duyệt.

Bước 5: Gọi API bằng access token

Công cụ lưu token ở nơi an toàn trên máy (ví dụ một tệp chỉ chủ sở hữu được đọc) và gửi kèm mỗi request trong header Authorization:
GET /api/v1/...
Authorization: Bearer <access token>
Khi API trả về 401, token đã hết hạn hoặc phiên đã bị đăng xuất: công cụ chỉ cần chạy lại luồng đăng nhập từ bước 1.

Các lỗi thường gặp

Khi tích hợp, công cụ nên hiển thị thông báo rõ ràng cho các lỗi sau:
  • 403 {"cliLogin":"notAllowed"}: quản trị viên chưa bật đăng nhập từ dòng lệnh cho phía tương ứng trong EzyLogin → Cài đặt thường.
  • 400 với redirectUri, state, codeChallenge, codeChallengeMethod hoặc clientName không hợp lệ: tham số ở bước 2 sai định dạng.
  • 400 {"code":"invalid"}: mã uỷ quyền sai, đã hết hạn 2 phút, đã được dùng, redirectUri không khớp hoặc codeVerifier không khớp với code_challenge.
  • 400 {"owner":"invalid"}: tài khoản đã phê duyệt không còn ở trạng thái hoạt động.

Ví dụ với Node.js

Đoạn mã dưới đây (Node.js 18 trở lên, không cần thư viện ngoài) thực hiện toàn bộ luồng đăng nhập của người dùng. Với quản trị viên, chỉ cần đổi baseUrl thành địa chỉ trang quản trị và API đổi token thành /ezylogin/api/v1/cli/me/token.
const crypto = require('crypto');
const http = require('http');
const { exec } = require('child_process');

const baseUrl = 'https://example.com';
const base64url = (buf) => buf.toString('base64url');

async function login() {
  const codeVerifier = base64url(crypto.randomBytes(32));
  const codeChallenge = base64url(crypto.createHash('sha256').update(codeVerifier).digest());
  const state = base64url(crypto.randomBytes(16));

  const server = http.createServer();
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  const redirectUri = `http://127.0.0.1:/callback`;

  const code = await new Promise((resolve, reject) => {
    server.on('request', (req, res) => {
      const url = new URL(req.url, redirectUri);
      if (url.pathname !== '/callback') {
        res.writeHead(404).end();
        return;
      }
      res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
      res.end('Đăng nhập thành công, bạn có thể quay lại terminal.');
      if (url.searchParams.get('state') !== state) {
        reject(new Error('state không khớp'));
      } else {
        resolve(url.searchParams.get('code'));
      }
    });
    const authorizeUrl = `/ezylogin/cli/authorize?` + new URLSearchParams({
      client_name: 'my-cli',
      redirect_uri: redirectUri,
      state,
      code_challenge: codeChallenge,
      code_challenge_method: 'S256',
    });
    console.log(`Mở trình duyệt để phê duyệt: `);
    exec(`process.platform === 'win32' ? 'start ""' : 'xdg-open' ""`);
  }).finally(() => server.close());

  const res = await fetch(`/ezylogin/api/v1/cli/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ code, codeVerifier, redirectUri }),
  });
  if (!res.ok) {
    throw new Error(`Đổi token thất bại:  `);
  }
  return res.json();
}

login().then((token) => console.log('Hết hạn lúc', new Date(token.accessTokenExpiredAt)));
Công cụ quản lý tài liệu trên chợ EzyPlatform cũng dùng đúng luồng này để đăng nhập vào ezyplatform.com.

Quản lý và thu hồi các phiên đăng nhập từ dòng lệnh

Mỗi lần phê duyệt tạo ra một access token mới và một phiên đăng nhập riêng, ghi lại địa chỉ IP và User-Agent của công cụ tại thời điểm đổi token. Nhờ vậy, đăng nhập từ dòng lệnh được quản lý giống hệt các lần đăng nhập trên trình duyệt:
  • Người dùng xem phiên này trong danh sách thiết bị đã đăng nhập của mình và có thể đăng xuất bất cứ lúc nào.
  • Quản trị viên xem và đăng xuất các phiên tại EzyLogin → Các phiên người dùng, có thể lọc theo người dùng, trạng thái và lý do thu hồi.
  • Phiên tự hết hạn theo cài đặt Thời gian token hết hạn của EzyPlatform.
Phiên đăng nhập từ dòng lệnh trong danh sách các phiên người dùng
  1. Phiên đăng nhập từ dòng lệnh của người dùng: cột Tác nhân người dùng hiển thị User-Agent mà công cụ gửi lên khi đổi token (ở đây là ezydocs/1.0 (Node.js)), vì vậy công cụ nên gửi một User-Agent dễ nhận biết. Nút ở cột Các hành động dùng để đăng xuất phiên này.
Sau khi một phiên bị đăng xuất, mọi request dùng token của phiên đó sẽ nhận lỗi 401 và công cụ phải đăng nhập lại.

Lưu ý bảo mật

Đăng nhập từ dòng lệnh trao cho công cụ quyền tương đương với chính tài khoản đã phê duyệt, vì vậy:
  • Chỉ bấm Phê duyệt khi chính bạn vừa chạy lệnh đăng nhập trên máy tính của mình, và tên công cụ trên trang phê duyệt đúng là công cụ bạn đang dùng.
  • Không chia sẻ access token, không đưa token vào mã nguồn hay commit lên kho mã.
  • Thường xuyên xem lại danh sách phiên đăng nhập và đăng xuất các phiên không còn dùng.
  • Chỉ bật đăng nhập từ dòng lệnh cho quản trị viên khi cần, và bật xác thực hai lớp cho các tài khoản quản trị viên.