diff --git a/docs/guides/2026-09-19-dos-id-auto-sso-guide.html b/docs/guides/2026-09-19-dos-id-auto-sso-guide.html new file mode 100644 index 00000000000..77fbf775ce9 --- /dev/null +++ b/docs/guides/2026-09-19-dos-id-auto-sso-guide.html @@ -0,0 +1,188 @@ + + + + + + +Guide DOS ID Auto-SSO - Nhân bản sang Crove Desk / CRM / Sign / Post + + + +
+

Guide: DOS ID Auto-SSO - thay trang login bằng redirect sang id.dos.me

+
Ngày: 2026-09-19  ·  Pattern đã ship: Crove-Cal PR #78 + #79 (đang chạy production cal.crove.com)  ·  Áp dụng cho: Crove Desk, Crove CRM, Crove Sign, Crove Post, và mọi app hệ sinh thái DOS
+
+ +
+
+ Tóm tắt một câu: thôi customize trang login của từng app - middleware chuyển user chưa đăng nhập thẳng sang id.dos.me (đăng nhập + đăng ký ở một nơi), trang login cũ giữ làm đường dự phòng ?direct=1. Mỗi app chỉ cần 3 mảnh: gate ở middleware + trang cầu nối + escape hatch, không đụng UI login nữa. +
+ +

1. Flow hoạt động

+
+User vào /auth/login (chưa đăng nhập)
+        |
+        v
+[MIDDLEWARE] dos-id đã cấu hình (OIDC client id + secret) và KHÔNG có ?direct=1?
+        |  có                              |  không (chưa cấu hình / direct=1)
+        v                                  v
+  302 -> /auth/sso                  Hiện trang login cũ (form email, magic link...)
+        |                           = đường break-glass cho admin
+        v
+[TRANG CẦU NỐI /auth/sso]
+  gọi signIn("dos-id", { callbackUrl })
+  (auth library lo CSRF / state / nonce)
+        |
+        v
+  302 -> id.dos.me (đăng nhập / đăng ký)
+        |
+        v
+  callback về app -> JIT tạo user/org nếu user mới -> vào app
+  
+ +

2. Bốn nguyên tắc bắt buộc (không được bỏ)

+ + + + + + + + +
#Nguyên tắcVì sao
1Luôn đi qua auth library (next-auth sign-in endpoint / tương đương), không tự viết redirect thẳng tới authorize endpoint của IdPState/nonce/CSRF phải do thư viện xử lý. Tự chế = tự mở lỗổng login CSRF.
2Gate redirect theo đúng điều kiện đăng ký provider - chỉ redirect khi OIDC client id + secret thật sự được cấu hìnhRedirect tới provider chưa đăng ký = lỗi "unknown provider" cho mọi user. Ở Crove-Cal, điều kiện middleware mirror y hệt điều kiện if (oidcId && oidcSecret) trong phần đăng ký provider.
3Giữ break-glass: /auth/login?direct=1 (hoặc tương đương) luôn hiện form login cũid.dos.me chết = không ai vào được app. Admin cần đường vào local khi IdP unreachable.
4Logout không được auto-login lại - trang sau logout phải là trang dừng có nút bấm, không tự kích hoạt sign-inSession IdP còn sống mà logout xong bị đẩy ngay vào sign-in = "đăng xuất không được" - bug kinh điển của SSO auto-redirect.
+ +

3. Chuẩn bị cho mỗi app (một lần)

