# Prompt: แก้ไข Auth.js ClientFetchError และเพิ่มระบบ Session Inactivity Timeout 5 นาที ## บทบาทของคุณ คุณคือ Senior Full-Stack Developer ที่มีความเชี่ยวชาญด้าน: - Next.js 16 App Router - React - TypeScript - Auth.js / NextAuth - Middleware - Session Management - Security และ Authentication Flow - TanStack Query หรือระบบ Client State ที่มีอยู่ในโครงการ - Next.js Turbopack ให้ตรวจสอบโครงสร้างและโค้ดเดิมของโครงการก่อนดำเนินการแก้ไข โดยต้องปรับปรุงจากโครงสร้างที่มีอยู่จริง ห้ามสร้างระบบ Authentication ซ้ำซ้อนโดยไม่จำเป็น --- # ปัญหาที่ต้องแก้ไข ระบบพบ Error ฝั่ง Client ดังนี้: ```text Error Type: Console ClientFetchError Error Message: Unexpected token '<', " ``` ซึ่งอาจเกิดจาก: - Auth.js Route ไม่ทำงานหรืออยู่ผิดตำแหน่ง - `/api/auth/session` ตอบกลับเป็น 404 หรือ 500 - Middleware Redirect `/api/auth/*` ไปหน้าล็อกอิน - Auth.js Configuration Error - Database หรือ Adapter Error - Environment Variable ไม่ครบหรือไม่ถูกต้อง - Custom Error Handler ส่ง HTML แทน JSON - Reverse Proxy หรือ Application Redirect ทำให้ Auth API ถูกเปลี่ยนปลายทาง - Client เรียก `getSession()` หรือ `useSession()` ขณะที่ Auth Route ล้มเหลว - SessionProvider ถูกตั้งค่าผิดตำแหน่ง - Error Boundary หรือ Route Handler จัดการ Error ไม่ถูกต้อง --- # เป้าหมายหลัก ดำเนินการให้ครบทั้ง 2 ส่วนดังนี้: 1. ตรวจสอบและแก้ไข Auth.js `ClientFetchError` 2. เพิ่มระบบ Session Inactivity Timeout เมื่อผู้ใช้งานไม่มีการเคลื่อนไหวต่อเนื่อง 5 นาที --- # ส่วนที่ 1: ตรวจสอบและแก้ไข Auth.js ClientFetchError ## 1. ตรวจสอบโครงสร้าง Auth.js ปัจจุบัน ตรวจสอบไฟล์ที่เกี่ยวข้องทั้งหมด เช่น: ```text auth.ts src/auth.ts app/api/auth/[...nextauth]/route.ts src/app/api/auth/[...nextauth]/route.ts middleware.ts src/middleware.ts proxy.ts src/proxy.ts providers.tsx session-provider.tsx layout.tsx .env .env.local next.config.ts ``` รวมถึงไฟล์ Authentication, Authorization และ Database Adapter ที่เกี่ยวข้อง ห้ามสมมติชื่อไฟล์หรือโครงสร้าง ให้ค้นหาจากโครงการจริงก่อน --- ## 2. ตรวจสอบ Auth Route ตรวจสอบว่า Auth.js Route อยู่ในตำแหน่งที่ถูกต้องสำหรับ App Router เช่น: ```text src/app/api/auth/[...nextauth]/route.ts ``` และ Export Handler ถูกต้อง เช่น: ```ts import { handlers } from "@/auth"; export const { GET, POST } = handlers; ``` ตรวจสอบเพิ่มเติมว่า: - Import path ถูกต้อง - `handlers` ถูก Export จาก Auth configuration จริง - ไม่มี Circular Dependency - ไม่มี Route อื่นชนกับ `/api/auth/[...nextauth]` - ไม่มี Rewrite หรือ Redirect ที่กระทบ Auth Route --- ## 3. ตรวจสอบ Auth.js Configuration ตรวจสอบไฟล์ Auth configuration ว่ามีรูปแบบถูกต้อง เช่น: ```ts import NextAuth from "next-auth"; export const { handlers, auth, signIn, signOut } = NextAuth({ providers: [], }); ``` ตรวจสอบ: - Providers - Credentials Provider - Adapter - Session Strategy - JWT Callback - Session Callback - Authorized Callback - Sign-in Page - Error Page - Environment Variables - Database Connection - Cookie Configuration - Trust Host Configuration - Base Path หากมีการตั้งค่า หากมี Custom Adapter หรือ LDAP/Active Directory ให้ตรวจสอบ Error Handling ของส่วนนั้นด้วย --- ## 4. ตรวจสอบ Endpoint `/api/auth/session` ทดสอบ Endpoint นี้โดยตรง: ```text GET /api/auth/session ``` ผลลัพธ์ที่ถูกต้องต้องเป็น JSON และมี `Content-Type` เป็น: ```text application/json ``` ตัวอย่างผลลัพธ์เมื่อยังไม่ได้เข้าสู่ระบบ: ```json {} ``` หรือ Response ตามมาตรฐานของ Auth.js ต้องไม่ตอบกลับเป็น: - HTML - หน้า Login - หน้า 404 - หน้า Error ของ Next.js - Redirect ไปหน้าอื่น - Reverse Proxy Error Page ให้ตรวจสอบ HTTP Status เช่น: ```text 200 302 307 401 404 500 ``` และหาสาเหตุจาก Status ที่พบจริง --- ## 5. ตรวจสอบ Middleware หรือ Proxy ตรวจสอบว่า Middleware หรือ Proxy ไม่ได้ดัก Route ต่อไปนี้: ```text /api/auth /api/auth/* /_next/* /favicon.ico ``` Auth.js API Route ต้องไม่ถูก Redirect ไปหน้าล็อกอิน ปรับ Matcher ให้ยกเว้น Auth API และ Static Assets อย่างเหมาะสม เช่นแนวทาง: ```ts export const config = { matcher: ["/((?!api/auth|_next/static|_next/image|favicon.ico).*)"], }; ``` แต่ต้องปรับให้เข้ากับโครงสร้างจริงของโครงการ และต้องไม่ทำให้ Route Protection เดิมเสียหาย หากโครงการใช้ `proxy.ts` ตามโครงสร้างของ Next.js รุ่นปัจจุบัน ให้ตรวจสอบไฟล์นั้นด้วย ไม่จำกัดเฉพาะ `middleware.ts` --- ## 6. ตรวจสอบ Environment Variables ตรวจสอบ Environment Variables ที่เกี่ยวข้อง เช่น: ```text AUTH_SECRET AUTH_URL NEXTAUTH_SECRET NEXTAUTH_URL DATABASE_URL ``` รวมถึง Environment Variables ของ: - LDAP - Active Directory - OAuth Provider - Database Adapter - Reverse Proxy - Internal API ให้ใช้ชื่อตัวแปรตาม Version และโครงสร้าง Auth.js ที่โครงการใช้งานจริง ห้ามเปิดเผย Secret ใน Log หรือ Commit หาก Environment Variable ที่จำเป็นขาด ให้เพิ่มตัวอย่างใน: ```text .env.example ``` โดยใช้ Placeholder เท่านั้น --- ## 7. ตรวจสอบ Server Error ที่แท้จริง ตรวจสอบ Server Log และ Terminal Log ขณะที่เรียก: ```text /api/auth/session ``` ค้นหา Root Cause เช่น: - Database Connection Error - Prisma Initialization Error - LDAP Connection Error - Provider Configuration Error - Invalid Secret - Callback Error - JWT Decryption Error - Cookie Parsing Error - Runtime Compatibility Error - Edge Runtime ไม่รองรับ Library บางตัว - Node Runtime Configuration Error ห้ามแก้ด้วยการซ่อน Error ฝั่ง Client เพียงอย่างเดียว ต้องแก้ Root Cause ที่ทำให้ Endpoint ส่ง HTML --- ## 8. ปรับปรุง Error Handling ปรับปรุง Error Handling เพื่อให้: - Auth API ไม่ส่ง HTML Response โดยไม่จำเป็น - Client ไม่เกิด Unhandled Promise Rejection - ไม่แสดง Stack Trace หรือข้อมูล Sensitive แก่ผู้ใช้งาน - Server Log มีข้อมูลเพียงพอสำหรับ Debug - Production แสดงข้อความที่เหมาะสม - Development ยังสามารถตรวจสอบ Root Cause ได้ หาก `useSession()`, `getSession()`, `SessionProvider` หรือ Custom API Client มี Error Handling ไม่ครบ ให้ปรับปรุงด้วย ห้ามใช้วิธีแก้แบบ: ```ts try { // ... } catch { return null; } ``` หากทำให้ Root Cause ถูกซ่อนโดยไม่มี Logging ที่เหมาะสม --- # ส่วนที่ 2: เพิ่มระบบ Session Inactivity Timeout 5 นาที ## ความต้องการ เมื่อผู้ใช้งานเข้าสู่ระบบแล้ว หากไม่มีการเคลื่อนไหวหรือไม่มี Interaction กับระบบต่อเนื่องเป็นเวลา: ```text 5 นาที ``` ให้ระบบดำเนินการดังนี้: 1. แสดงข้อความแจ้งผู้ใช้งานว่า: ```text คุณไม่ได้ใช้งานระบบเป็นเวลานาน เซสชันของคุณหมดอายุแล้ว กรุณาเข้าสู่ระบบใหม่ ``` 2. ทำการ Sign out จาก Auth.js อย่างถูกต้อง 3. ล้าง Client State หรือข้อมูล Session ที่เกี่ยวข้อง 4. Redirect กลับไปหน้าล็อกอิน 5. ผู้ใช้งานต้องเข้าสู่ระบบใหม่ก่อนใช้งานระบบต่อ 6. ห้ามเกิด Redirect Loop 7. ห้ามกระทบผู้ใช้งานที่ยังมีการเคลื่อนไหวอยู่ --- ## 1. นิยาม Activity ของผู้ใช้งาน ให้ถือว่า Event ต่อไปนี้เป็น Activity: ```text mousedown mousemove keydown scroll touchstart click focus ``` สามารถเลือกใช้เฉพาะ Event ที่เหมาะสมเพื่อไม่ให้เกิด Performance Issue ต้องใช้ Event แบบ Throttle หรือ Debounce เพื่อไม่ให้ Reset Timer ถี่เกินไป โดยเฉพาะ: ```text mousemove scroll ``` --- ## 2. เริ่มตรวจจับเฉพาะผู้ที่เข้าสู่ระบบแล้ว ระบบ Inactivity Timeout ต้องทำงานเฉพาะเมื่อ: ```ts status === "authenticated"; ``` ห้ามเริ่ม Timer ในกรณี: ```ts status === "loading"; status === "unauthenticated"; ``` เมื่อผู้ใช้งาน Logout แล้ว ต้อง Cleanup: - Timer - Event Listener - Storage Listener - Broadcast Channel - Pending Callback ทั้งหมดอย่างถูกต้อง --- ## 3. ระยะเวลา Timeout กำหนดค่ากลาง เช่น: ```ts const INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000; ``` ควรจัดเก็บไว้ใน Configuration หรือ Constant กลาง เพื่อให้เปลี่ยนภายหลังได้ง่าย ห้ามกระจายค่า `300000` ไว้หลายจุดในระบบ --- ## 4. รูปแบบการแจ้งเตือน เมื่อครบ 5 นาที ให้แสดง Dialog, AlertDialog, Modal หรือ Toast ตาม Component มาตรฐานของโครงการ แนะนำให้ใช้: ```text AlertDialog ``` ข้อความ: ```text เซสชันหมดอายุ ``` รายละเอียด: ```text คุณไม่ได้ใช้งานระบบเป็นเวลานาน เซสชันของคุณหมดอายุแล้ว กรุณาเข้าสู่ระบบใหม่ ``` ปุ่ม: ```text เข้าสู่ระบบใหม่ ``` เมื่อผู้ใช้งานกดปุ่ม ให้ Redirect ไปหน้าล็อกอิน อย่างไรก็ตาม เพื่อความปลอดภัย ระบบต้อง Sign out และถือว่า Session หมดอายุทันทีเมื่อครบเวลา ไม่ควรรอให้ผู้ใช้งานกดปุ่มก่อนจึงค่อย Sign out หากใช้ Toast ให้ตั้งระยะเวลาที่ผู้ใช้งานสามารถอ่านข้อความได้อย่างเหมาะสมก่อน Redirect ห้ามใช้ Native Browser Alert หากโครงการมี UI Component มาตรฐานอยู่แล้ว --- ## 5. ลำดับการทำงานเมื่อหมดเวลา ลำดับที่ต้องการ: ```text ครบเวลาไม่มี Activity ↓ ป้องกันการทำงานซ้ำด้วย Flag ↓ หยุด Timer และถอด Event Listener ↓ แสดงข้อความแจ้ง Session หมดอายุ ↓ ล้างข้อมูล Client State ที่มีความ Sensitive ↓ เรียก signOut ของ Auth.js ↓ Redirect ไปหน้า Login ``` ตัวอย่างแนวทาง: ```ts await signOut({ redirect: false, }); router.replace("/login?reason=inactivity"); router.refresh(); ``` ให้ปรับตาม Auth.js API และโครงสร้างจริงของโครงการ หากจำเป็นต้องแสดงข้อความหลัง Redirect ให้ส่ง Query Parameter เช่น: ```text /login?reason=inactivity ``` จากนั้นหน้า Login แสดงข้อความ: ```text คุณไม่ได้ใช้งานระบบเป็นเวลานาน เซสชันของคุณหมดอายุแล้ว กรุณาเข้าสู่ระบบใหม่ ``` ต้องลบหรือ Replace URL อย่างเหมาะสมหลังแสดงข้อความ เพื่อไม่ให้ข้อความแสดงซ้ำเมื่อ Refresh โดยไม่จำเป็น --- ## 6. รองรับหลาย Tab ระบบควรรองรับกรณีเปิดหลาย Browser Tab เมื่อผู้ใช้งานมี Activity ใน Tab หนึ่ง ควร Sync `lastActivity` ให้ Tab อื่นรับรู้ เพื่อไม่ให้ Tab อื่น Logout ผู้ใช้งานผิดพลาด สามารถใช้: ```text localStorage storage event BroadcastChannel ``` แนวทางที่แนะนำ: ```text auth:last-activity auth:session-expired ``` ข้อกำหนด: - Activity จาก Tab หนึ่งต้องอัปเดตเวลาให้ Tab อื่น - เมื่อ Session หมดอายุใน Tab หนึ่ง Tab อื่นต้อง Logout ตาม - ป้องกันหลาย Tab เรียก `signOut()` พร้อมกันจำนวนมาก - Cleanup Channel และ Listener เมื่อ Component Unmount - ต้องตรวจสอบการทำงานใน Browser ที่ไม่รองรับ `BroadcastChannel` --- ## 7. ห้ามใช้เฉพาะ Client Timer เป็น Security Control Client-side inactivity timer เป็นส่วนของ UX เท่านั้น ต้องตรวจสอบ Session Configuration ฝั่ง Server ร่วมด้วย เช่น: - JWT expiration - Session maxAge - Cookie expiration - Server-side authorization - Permission validation ใน Protected Route - API Route ต้องตรวจ Session ทุกครั้ง ห้ามถือว่าผู้ใช้หมดสิทธิ์เพียงเพราะ Client Redirect แล้วเท่านั้น อย่างไรก็ตาม ระยะเวลาหมดอายุฝั่ง Server ไม่จำเป็นต้องเท่ากับ Inactivity Timeout 5 นาที หากระบบต้องรองรับ Sliding Session แต่ต้องอธิบายความแตกต่างไว้ใน Implementation Report --- ## 8. ระวังการสูญหายของข้อมูลใน Form ตรวจสอบว่าหน้าใดมี Form ที่ผู้ใช้งานอาจกำลังกรอกข้อมูล เมื่อ Session หมดอายุ: - ต้องไม่พยายามบันทึกข้อมูลหลังหมด Session - ต้องไม่เกิด API Request ด้วย Session ที่หมดอายุ - ต้องไม่เก็บข้อมูล Sensitive ไว้ใน Local Storage โดยไม่จำเป็น - สามารถแจ้งข้อความว่าข้อมูลที่ยังไม่ได้บันทึกอาจสูญหายได้ หากเหมาะสม ไม่ต้องเพิ่มระบบ Auto Save เว้นแต่โครงการมีอยู่แล้ว --- # สถาปัตยกรรมที่แนะนำ สร้าง Component หรือ Hook กลาง เช่น: ```text src/components/auth/session-inactivity-guard.tsx ``` หรือ: ```text src/hooks/use-session-inactivity.ts ``` แล้วนำไปติดตั้งในตำแหน่งที่ครอบคลุมเฉพาะ Protected Area เช่น: ```text src/app/dashboard/layout.tsx ``` หรือ Protected Layout ที่มีอยู่จริง ไม่ควรติดตั้งใน Root Layout หากทำให้หน้า Login เริ่มจับเวลาโดยไม่จำเป็น ตัวอย่างโครงสร้าง: ```text Protected Layout ├── SessionProvider ├── SessionInactivityGuard └── Protected Page Content ``` ให้ใช้ Provider และ Layout เดิมของโครงการ หากมีอยู่แล้ว --- # ข้อกำหนดด้าน UX ## ข้อความภาษาไทย หัวข้อ: ```text เซสชันหมดอายุ ``` รายละเอียด: ```text คุณไม่ได้ใช้งานระบบเป็นเวลานาน เซสชันของคุณหมดอายุแล้ว กรุณาเข้าสู่ระบบใหม่ ``` ปุ่ม: ```text เข้าสู่ระบบใหม่ ``` กรณี Redirect ไปหน้า Login โดยตรง ให้หน้า Login แสดงข้อความ: ```text คุณไม่ได้ใช้งานระบบเป็นเวลานาน กรุณาเข้าสู่ระบบใหม่ ``` --- # ข้อกำหนดด้านความปลอดภัย - ห้าม Log Token - ห้าม Log Cookie - ห้าม Log Password - ห้าม Log LDAP Credential - ห้ามเปิดเผย Environment Secret - ห้ามเก็บ Access Token ใน Local Storage หากระบบเดิมไม่ได้ออกแบบเช่นนั้น - API และ Server Action ต้องตรวจ Session ฝั่ง Server - Protected Route ต้องไม่พึ่ง Client Guard เพียงอย่างเดียว - ป้องกัน Open Redirect - Redirect URL ต้องเป็น Internal Path ที่กำหนดไว้ - ป้องกันการเรียก Logout ซ้ำ - ป้องกัน Redirect Loop ระหว่าง Login และ Protected Route --- # Test Cases ที่ต้องตรวจสอบ ## Auth.js Error ### TC-AUTH-001 เปิด: ```text /api/auth/session ``` ผลลัพธ์ที่คาดหวัง: - HTTP Status ถูกต้อง - Response เป็น JSON - `Content-Type` เป็น JSON - ไม่มี HTML Response ### TC-AUTH-002 เปิดหน้า Protected Route ขณะยังไม่เข้าสู่ระบบ ผลลัพธ์ที่คาดหวัง: - Redirect ไปหน้า Login อย่างถูกต้อง - `/api/auth/session` ไม่ถูก Middleware Redirect - ไม่เกิด `Unexpected token '<'` ### TC-AUTH-003 เข้าสู่ระบบสำเร็จ ผลลัพธ์ที่คาดหวัง: - `useSession()` ได้สถานะ `authenticated` - ไม่มี `ClientFetchError` - Session โหลดได้ตามปกติ ### TC-AUTH-004 Database หรือ LDAP ไม่พร้อมใช้งาน ผลลัพธ์ที่คาดหวัง: - Server มี Error Log ที่เหมาะสม - Client ไม่เกิด Unhandled Error - ผู้ใช้งานได้รับข้อความที่เหมาะสม - ไม่มีข้อมูล Sensitive ถูกเปิดเผย --- ## Session Inactivity Timeout ### TC-IDLE-001 เข้าสู่ระบบและไม่มี Activity 5 นาที ผลลัพธ์ที่คาดหวัง: - แสดงข้อความ Session หมดอายุ - Sign out สำเร็จ - Redirect ไปหน้า Login - ต้อง Login ใหม่ ### TC-IDLE-002 มี Activity ก่อนครบ 5 นาที ผลลัพธ์ที่คาดหวัง: - Timer ถูก Reset - ผู้ใช้งานไม่ถูก Logout ### TC-IDLE-003 มี Activity ต่อเนื่อง ผลลัพธ์ที่คาดหวัง: - ระบบไม่ Logout - ไม่มี Memory Leak - ไม่มี Timer ถูกสร้างซ้ำ ### TC-IDLE-004 เปิดระบบหลาย Tab และใช้งานอยู่ใน Tab หนึ่ง ผลลัพธ์ที่คาดหวัง: - Activity Sync ไปยัง Tab อื่น - Tab อื่นไม่ Logout ผู้ใช้งานผิดพลาด ### TC-IDLE-005 Session หมดอายุใน Tab หนึ่ง ผลลัพธ์ที่คาดหวัง: - ทุก Tab รับรู้ว่า Session หมดอายุ - ทุก Tab กลับหน้า Login - ไม่เรียก Sign out ซ้ำอย่างผิดปกติ ### TC-IDLE-006 Logout ด้วยตนเองก่อนครบเวลา ผลลัพธ์ที่คาดหวัง: - Timer และ Event Listener ถูก Cleanup - ไม่แสดงข้อความ Inactivity ภายหลัง ### TC-IDLE-007 Refresh หน้าใกล้ครบเวลา ผลลัพธ์ที่คาดหวัง: - ระบบอ่าน `lastActivity` ที่เหมาะสม - ไม่ Reset Timeout อย่างไม่ถูกต้อง หากไม่มี Activity จริง - ไม่ Logout ซ้ำ ### TC-IDLE-008 อยู่หน้า Login ผลลัพธ์ที่คาดหวัง: - ไม่มี Inactivity Timer ทำงาน - ไม่มี Redirect Loop - ไม่มีข้อความหมดอายุแสดงซ้ำโดยไม่จำเป็น ### TC-IDLE-009 ผู้ใช้งานกำลังกรอก Form แต่ไม่มี Mouse หรือ Keyboard Activity เกิน 5 นาที ผลลัพธ์ที่คาดหวัง: - Session หมดอายุตามข้อกำหนด - ระบบแจ้งเตือนอย่างชัดเจน - ไม่มีการส่ง Form หลัง Session หมดอายุ --- # Quality Gate ก่อนจบงานต้องดำเนินการ: ```bash npm run lint npm run typecheck npm run build ``` หรือใช้คำสั่งที่มีอยู่จริงใน `package.json` ต้องแก้ไข: - TypeScript Error - ESLint Error - Build Error - Import Error - Hydration Error - Unhandled Promise Rejection - Memory Leak จาก Timer หรือ Event Listener ห้ามปิด Rule หรือใช้: ```ts any @ts-ignore eslint-disable ``` โดยไม่มีเหตุผลที่จำเป็นและมีคำอธิบาย --- # ข้อจำกัดในการแก้ไข - รักษาโครงสร้างและ Coding Convention เดิม - ใช้ Component มาตรฐานที่โครงการมีอยู่แล้ว - ไม่สร้าง Auth Provider ซ้ำ - ไม่เปลี่ยน Authentication Flow โดยไม่จำเป็น - ไม่ลบ Permission หรือ Route Protection เดิม - ไม่ Hardcode Domain - ไม่ Hardcode Secret - ไม่ Hardcode Login URL หลายตำแหน่ง - ไม่เปลี่ยน Session Strategy หากไม่มีเหตุผลรองรับ - ไม่แก้เฉพาะอาการโดยละเลย Root Cause - ห้ามทำให้ `/api/auth/session` ถูก Redirect ไปหน้า Login --- # ผลลัพธ์ที่ต้องส่งมอบ หลังดำเนินการเสร็จ ให้จัดทำรายงานสรุป Markdown โดยประกอบด้วย: ## 1. Root Cause ระบุสาเหตุจริงของ: ```text Unexpected token '<', "