ทำไมถูกเด้งออกจากระบบ / ขึ้น "หมดเวลาใช้งาน"
ตอบสั้น: มี 2 ชั้นซ้อนกัน — (1) cookie เซสชันของ Next (sml_ai_session) หมดเมื่อไม่ได้ใช้งานเกิน 30 นาที (sliding) และ (2) JWT ของ backend: access token อายุ 15 นาที (หมุนต่ออัตโนมัติด้วย refresh token อายุ 7 วัน) ถ้าอย่างใดอย่างหนึ่งใช้ไม่ได้ คำขอจะได้ 401 และหน้าเว็บพาไป /login?returnTo=<หน้าเดิม> ให้เข้าสู่ระบบใหม่แล้วกลับมาหน้าเดิม
รายละเอียด
ฝั่ง Next (cookie): decodeSession คืน null ถ้า now - lastActivity > 30 นาที (session.ts:24, ตรวจที่ :74) ทุกคำขอที่ผ่าน requireSession เขียน cookie ใหม่พร้อม lastActivity (api-server.ts:248) จึงเป็นแบบ sliding ไม่มี session → ตอบ 401 รหัส NO_SESSION ข้อความ "หมดเวลาใช้งาน กรุณาเข้าสู่ระบบใหม่"
ฝั่ง backend (JWT, HS256): JWT_SIGNING_KEY เซ็น 3 ชนิด — preauth (PRE_AUTH_TOKEN_TTL_MINUTES ค่าเริ่ม 5), access (ACCESS_TOKEN_TTL_MINUTES 15), refresh (REFRESH_TOKEN_TTL_HOURS 168) ค่าเริ่มต้นอยู่ที่ config.go:42-44 access และ refresh ที่ออกคู่กันใช้ jti เดียวกัน VerifyAccessToken (jwt_token_service.go:150) ตรวจลายเซ็น หมดอายุ และ IsRevoked(jti) ถ้าไม่ผ่าน → 401 "missing or invalid access token" (middleware/auth.go:39)
การต่ออายุอัตโนมัติ: getValidAccessToken (api-server.ts:555) เรียก /auth/refresh ล่วงหน้าเมื่อ access token เหลือน้อยกว่า 1 นาที Service.Refresh (service.go:254) ตรวจ refresh token → verifySession ตรวจซ้ำว่าผู้ใช้ยังมีอยู่/ใช้งานได้ และยังเข้าบริษัทนั้นได้ → เพิกถอน jti เก่า → ออกคู่ใหม่ (ค่า level ถูกอ่านใหม่ตรงนี้) Next รวมคำขอ refresh ที่ชนกันเป็นรอบเดียว (rotateRefreshToken, api-server.ts:89, แคชผล 60 วินาที) เพราะ refresh token ใช้ซ้ำไม่ได้
ออกจากระบบ: Logout (service.go:287) เพิกถอน jti → access token เดิมใช้ไม่ได้ทันที (ไม่ต้องรอ 15 นาที) ส่วนฝั่งเว็บเรียก /api/auth/logout แล้วล้าง cookie
เมื่อใดบังคับเข้าสู่ระบบใหม่:
| สถานการณ์ | สาเหตุในโค้ด |
|---|---|
| ไม่ได้ใช้งานเกิน 30 นาที | cookie idle (session.ts:74) |
| ไม่มีการต่ออายุนานเกิน 7 วัน | refresh token หมดอายุ (ทุกครั้งที่ refresh ได้ refresh token ใหม่อายุ 7 วันอีกครั้ง) |
ผู้ดูแลปิดผู้ใช้ (ActiveStatus ไม่ใช่ใช้งาน) หรือถอนสิทธิ์เข้าบริษัท |
verifySession ตอบ "session is no longer valid" ตอน refresh (service.go:208, :222) |
เปลี่ยน JWT_SIGNING_KEY |
token เก่าทั้งหมดตรวจลายเซ็นไม่ผ่าน |
| Logout จากเครื่องอื่น | jti ถูกเพิกถอน |
ฝั่งหน้าจอ: PersistentWorkspaceHost ดัก fetch ที่ได้ 401 (:279) เก็บ returnTo ลง sessionStorage แล้วแสดงแถบ/ปุ่มให้เข้าสู่ระบบใหม่หรือพาไป /login?returnTo= — งานที่ยังไม่บันทึกบนจอไม่ถูกเก็บให้อัตโนมัติ
ข้อควรระวัง
- สิทธิ์เมนูไม่ได้ฝังใน token โหลดสดทุกคำขอ จึงเปลี่ยนสิทธิ์แล้วมีผลทันที แต่
levelใน token เปลี่ยนเฉพาะตอน refresh (ทำไมเข้าเมนู/กดบันทึกแล้วขึ้น 403 ไม่มีสิทธิ์ และตั้งสิทธิ์เมนูให้ผู้ใช้อย่างไร) - อย่าแก้ปัญหา "เด้งออกบ่อย" ด้วยการยืด TTL โดยไม่ดู idle 30 นาทีของ cookie ซึ่งเป็นตัวที่ตัดก่อน
- ถ้า backend ไม่ได้ตั้ง
JWT_SIGNING_KEYทุกการออก/ตรวจ token ล้มเหลวแบบ fail-closed (ErrSigningKeyNotConfigured) — login ไม่ผ่านเลย ไม่ใช่เด้งออก - บัญชีสาธิต (demo) ไม่ผ่านการ refresh ผ่าน backend (
!session.isDemo,api-server.ts:588)
ดูเพิ่ม
- JWT session token และการเพิกถอน (JWT + revocation store)
- เซสชันและการยืนยันตัวตนฝั่ง frontend (Auth session & tokens)
- ขั้นตอน login / เลือกบริษัท / refresh / logout (Identity login flow)
- บทเรียนตัวตนผู้เช่า การล็อกอิน และ session (Tenant, Auth & Sessions)
- ระบบแยกบริษัท/ฐานข้อมูล (tenant, provider, company) อย่างไร และเลือกบริษัทตอน login อย่างไร
- ADR-0004 การเพิกถอน session ตอน logout (RevocationStore)
ที่มาในโค้ด
backend/internal/adapter/outbound/identity/jwt_token_service.go:150VerifyAccessToken(เช็กIsRevokedบรรทัด 162) /:184VerifyRefreshTokenbackend/internal/adapter/inbound/http/middleware/auth.go:39Authตอบ 401 เมื่อ token ไม่ถูกต้องbackend/internal/application/identity/service.go:254Refresh,:287Logout,:194verifySessionbackend/internal/platform/config/config.go:42ค่าเริ่มต้น TTL ของ preauth/access/refreshfrontend/src/lib/session.ts:24IDLE_TIMEOUT_MS(30 นาที)frontend/src/lib/api-server.ts:89rotateRefreshToken/:248requireSession/:555getValidAccessTokenfrontend/shared/layout/PersistentWorkspaceHost.tsx:279ดัก 401 แล้วพาไป/login?returnTo=