เซสชันและการยืนยันตัวตนฝั่ง frontend (Auth session & tokens)
เบราว์เซอร์ถือแค่ cookie เซสชันที่เซ็นลายเซ็นและ httpOnly; JWT access/refresh token ของ backend ถูกเก็บ "ใน cookie เซสชันนั้น" และ Next.js server (route handlers) เป็นคนแนบ Authorization: Bearer ให้เวลา proxy ไป Go backend
ฝั่งเบราว์เซอร์ไม่เห็น token โดยตรง
ทำงานอย่างไร
Cookie เซสชัน (src/lib/session.ts)
- ชื่อ cookie
sml_ai_session(session-name.ts:6). ค่าเป็นbase64(data).signatureเซ็นด้วย HMAC-SHA256 (createHmac, บรรทัด ~44) และตรวจด้วยcrypto.timingSafeEqual(บรรทัด 67) - ถ้าลายเซ็นไม่ตรงถือว่าไม่มี session. - Idle timeout 30 นาที:
IDLE_TIMEOUT_MS = 30*60*1000(บรรทัด 24);decodeSessionคืน null ถ้าDate.now() - lastActivityเกิน (บรรทัด 74). แบบ sliding: ทุก request ที่ผ่านrequireSessionเขียน cookie ใหม่พร้อมlastActivity(touchSessionบรรทัด 251,maxAgeบรรทัด 246). - ตัวเลือก cookie:
httpOnly: true,sameSite: "lax",secureเฉพาะ production (บรรทัด 82-84). - API ของโมดูล:
getSession()(212),setSession()(234),touchSession()(251),clearSession()(262),encodeSession/decodeSession(49/56). ทั้งหมดเป็น server-only (ใช้ใน layout/route handler).
ข้อมูลใน session (src/types/auth.ts:44-76, SessionData): provider, userCode, userName, userLevel, mode ("user"|"admin"), email?, role? ("ADMIN"|"MEMBER"), mappedUserCode?, isDemo?, dataGroup?, database?, databaseName?, loginAt, lastActivity, accessToken?, refreshToken?, preAuthToken?, availableCompanies?.
preAuthToken+availableCompanies= ช่วงรอเลือกบริษัท (ผู้ใช้ที่มีหลาย Company) แล้วเรียก/auth/select-companyต่อ (api/auth/login/route.ts:82-90,api/auth/databases/route.ts:208).- ฝั่ง UI ถือว่าเป็นผู้ดูแลถ้า
session.isDemo || role === "ADMIN" || userLevel >= 9(TabWorkspace.tsx:197,api-server.ts:271).
ทางเข้า (route handlers ใน src/app/api/auth/)
| ทาง | ไฟล์ | หลักการ |
|---|---|---|
| user/admin + รหัสผ่าน | login/route.ts |
เรียก backend /auth/login; โหมด admin ต้อง userLevel >= 9 (login/route.ts:135-141,275); บัญชีสาธิตสาธารณะ ADMIN/SUPERADMIN ห้ามเข้าโหมด admin (บรรทัด 21-30) |
| Demo | demo/route.ts:11-15 |
getOrMintSmlDemoSession(true) แล้ว setSession (ฐาน smldemo) |
google/route.ts |
รับ idToken (POST) หรือ redirect OAuth (GET) แล้วเรียก backend workspace google-login ด้วยหัว X-Admin-Api-Key (บรรทัด 27-35,152); state OAuth เก็บ cookie httpOnly สั้นๆ sml_google_oauth_state ที่ path /api/auth/google (oauth-state.ts:7) เทียบแบบ constant-time |
|
| เลือกฐาน/บริษัท | databases/route.ts |
`/api/v1/admin/workspace/databases |
| ดู session | session/route.ts:6-11 |
คืนข้อมูล session หรือ 401 NO_SESSION |
| ออก | logout/route.ts:17 |
เรียก backend /auth/logout แล้ว clearSession |
| เปลี่ยนรหัสผ่าน | change-password/route.ts |
ตรวจรหัสเดิมด้วย /auth/login แล้ว PATCH /api/v1/admin/users |
Token refresh แบบกันชนกัน (src/lib/api-server.ts)
rotateRefreshToken(บรรทัด 88-150) เรียก/auth/refreshแบบ single-flight:inFlightRefresh(บรรทัด 104) รวมหลาย request ที่ใช้ refresh token ตัวเดียวกัน, และrecentRefreshCacheเก็บคู่ token ใหม่ 60 วินาที (RECENT_REFRESH_TTL_MS, บรรทัด 87) เพราะ backend หมุน refresh token ทุกครั้ง (ใช้ซ้ำไม่ได้).getValidAccessToken(555) ตรวจอายุ access token และ refresh ล่วงหน้าถ้าเหลือน้อยกว่า 1 นาที (บรรทัด 586).fetchServiceWithAuthRetry(388): ถ้า endpoint มี/adminแนบX-Admin-Api-Key(จากgetAdminApiKey()ใน env; ไม่มี ->503 NO_ADMIN_API_KEY), retry สูงสุด 3 ครั้ง, ถ้า 401 จะหมุน token แล้วลองใหม่ (บรรทัด 454-458).proxyToService(496) ห่อผลลัพธ์ให้เป็นรูป{ ok, data|error, code }เสมอ; 401 ->SESSION_EXPIRED(เส้นทาง API ฝั่ง Next.js ที่ส่งต่อไป Go backend (API proxy routes)).
ตัวกันสิทธิ์ใน route handler (api-server.ts)
requireSession()(248) - ต้องมี session (ไม่งั้น 401) และเรียกgetValidAccessToken+ ต่ออายุlastActivity.requireAdmin()(263) -userLevel >= 9 || role === "ADMIN"; โหมด demo (ฐาน smldemo) ถูกบล็อก 403.requireSystemAdmin()(359) /requireSystemAdminView()(311) - โซนผู้ดูแลส่วนกลาง;isDemoCentralViewer(301) ให้ผู้ใช้ demo "ดูได้อย่างเดียว" และdemoSafe(345) กรองผลให้เห็นเฉพาะแถวของ smldemo (หน้าจอผู้ดูแลระบบส่วนกลาง (Admin Console)).
ฝั่งเบราว์เซอร์เมื่อ session หมด: PersistentWorkspaceHost patch fetch ดัก 401 แล้วพาไป /login?returnTo= (โครงหน้าเว็บและการนำทาง (App shell & routing)). ตัว client api-client.ts คืน ApiResult<T> ไม่ throw (ตัวเรียก API และตัวประกอบ SQL ฝั่งเบราว์เซอร์ (api-client, query-client, sml-query-builder)).
ข้อควรระวัง
- ห้ามใส่ค่าลับ (รหัส,
ADMIN_API_KEY, token) ในโค้ด/โน้ต; อ่านจาก env เท่านั้น. - proxy.ts ตรวจแค่ "มี cookie" - การตรวจลายเซ็น/หมดอายุจริงเกิดตอน
getSession(). - สิทธิ์จอ (
ScreenRights) ฝั่ง UI เป็นแค่ UX guard backend ตรวจซ้ำ (พื้นที่ทำงานแบบแท็บ MDI และสิทธิ์จอ (TabWorkspace)).
ที่มาในโค้ด
frontend/src/lib/session.ts:15-84,212-262- cookie, HMAC, idle timeoutfrontend/src/types/auth.ts:44-76- SessionDatafrontend/src/lib/api-server.ts:87-150,248-300,388-458,496-553,555-612- refresh, guards, proxyfrontend/src/app/api/auth/*/route.ts- ทางเข้า/ออกfrontend/src/app/api/auth/google/oauth-state.ts:7- OAuth state cookie
ดูเพิ่ม
ภาพรวม Frontend ของ SML Ai Account (Frontend overview) · หน้าเข้าสู่ระบบและเลือกฐานข้อมูล (Login & select database) · เส้นทาง API ฝั่ง Next.js ที่ส่งต่อไป Go backend (API proxy routes) · โครงหน้าเว็บและการนำทาง (App shell & routing) · หน้าจอผู้ดูแลระบบส่วนกลาง (Admin Console) · สารบัญมาตรฐานและกฎเหล็กของ SML Ai Account (Standards Overview)