+
    +
  1. Đăng ký OIDC client trên id.dos.me cho app: client_id + client_secret, redirect/callback URL trỏ về app (vd https://desk.crove.com/api/auth/callback/dos-id).
  2. +
  3. Khai báo env cho app: OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_WELL_KNOWN_URL (cùng bộ id.dos.me).
  4. +
  5. Bảo đảm app có sẵn OIDC provider đăng ký theo env (gate nếu thiếu env - xem Crove-Cal packages/features/auth/lib/next-auth-options.ts, hàm DosIdProvider).
  6. +
+
+ Lưu ý env trên Docker: biến env client-side phải nằm trong turbo.json globalEnv (nếu dùng turbo) và trong Dockerfile/compose đúng giai đoạn, nếu không build cache sẽ trả giá trị sai - đây là finding MD-29 Crove-Cal từng dính. +
+ +

4. Ba mảnh code cần thêm (Next.js + next-auth)

+

Tham khảo trực tiếp Crove-Cal PR #78 (đã chạy production):

+ +
+// Mảnh 1 - middleware (rút gọn từ proxy.ts Crove-Cal)
+const isDosIdProviderConfigured = () =>
+  !!((process.env.OIDC_CLIENT_ID || "").trim() &&
+     (process.env.OIDC_CLIENT_SECRET || "").trim());
+
+const shouldRedirectToDosIdSso = (url) =>
+  isDosIdProviderConfigured() &&
+  (url.pathname === "/auth/login" || url.pathname === "/login") &&
+  url.searchParams.get("direct") !== "1";
+
+// trong middleware handler:
+if (shouldRedirectToDosIdSso(url)) {
+  const sso = new URL("/auth/sso", req.url);
+  const cb = url.searchParams.get("callbackUrl");
+  if (cb) sso.searchParams.set("callbackUrl", cb);
+  return NextResponse.redirect(sso);
+}
+  
+
+// Mảnh 2 - trang cầu nối (rút gọn từ sso-view.tsx Crove-Cal)
+useEffect(() => {
+  signIn("dos-id", { callbackUrl, redirect: true }).catch(() => setFailed(true));
+}, [callbackUrl]);
+// UI: failed ? "Không kết nối được DOS.Me ID" : "Đang chuyển tới DOS.Me ID..."
+// + nút fallback: /auth/login?direct=1&callbackUrl=...
+  
+ +

Stack không phải next-auth (Crove CRM, Desk, Sign...)

+

Nguyên tắc y hệt, chỉ đổi tên công cụ: tìm nơi auth library nhận "bắt đầu sign-in với provider X" (một endpoint/hàm chính thức - Laravel: Socialite::driver('dos')->stateless()-KHÔNG, luôn dùng redirect() có state; Passport-SAML/OIDC: authorize URL do thư viện sinh) và gọi nó từ trang cầu nối. Cấm tự nối chuỗi authorize URL bằng tay.

+ +

5. Test bắt buộc trước khi merge

+
    +
  1. Unit test middleware: redirect khi đủ env; KHÔNG redirect khi thiếu env / có direct=1 / path khác login; forward callbackUrl. (Xem 6 test trong apps/web/proxy.test.ts của Crove-Cal.)
  2. +
  3. Chạy app với env đủ: vào /auth/login phải bật qua /auth/sso tới id.dos.me.
  4. +
  5. /auth/login?direct=1 phải hiện form cũ.
  6. +
  7. Logout xong phải dừng ở trang logout, KHÔNG tự vào lại app.
  8. +
  9. User mới (chưa có trong app) đăng nhập lần đầu qua id.dos.me phải được JIT provisioning đúng org/permission.
  10. +
+ +

6. Runbook deploy lên server crove (theo cách Crove-Cal đang chạy)

+
    +
  1. Merge PR -> CI xanh (type-check + lint + test) -> deploy-docker build image :latest mới lên GHCR (sau ci-gate).
  2. +
  3. SSH server: gcloud compute ssh crove-server --zone=asia-southeast1-b --project=crove-os.
  4. +
  5. cd /opt/crove/<app> && docker compose pull <service> && docker compose up -d --no-deps <service>
  6. +
  7. Theo dõi: docker ps --filter name=<app> (chờ healthy) + docker logs <app> --tail 30.
  8. +
  9. Verify ngoài: mở app production, vào /auth/login phải chuyển sang id.dos.me.
  10. +
+
+ Rollback 30 giây khi container crash-loop: tìm image cũ vẫn còn trên server docker images (dangling), gán lại tag rồi recreate: +
docker tag <old-image-id> ghcr.io/dos/<app>:latest
+cd /opt/crove/<app> && docker compose up -d --no-deps <service>
+ Lần 18/09 Crove-Cal crash-loop vì permission (xem mục 7) - rollback bằng đúng lệnh này, prod sống lại ngay khi chờ image fix. +
+ +

7. Bài học từ lần đầu ship (đọc trước khi deploy app kế tiếp)

+ + +

8. Liên kết tham chiếu trong Crove-Cal

+ + + + + + + + + + +
Thành phầnVị trí
Middleware gate + redirectapps/web/proxy.ts (hàm shouldRedirectToDosIdSso)
Trang cầu nốiapps/web/modules/auth/sso-view.tsx + app/(use-page-wrapper)/auth/sso/page.tsx
Điều kiện đăng ký provider (middleware phải mirror cái này)packages/features/auth/lib/next-auth-options.ts (hàm DosIdProvider)
Unit testapps/web/proxy.test.ts (describe "DOS ID auto-SSO redirect")
i18n keyspackages/i18n/locales/en/common.json: sso_redirecting_title, sso_redirecting_body, sso_redirect_failed, sso_use_other_login
PR tham chiếu#78 (feature) + #79 (hotfix turbo cache dir)
+ +
+ Định nghĩa xong với mỗi app: (1) OIDC client trên id.dos.me + env cấu hình, (2) 3 mảnh code theo mục 4, (3) 5 bài test mục 5 pass, (4) deploy theo runbook mục 6 kèm verify production, (5) ghi chú lại bất kỳ bài học mới vào guide này (đặt file guide trong repo của app đó). +
+
+ + + +