Now.js Framework Documentation

Now.js Framework Documentation

Authentication System - ระบบการตรวจสอบสิทธิ์

TH 31 Oct 2025 01:19

Authentication System - ระบบการตรวจสอบสิทธิ์

เอกสารฉบับนี้สรุปภาพรวมสถาปัตยกรรม ฟีเจอร์ และขั้นตอนการใช้งานระบบการตรวจสอบสิทธิ์ (Authentication) ของ Now.js Framework พร้อมแนวทางการออกแบบและการรักษาความปลอดภัยที่ควรรู้

📋 สารบัญ

  1. ภาพรวม
  2. สถาปัตยกรรมระบบ
  3. คอมโพเนนต์หลัก
  4. เริ่มต้นใช้งานอย่างรวดเร็ว
  5. ลำดับขั้นการยืนยันตัวตน
  6. ตัวอย่างการใช้งาน
  7. แนวทางปฏิบัติที่แนะนำ
  8. ข้อควรคำนึงด้านความปลอดภัย
  9. เอกสารที่เกี่ยวข้อง

ภาพรวม

ระบบการตรวจสอบสิทธิ์ของ Now.js ถูกออกแบบให้ครอบคลุมวงจรการยืนยันตัวตนทั้งหมด ทั้งในมุมมองของผู้ใช้ ฝั่งไคลเอนต์ และการประสานกับ API เพื่อให้ทีมพัฒนาสามารถสร้างเว็บแอปพลิเคชันสมัยใหม่ได้อย่างปลอดภัยและยืดหยุ่น

ฟีเจอร์หลัก

  • รองรับ JWT แบบครบถ้วน: จัดการทั้ง access token และ refresh token ในตัว
  • หลายกลยุทธ์การยืนยันตัวตน: ใช้งาน Bearer, Basic, OAuth หรือโหมดผสมได้ตามต้องการ
  • คุกกี้ HttpOnly: เก็บโทเค็นในคุกกี้ HttpOnly เพื่อป้องกันการถูกขโมยผ่าน XSS
  • รีเฟรชโทเค็นอัตโนมัติ: ต่ออายุโทเค็นก่อนหมดอายุเพื่อรักษาประสบการณ์ใช้งาน
  • Route Guard อัจฉริยะ: ตรวจสอบบทบาท (role) และสิทธิ์ (permission) ก่อนเข้าแต่ละเส้นทาง
  • ป้องกัน CSRF: จัดการ CSRF token และการแนบ header ให้อัตโนมัติ
  • ระบบจัดการข้อผิดพลาด: จับและกู้คืนจากข้อผิดพลาดที่พบบ่อย พร้อมวงจร retry
  • สถานะการโหลด: ประสานกับ AuthLoadingManager เพื่อสื่อสารสถานะการทำงานแก่ผู้ใช้
  • บริหารเซสชัน: เก็บและซิงก์สถานะผู้ใช้ร่วมกับ localStorage / SessionStorage
  • โซเชียลล็อกอิน: เชื่อมต่อผู้ให้บริการ OAuth เช่น Google, Facebook ได้ทันที

เมื่อควรเลือกใช้ระบบนี้

แนะนำให้ใช้เมื่อ:

  • จำเป็นต้องยืนยันตัวตนผู้ใช้ก่อนเข้าถึงข้อมูลหรือฟีเจอร์สำคัญ
  • ต้องการระบบกำหนดสิทธิ์ตามบทบาท (RBAC) หรือสิทธิ์ระดับละเอียด
  • ต้องใช้โทเค็นเพื่อทำงานร่วมกับ API ภายนอกหรือบริการไมโครเซอร์วิส
  • ต้องการเติมเต็มประสบการณ์ผู้ใช้ เช่น รีเฟรชโทเค็นอัตโนมัติหรือจำเส้นทางที่ตั้งใจเข้าถึง
  • มีแผนผนวกบัญชีโซเชียลเข้ากับระบบการยืนยันตัวตน

ควรพิจารณาทางเลือกอื่นหาก:

  • แอปพลิเคชันเปิดให้เข้าถึงข้อมูลแบบสาธารณะโดยไม่ต้องล็อกอิน
  • โปรเจกต์ต้องการโค้ดที่เรียบง่ายมากและใช้การยืนยันแบบพื้นฐานเท่านั้น
  • มีระบบการยืนยันตัวตนจากภายนอกที่บังคับใช้อยู่แล้วและไม่ต้องการบูรณาการเพิ่มเติม

สถาปัตยกรรมระบบ

โซลูชัน Authentication ของ Now.js แบ่งออกเป็น 5 ชั้นประกอบหลัก เพื่อให้ปรับแต่งและขยายได้ง่ายดังแผนภาพต่อไปนี้:

┌─────────────────────────────────────────────────────────────┐
│                     Application Layer                       │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│  │   Router     │  │     Views    │  │   Components │     │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘     │
└─────────┼──────────────────┼──────────────────┼─────────────┘
          │                  │                  │
          ▼                  ▼                  ▼
┌─────────────────────────────────────────────────────────────┐
│                   Authentication Layer                       │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│  │  AuthGuard   │  │ AuthManager  │  │ Error Handler│     │
│  │ (Route Guard)│  │(Core Manager)│  │(Error Handle)│     │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘     │
│         │                  │                  │              │
│         │    ┌─────────────┼─────────────┐   │              │
│         │    │             │             │   │              │
│         ▼    ▼             ▼             ▼   ▼              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│  │TokenService  │  │LoadingManager│  │ SecurityMgr  │     │
│  │(Token Mgmt)  │  │(Loading UI)  │  │(CSRF, etc.)  │     │
│  └──────────────┘  └──────────────┘  └──────────────┘     │
└─────────────────────────────────────────────────────────────┘
          │                  │                  │
          ▼                  ▼                  ▼
┌─────────────────────────────────────────────────────────────┐
│                      Storage Layer                           │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│  │   Cookies    │  │ LocalStorage │  │SessionStorage│     │
│  │(HttpOnly JWT)│  │(User Data)   │  │(Temp Data)   │     │
│  └──────────────┘  └──────────────┘  └──────────────┘     │
└─────────────────────────────────────────────────────────────┘

ลำดับการไหลของข้อมูลหลัก

  1. การเข้าสู่ระบบของผู้ใช้:

    User → AuthManager.login() → API → Response (JWT)
      → TokenService.store() → HttpOnly Cookie
      → AuthManager.setAuthenticatedUser() → Update State
  2. การเข้าถึงเส้นทางที่ต้องยืนยันตัวตน:

    Router → AuthGuard.checkRoute() → AuthManager.checkAuthStatus()
      → TokenService.getToken() → Validate Token
      → Check Permissions/Roles → Allow/Deny
  3. การรีเฟรชโทเค็น:

    Request → 401 Error → AuthManager.refreshToken()
       → TokenService.getRefreshToken() → API
       → New Access Token → Continue Request

คอมโพเนนต์หลัก

1. AuthManager (Core)

ไฟล์: Now/js/AuthManager.js
หน้าที่: ดูแลวงจรการยืนยันตัวตนตั้งแต่เริ่มต้นจนจบ

ฟีเจอร์เด่น:

  • กระบวนการเข้าสู่ระบบและออกจากระบบที่ครบถ้วน
  • บริหาร access token และ refresh token
  • จัดการสถานะเซสชันและผู้ใช้
  • ต่ออายุโทเค็นอัตโนมัติเมื่อใกล้หมดอายุ
  • เชื่อมต่อโซเชียลล็อกอินและ OAuth ได้ทันที
  • ป้องกันการโจมตีด้วย CSRF ในตัว

เอกสาร: AuthManager.md

2. TokenService

ไฟล์: Now/js/TokenService.js
หน้าที่: จัดการโทเค็น JWT ตั้งแต่การแยกวิเคราะห์ การตรวจสอบ ไปจนถึงการจัดเก็บ

ฟีเจอร์เด่น:

  • แยกวิเคราะห์ payload ของ JWT ได้สะดวก
  • ตรวจสอบอายุและความถูกต้องของโทเค็น
  • จัดเก็บโทเค็นในคุกกี้หรือ localStorage ตามนโยบายที่ตั้งไว้
  • ดึงข้อมูลผู้ใช้จากโทเค็นเพื่อใช้งานต่อ

เอกสาร: TokenService.md

3. AuthGuard

ไฟล์: Now/js/AuthGuard.js
หน้าที่: ป้องกันเส้นทาง (route) ตามเงื่อนไขการยืนยันตัวตนและสิทธิ์การเข้าถึง

ฟีเจอร์เด่น:

  • กำหนดเส้นทางที่ต้องล็อกอินก่อนเข้าถึง
  • ตรวจสอบบทบาทผู้ใช้ (role-based access control)
  • ตรวจสอบสิทธิ์เชิงละเอียด (permission checking)
  • รองรับการเพิ่มกฎตรวจสอบพิเศษเพิ่มเติม
  • จดจำ URL ที่ผู้ใช้ตั้งใจเข้าก่อนถูกเปลี่ยนเส้นทาง

เอกสาร: AuthGuard.md

4. AuthErrorHandler

ไฟล์: Now/js/AuthErrorHandler.js
หน้าที่: จัดการข้อผิดพลาดที่เกี่ยวข้องกับการยืนยันตัวตนและวางมาตรการตอบสนอง

ฟีเจอร์เด่น:

  • รองรับการจัดการข้อผิดพลาดมากกว่า 11 ประเภท
  • ดำเนินการตอบสนองอัตโนมัติ เช่น redirect, render, retry
  • รีเฟรชโทเค็นเมื่อหมดอายุหากเป็นไปได้
  • ปรับใช้มาตรการจำกัดความถี่ (rate limiting)
  • เปิดให้กำหนดการจัดการข้อผิดพลาดเพิ่มเติมได้เอง

เอกสาร: AuthErrorHandler.md

5. AuthLoadingManager

ไฟล์: Now/js/AuthLoadingManager.js
หน้าที่: ควบคุมสถานะการโหลดที่เกี่ยวข้องกับกระบวนการยืนยันตัวตนทั้งหมด

ฟีเจอร์เด่น:

  • ครอบคลุมสถานะการโหลดมากกว่า 9 ประเภทของการทำงาน
  • แสดง overlay/loading UI ตามมาตรฐานของระบบ
  • ติดตามความคืบหน้าการทำงานที่ใช้เวลานาน
  • จัดการกรณีหมดเวลา (timeout) ได้อัตโนมัติ
  • ปรับแต่ง UI การโหลดเพิ่มเติมได้ตามความต้องการ

เอกสาร: AuthLoadingManager.md

เริ่มต้นใช้งานอย่างรวดเร็ว

1. การติดตั้ง

ระบบยืนยันตัวตนถูกบันเดิลมาพร้อม Now.js Framework อยู่แล้ว:

<!-- โหลด AuthManager และโมดูลที่เกี่ยวข้อง -->
<script src="/Now/js/TokenService.js"></script>
<script src="/Now/js/AuthLoadingManager.js"></script>
<script src="/Now/js/AuthErrorHandler.js"></script>
<script src="/Now/js/AuthGuard.js"></script>
<script src="/Now/js/AuthManager.js"></script>

2. การตั้งค่าพื้นฐาน

// เริ่มต้นใช้งาน AuthManager
await AuthManager.init({
  enabled: true,
  type: 'jwt-httponly',

  endpoints: {
    login: '/api/v1/auth/login',
    logout: '/api/v1/auth/logout',
    verify: '/api/v1/auth/verify',
    refresh: '/api/v1/auth/refresh'
  },

  security: {
    csrf: true,
    autoRefresh: true
  },

  redirects: {
    afterLogin: '/dashboard',
    afterLogout: '/login',
    unauthorized: '/login'
  }
});

console.log('ระบบยืนยันตัวตนพร้อมใช้งานแล้ว!');

3. การเข้าสู่ระบบของผู้ใช้

// ตัวอย่างการเข้าสู่ระบบแบบพื้นฐาน
const result = await AuthManager.login({
  username: 'user@example.com',
  password: 'secret123'
});

if (result.success) {
  console.log('เข้าสู่ระบบสำเร็จ!');
  console.log('ข้อมูลผู้ใช้:', AuthManager.getUser());
  // ระบบจะพาไปยัง /dashboard ให้อัตโนมัติ
}

4. ตรวจสอบสถานะการยืนยันตัวตน

// ตรวจสอบว่าผู้ใช้ล็อกอินแล้วหรือไม่
if (AuthManager.isAuthenticated()) {
  const user = AuthManager.getUser();
  console.log('ยินดีต้อนรับ', user.name);
} else {
  console.log('โปรดเข้าสู่ระบบ');
}

5. ออกจากระบบ

// ออกจากระบบผู้ใช้ปัจจุบัน
await AuthManager.logout();
console.log('ออกจากระบบเรียบร้อย');
// ระบบจะพาไปหน้า /login ให้อัตโนมัติ

ลำดับขั้นการยืนยันตัวตน

1. ลำดับการเข้าสู่ระบบ (ครบวงจร)

// ขั้นที่ 1: ผู้ใช้ส่งฟอร์มเข้าสู่ระบบ
document.getElementById('loginForm').addEventListener('submit', async (e) => {
  e.preventDefault();

  // ขั้นที่ 2: เรียก AuthManager.login()
  const result = await AuthManager.login({
    username: document.getElementById('username').value,
    password: document.getElementById('password').value
  });

  // ขั้นที่ 3: ตรวจสอบผลลัพธ์
  if (result.success) {
    // เข้าสู่ระบบสำเร็จ
    // - JWT ถูกเก็บในคุกกี้ HttpOnly
    // - ข้อมูลผู้ใช้ถูกเก็บใน localStorage
    // - ระบบจะนำไปหน้า dashboard ให้อัตโนมัติ
    console.log('เข้าสู่ระบบสำเร็จ!');
  } else {
    // เข้าสู่ระบบไม่สำเร็จ
    console.error('เข้าสู่ระบบล้มเหลว:', result.error);
    alert(result.error);
  }
});

กระบวนการเบื้องหลัง:

  1. AuthLoadingManager แสดง loading overlay
  2. AuthManager เรียก login endpoint
  3. Server ตรวจสอบ credentials
  4. Server ส่ง JWT tokens กลับมา
  5. TokenService เก็บ access token ใน httpOnly cookie
  6. TokenService เก็บ refresh token ใน httpOnly cookie (ถ้ามี)
  7. AuthManager เก็บ user data ใน localStorage
  8. AuthManager อัปเดต state (authenticated = true)
  9. AuthManager redirect ไป afterLogin route
  10. AuthLoadingManager ปิด loading overlay

2. ลำดับการตรวจเส้นทางที่ต้องยืนยันตัวตน

// ตั้งค่า Router พร้อม auth guard
await Router.init({
  routes: [
    {
      path: '/dashboard',
      handler: 'renderDashboard',
      metadata: {
        requiresAuth: true,
        roles: ['user', 'admin'],
        permissions: ['read:dashboard']
      }
    }
  ],

  auth: {
    enabled: true,
    guard: AuthGuard.checkRoute,
    defaultRequireAuth: false,
    redirects: {
      unauthorized: '/login',
      forbidden: '/403'
    }
  }
});

// เมื่อผู้ใช้ไปยัง /dashboard
Router.navigate('/dashboard');

กระบวนการเบื้องหลัง:

  1. Router เรียก AuthGuard.checkRoute()
  2. AuthGuard ขอให้ AuthManager.checkAuthStatus() ตรวจสอบสถานะ
  3. AuthManager ตรวจสอบการยืนยันตัวตนปัจจุบัน
  4. หากมีโทเค็นแต่ยังไม่ยืนยัน → เรียก verify endpoint
  5. AuthGuard ตรวจสอบบทบาทและสิทธิ์ที่ตั้งไว้
  6. หากผ่านเงื่อนไข → อนุญาตให้เข้าหน้าเป้าหมาย
  7. หากไม่ผ่าน → เปลี่ยนเส้นทางไปหน้า unauthorized

3. ลำดับการรีเฟรชโทเค็น

// การรีเฟรชโทเค็นอัตโนมัติ (ตั้งค่าไว้ใน AuthManager)
await AuthManager.init({
  security: {
    autoRefresh: true,
    refreshBeforeExpiry: 5 * 60 * 1000 // 5 minutes
  }
});

// การรีเฟรชโทเค็นด้วยตนเอง
const refreshed = await AuthManager.refreshToken();
if (refreshed) {
  console.log('รีเฟรชโทเค็นสำเร็จ');
}

กระบวนการเบื้องหลัง:

  1. AuthManager ตั้ง timer ก่อน token หมดอายุ 5 นาที
  2. Timer trigger → call refreshToken()
  3. TokenService อ่าน refresh token จาก cookie
  4. AuthManager เรียก refresh endpoint พร้อม refresh token
  5. Server validate refresh token
  6. Server ส่ง access token ใหม่กลับมา
  7. TokenService เก็บ access token ใหม่
  8. AuthManager ตั้ง timer ใหม่

ตัวอย่างรีเฟรชอัตโนมัติเมื่อเจอ 401:

// กำหนดใน interceptor ของ HttpClient
httpClient.on('response', async (response) => {
  if (response.status === 401) {
    // พยายามรีเฟรชโทเค็นใหม่
    const refreshed = await AuthManager.refreshToken();

    if (refreshed) {
      // ส่งคำร้องเดิมอีกครั้งด้วยโทเค็นใหม่
      return httpClient.retry(response.config);
    } else {
      // รีเฟรชไม่สำเร็จ - ออกจากระบบผู้ใช้
      await AuthManager.logout(false);
      Router.navigate('/login');
    }
  }
});

4. ลำดับการจัดการข้อผิดพลาด

// AuthErrorHandler ถูกเชื่อมต่อให้อัตโนมัติ
// แต่สามารถปรับแต่งการจัดการข้อผิดพลาดเพิ่มเติมได้

document.addEventListener('auth:error', (e) => {
  const { errorType, message, action } = e.detail;

  console.log('ข้อผิดพลาดด้านการยืนยันตัวตน:', errorType);
  console.log('ข้อความ:', message);
  console.log('การดำเนินการที่เกิดขึ้น:', action);

  // จัดการเพิ่มเติมตามต้องการ
  if (errorType === 'TOKEN_EXPIRED') {
    console.log('โทเค็นหมดอายุ กำลังรีเฟรช...');
  }
});

ประเภทข้อผิดพลาดที่รองรับ:

  • UNAUTHORIZED - ไม่มีโทเค็นหรือโทเค็นไม่ถูกต้อง
  • FORBIDDEN - ไม่มีสิทธิ์เข้าถึงทรัพยากร
  • TOKEN_EXPIRED - โทเค็นหมดอายุ
  • TOKEN_INVALID - โทเค็นไม่ถูกต้อง
  • SESSION_EXPIRED - เซสชันหมดอายุ
  • RATE_LIMITED - ทำคำร้องมากเกินไป
  • ACCOUNT_LOCKED - บัญชีถูกล็อกชั่วคราว
  • และอื่นๆ (รายละเอียดเพิ่มเติมใน AuthErrorHandler.md)

ตัวอย่างการใช้งาน

1. ฟอร์มเข้าสู่ระบบแบบครบวงจร

<!DOCTYPE html>
<html>
<head>
  <title>เข้าสู่ระบบ</title>
</head>
<body>
  <form id="loginForm">
    <input type="text" id="username" placeholder="ชื่อผู้ใช้" required>
    <input type="password" id="password" placeholder="รหัสผ่าน" required>
    <button type="submit">เข้าสู่ระบบ</button>
    <div id="error" style="color: red;"></div>
  </form>

  <script src="/Now/js/TokenService.js"></script>
  <script src="/Now/js/AuthLoadingManager.js"></script>
  <script src="/Now/js/AuthErrorHandler.js"></script>
  <script src="/Now/js/AuthManager.js"></script>

  <script>
    // เริ่มต้นใช้ AuthManager
    (async () => {
      await AuthManager.init({
        enabled: true,
        endpoints: {
          login: '/api/v1/auth/login',
          logout: '/api/v1/auth/logout'
        },
        redirects: {
          afterLogin: '/dashboard'
        }
      });

      // จัดการการส่งฟอร์มเข้าสู่ระบบ
      document.getElementById('loginForm').addEventListener('submit', async (e) => {
        e.preventDefault();

        const errorDiv = document.getElementById('error');
        errorDiv.textContent = '';

        try {
          const result = await AuthManager.login({
            username: document.getElementById('username').value,
            password: document.getElementById('password').value
          });

          if (result.success) {
            // เข้าสู่ระบบสำเร็จ - ระบบจะเปลี่ยนหน้าให้เอง
            console.log('เข้าสู่ระบบสำเร็จ!');
          } else {
            // แสดงข้อความผิดพลาด
            errorDiv.textContent = result.error || 'เข้าสู่ระบบไม่สำเร็จ';
          }
        } catch (error) {
          errorDiv.textContent = 'เกิดข้อผิดพลาด โปรดลองอีกครั้ง';
          console.error('ข้อผิดพลาดในการเข้าสู่ระบบ:', error);
        }
      });
    })();
  </script>
</body>
</html>

2. แดชบอร์ดที่ต้องยืนยันตัวตนก่อนเข้าถึง

<!DOCTYPE html>
<html>
<head>
  <title>แดชบอร์ด</title>
</head>
<body>
  <div id="dashboard">
    <h1>ยินดีต้อนรับ <span id="userName"></span>!</h1>
    <button id="logoutBtn">ออกจากระบบ</button>
  </div>

  <script src="/Now/js/TokenService.js"></script>
  <script src="/Now/js/AuthManager.js"></script>

  <script>
    (async () => {
      // เริ่มต้นใช้งาน AuthManager
      await AuthManager.init({
        enabled: true
      });

      // ตรวจสอบสถานะการยืนยันตัวตน
      if (!AuthManager.isAuthenticated()) {
        // ยังไม่เข้าสู่ระบบ - พาไปหน้าเข้าสู่ระบบ
        window.location.href = '/login';
        return;
      }

      // ดึงข้อมูลผู้ใช้
      const user = AuthManager.getUser();
      document.getElementById('userName').textContent = user.name;

      // จัดการการออกจากระบบ
      document.getElementById('logoutBtn').addEventListener('click', async () => {
        await AuthManager.logout();
        // ระบบจะเปลี่ยนหน้าไป /login ให้อัตโนมัติ
      });
    })();
  </script>
</body>
</html>

3. ส่วนติดต่อผู้ใช้ตามบทบาท

// แสดงหรือซ่อนองค์ประกอบตามบทบาทของผู้ใช้
(async () => {
  await AuthManager.init({ enabled: true });

  if (!AuthManager.isAuthenticated()) {
    window.location.href = '/login';
    return;
  }

  const user = AuthManager.getUser();

  // ตรวจสอบบทบาท
  if (AuthManager.hasRole('admin')) {
    // แสดงแดชบอร์ดผู้ดูแลระบบ
    document.getElementById('adminPanel').style.display = 'block';
  }

  // ตรวจสอบสิทธิ์
  if (AuthManager.hasPermission('edit:posts')) {
    // แสดงปุ่มแก้ไข
    document.getElementById('editButton').style.display = 'block';
  }
})();

4. เรียกใช้งาน API พร้อมการยืนยันตัวตน

// ApiService จะส่งโทเค็นยืนยันตัวตนให้โดยอัตโนมัติ
const response = await ApiService.get('/api/users');

if (response.ok) {
  console.log('ข้อมูลผู้ใช้:', response.data);
} else if (response.status === 401) {
  // โทเค็นหมดอายุ - AuthManager จะจัดการให้เอง
  console.log('โทเค็นหมดอายุ กำลังรีเฟรช...');
} else {
  console.error('ข้อผิดพลาด:', response.error);
}

5. การเข้าสู่ระบบผ่านโซเชียล

// เริ่มต้นพร้อมกำหนด endpoint สำหรับโซเชียลล็อกอิน
await AuthManager.init({
  enabled: true,
  endpoints: {
    social: '/api/v1/auth/social/{provider}',
    callback: '/api/v1/auth/callback'
  }
});

// ปุ่มเข้าสู่ระบบด้วย Google
document.getElementById('googleLogin').addEventListener('click', async () => {
  await AuthManager.socialLogin('google', {
    redirect_uri: 'http://localhost/auth/callback'
  });
  // ระบบจะเปิดหน้าต่างป็อปอัปสำหรับ Google OAuth
});

// ปุ่มเข้าสู่ระบบด้วย Facebook
document.getElementById('facebookLogin').addEventListener('click', async () => {
  await AuthManager.socialLogin('facebook', {
    redirect_uri: 'http://localhost/auth/callback'
  });
});

// จัดการผลลัพธ์หลังกลับจากผู้ให้บริการโซเชียล
if (window.location.pathname === '/auth/callback') {
  const params = new URLSearchParams(window.location.search);
  const code = params.get('code');
  const provider = params.get('provider');

  if (code && provider) {
    const result = await AuthManager.handleAuthCallback({
      code,
      provider
    });

    if (result.success) {
      console.log('เข้าสู่ระบบผ่านโซเชียลสำเร็จ!');
      window.location.href = '/dashboard';
    }
  }
}

6. การตรวจสอบเพิ่มเติมตามเงื่อนไขเฉพาะ

// ตั้งค่า Router พร้อมกฎตรวจสอบเพิ่มเติม
await Router.init({
  routes: [
    {
      path: '/admin/settings',
      handler: 'renderSettings',
      metadata: {
        requiresAuth: true,
        validate: async (to, from, router) => {
          const user = AuthManager.getUser();

          // ตรวจสอบสถานะการสมัครใช้งาน (subscription)
          if (!user.subscription || user.subscription.status !== 'active') {
            return {
              allowed: false,
              action: 'redirect',
              target: '/subscribe',
              reason: 'ต้องมีการสมัครใช้งานที่ใช้งานอยู่'
            };
          }

          // ตรวจสอบว่า IP อยู่ใน whitelist
          const response = await fetch('/api/check-ip');
          const { allowed } = await response.json();

          if (!allowed) {
            return {
              allowed: false,
              action: 'block',
              reason: 'IP not whitelisted'
            };
          }

          return { allowed: true };
        }
      }
    }
  ],

  auth: {
    enabled: true,
    guard: AuthGuard.checkRoute
  }
});

แนวทางปฏิบัติที่แนะนำ

1. ความปลอดภัยของโทเค็น

ควรทำ:

  • ใช้คุกกี้แบบ httpOnly สำหรับเก็บโทเค็น
  • ตั้งค่า secure สำหรับสภาพแวดล้อมจริง (ต้องใช้ HTTPS)
  • ใช้ค่า SameSite = Strict หรือ Lax ตามความเหมาะสม
  • หมุนเวียน (rotate) refresh token เป็นประจำ
  • กำหนดอายุโทเค็นให้เหมาะสม (เช่น access: 15 นาที, refresh: 7 วัน)

ไม่ควรทำ:

  • เก็บโทเค็นใน localStorage เพราะเสี่ยงต่อ XSS
  • เก็บข้อมูลอ่อนไหวไว้ใน payload ของ JWT
  • ส่งโทเค็นผ่าน URL parameters
  • ใช้โทเค็นที่ไม่มีวันหมดอายุ
// ✅ ตัวอย่างที่ดี - เก็บโทเค็นในคุกกี้ HttpOnly
await AuthManager.init({
  type: 'jwt-httponly',
  token: {
    cookieOptions: {
      httpOnly: true,  // ป้องกันการเข้าถึงผ่าน JavaScript
      secure: true,    // ใช้ได้เฉพาะผ่าน HTTPS
      sameSite: 'Strict'
    }
  }
});

// ❌ ตัวอย่างที่ไม่ควรทำ - เก็บใน localStorage
localStorage.setItem('token', token); // เสี่ยงต่อการถูกโจมตีแบบ XSS

2. การป้องกัน CSRF

ควรทำ:

  • เปิดใช้งานระบบป้องกัน CSRF
  • ส่งค่า CSRF token ผ่าน header ทุกครั้งที่จำเป็น
  • ตรวจสอบความถูกต้องของ CSRF token บนเซิร์ฟเวอร์
  • ใช้คุกกี้ที่ตั้งค่า SameSite เพื่อลดการปลอมคำร้อง
// ✅ ตัวอย่างที่ดี - เปิดระบบป้องกัน CSRF
await AuthManager.init({
  security: {
    csrf: true,
    csrfIncludeSafeMethods: true
  }
});

// CSRF token จะถูกแนบในคำร้องให้อัตโนมัติ
const response = await ApiService.post('/api/users', userData);

3. การจัดการข้อผิดพลาด

ควรทำ:

  • รองรับการจัดการข้อผิดพลาดทุกประเภทที่ระบบอาจส่งมา
  • แสดงข้อความที่ผู้ใช้เข้าใจได้ง่าย
  • บันทึกข้อผิดพลาดเพื่อช่วยดีบัก
  • ออกแบบการลองทำซ้ำ (retry) ให้เหมาะกับแต่ละกรณี
// ✅ ตัวอย่างที่ดี - จัดการข้อผิดพลาดอย่างครอบคลุม
document.addEventListener('auth:error', (e) => {
  const { errorType, message } = e.detail;

  // บันทึกข้อความสำหรับการดีบัก
  console.error('ข้อผิดพลาดของระบบยืนยันตัวตน:', errorType, message);

  // แสดงข้อความให้ผู้ใช้เข้าใจง่าย
  switch (errorType) {
    case 'TOKEN_EXPIRED':
      showNotification('เซสชันหมดอายุแล้ว โปรดเข้าสู่ระบบใหม่');
      break;
    case 'FORBIDDEN':
      showNotification('คุณไม่มีสิทธิ์เข้าถึงหน้านี้');
      break;
    default:
      showNotification('เกิดข้อผิดพลาด โปรดลองอีกครั้ง');
  }
});

4. การจัดการสถานะการโหลด

ควรทำ:

  • แสดงตัวบ่งชี้สถานะโหลดสำหรับงานแบบอะซิงโครนัส
  • ให้ข้อความตอบกลับที่ชัดเจนกับผู้ใช้
  • ปิดการใช้งานปุ่มระหว่างกำลังประมวลผล
  • กำหนดเวลาหมดเขต (timeout) ที่เหมาะสม
// ✅ ตัวอย่างที่ดี - จัดการสถานะโหลดอย่างเหมาะสม
async function handleLogin(credentials) {
  const submitBtn = document.getElementById('submitBtn');

  // ปิดการใช้งานปุ่มระหว่างล็อกอิน
  submitBtn.disabled = true;
  submitBtn.textContent = 'กำลังเข้าสู่ระบบ...';

  try {
    const result = await AuthManager.login(credentials);

    if (result.success) {
      // แจ้งผลลัพธ์ที่สำเร็จ
      submitBtn.textContent = 'สำเร็จ! กำลังเปลี่ยนหน้า...';
    }
  } catch (error) {
    // แจ้งเมื่อเกิดข้อผิดพลาด
    submitBtn.textContent = 'เข้าสู่ระบบไม่สำเร็จ';
  } finally {
    // เปิดให้ใช้ปุ่มอีกครั้ง
    setTimeout(() => {
      submitBtn.disabled = false;
      submitBtn.textContent = 'เข้าสู่ระบบ';
    }, 2000);
  }
}

5. การจัดการเซสชัน

ควรทำ:

  • ตรวจสอบสถานะเซสชันทุกครั้งเมื่อผู้ใช้กลับมาที่หน้าเว็บ
  • ล้างข้อมูลเซสชันทุกครั้งที่ผู้ใช้ออกจากระบบ
  • รับมือกรณีเซสชันหมดอายุอย่างเหมาะสม
  • กำหนดเวลาหมดอายุของเซสชันให้สัมพันธ์กับความเสี่ยงของระบบ
// ✅ ตัวอย่างที่ดี - ตรวจสอบเซสชันเมื่อโหลดหน้า
window.addEventListener('load', async () => {
  await AuthManager.init({ enabled: true });

  if (AuthManager.isAuthenticated()) {
    // ตรวจสอบว่าเซสชันยังคงใช้ได้อยู่หรือไม่
    const valid = await AuthManager.checkAuthStatus();

    if (!valid) {
      // เซสชันหมดอายุ
      console.log('เซสชันหมดอายุ โปรดเข้าสู่ระบบใหม่');
      await AuthManager.logout(false); // ไม่ต้องเรียกเซิร์ฟเวอร์
      window.location.href = '/login';
    }
  }
});

// ตรวจสอบเมื่อผู้ใช้กลับมาที่หน้าอีกครั้ง
document.addEventListener('visibilitychange', async () => {
  if (!document.hidden && AuthManager.isAuthenticated()) {
    // ผู้ใช้กลับมาที่หน้า - ตรวจสอบเซสชันอีกครั้ง
    await AuthManager.checkAuthStatus();
  }
});

6. การป้องกันเส้นทาง

ควรทำ:

  • ใช้ AuthGuard สำหรับเส้นทางที่ต้องการการยืนยันตัวตน
  • กำหนดค่าเริ่มต้นว่าหน้าใดจำเป็นต้องล็อกอิน
  • ระบุบทบาทและสิทธิ์ให้ชัดเจนสำหรับแต่ละเส้นทาง
  • ออกแบบประสบการณ์เมื่อไม่มีสิทธิ์เข้าถึงให้เหมาะสม
// ✅ ตัวอย่างที่ดี - ป้องกันเส้นทางอย่างรอบด้าน
await Router.init({
  routes: [
    // เส้นทางสำหรับบุคคลทั่วไป
    { path: '/', handler: 'home', metadata: { public: true } },
    { path: '/about', handler: 'about', metadata: { public: true } },

    // เส้นทางเฉพาะผู้เยี่ยมชม (ยังไม่ล็อกอิน)
    {
      path: '/login',
      handler: 'login',
      metadata: {
        guestOnly: true,
        redirectOnAuth: '/dashboard'
      }
    },

    // เส้นทางที่ต้องล็อกอินและมีบทบาทเฉพาะ
    {
      path: '/dashboard',
      handler: 'dashboard',
      metadata: {
        requiresAuth: true,
        roles: ['user', 'admin']
      }
    },

    // เส้นทางสำหรับผู้ดูแลระบบเท่านั้น
    {
      path: '/admin/*',
      handler: 'admin',
      metadata: {
        requiresAuth: true,
        roles: ['admin'],
        permissions: ['admin:access']
      }
    }
  ],

  auth: {
    enabled: true,
    guard: AuthGuard.checkRoute,
    defaultRequireAuth: false,
    redirects: {
      unauthorized: '/login',
      forbidden: '/403'
    }
  }
});

7. การทดสอบระบบยืนยันตัวตน

ควรทำ:

  • ทดสอบทุกลำดับขั้นการยืนยันตัวตน
  • ทดสอบสถานการณ์ที่เกิดข้อผิดพลาด
  • ทดสอบกระบวนการรีเฟรชโทเค็น
  • ทดสอบการป้องกันเส้นทางตามบทบาทและสิทธิ์
// ✅ ตัวอย่างที่ดี - ครอบคลุมกรณีทดสอบสำคัญ
describe('Authentication', () => {
  beforeEach(async () => {
    await AuthManager.init({ enabled: true });
    AuthManager.clearAuthData();
  });

  it('should login successfully', async () => {
    const result = await AuthManager.login({
      username: 'test@example.com',
      password: 'password123'
    });

    expect(result.success).toBe(true);
    expect(AuthManager.isAuthenticated()).toBe(true);
  });

  it('should handle invalid credentials', async () => {
    const result = await AuthManager.login({
      username: 'test@example.com',
      password: 'wrongpassword'
    });

    expect(result.success).toBe(false);
    expect(result.error).toBeTruthy();
  });

  it('should refresh token before expiry', async () => {
    // เข้าสู่ระบบก่อน
    await AuthManager.login({ username: 'test', password: 'test' });

    // สร้างโทเค็นจำลองที่ใกล้หมดอายุ
    const token = TokenService.getToken();
    const payload = TokenService.parseToken(token);
    payload.exp = Math.floor(Date.now() / 1000) + 240; // 4 minutes

    // ควรกระตุ้นให้ระบบรีเฟรชอัตโนมัติ
    await new Promise(resolve => setTimeout(resolve, 1000));

    expect(AuthManager.state.refreshTimer).toBeTruthy();
  });

  it('should protect routes based on roles', async () => {
    // เข้าสู่ระบบด้วยผู้ใช้ทั่วไป
    await AuthManager.login({ username: 'user', password: 'test' });

    // พยายามเข้าหน้าผู้ดูแลระบบ
    const result = await AuthGuard.checkRoute(
      { path: '/admin', metadata: { roles: ['admin'] } },
      null,
      Router
    );

    expect(result.allowed).toBe(false);
    expect(result.action).toBe('redirect');
  });
});

ข้อควรคำนึงด้านความปลอดภัย

1. ความปลอดภัยของโทเค็น

ความเสี่ยงที่ต้องระวัง:

  • การโจมตีแบบ XSS (Cross-Site Scripting)
  • การโจมตีแบบ CSRF (Cross-Site Request Forgery)
  • การขโมยโทเค็น
  • การสวมรอยเซสชันของผู้ใช้ (Session Hijacking)

แนวทางป้องกัน:

// ✅ ใช้คุกกี้แบบ httpOnly
await AuthManager.init({
  type: 'jwt-httponly',
  token: {
    cookieOptions: {
      httpOnly: true,     // ป้องกันการถูกอ่านผ่าน JavaScript (ลดความเสี่ยง XSS)
      secure: true,       // ใช้ผ่าน HTTPS เท่านั้น
      sameSite: 'Strict'  // ลดโอกาสถูกโจมตีแบบ CSRF
    }
  },
  security: {
    csrf: true            // เปิดระบบป้องกัน CSRF
  }
});

// ✅ ทำความสะอาดข้อมูลที่กรอกเข้ามา
const sanitize = (str) => {
  const div = document.createElement('div');
  div.textContent = str;
  return div.innerHTML;
};

const username = sanitize(userInput);

2. การจำกัดความถี่ (Rate Limiting)

ความเสี่ยงที่ต้องระวัง:

  • การเดารหัสผ่านแบบรัวๆ (Brute Force)
  • การโจมตีแบบ DDoS

แนวทางป้องกัน:

// ✅ เปิดใช้งานการจำกัดความถี่
await AuthManager.init({
  security: {
    rateLimiting: true,
    maxLoginAttempts: 5,
    lockoutTime: 30 * 60 * 1000 // 30 minutes
  }
});

// ตัวอย่างบนฝั่งเซิร์ฟเวอร์
// ใช้ไลบรารีอย่าง express-rate-limit
const rateLimit = require('express-rate-limit');

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // 5 requests per windowMs
  message: 'พยายามเข้าสู่ระบบบ่อยเกินไป โปรดลองอีกครั้งภายหลัง'
});

app.post('/api/v1/auth/login', loginLimiter, (req, res) => {
  // โค้ดจัดการการเข้าสู่ระบบ
});

3. ความปลอดภัยของรหัสผ่าน

แนวทางที่ควรทำ:

  • ไม่เก็บรหัสผ่านเป็นข้อความธรรมดา (plaintext)
  • ใช้อัลกอริทึมแฮชที่รัดกุม เช่น bcrypt หรือ argon2
  • กำหนดคุณสมบัติขั้นต่ำของรหัสผ่าน
  • ให้ผู้ใช้เห็นระดับความแข็งแรงของรหัสผ่าน
// ✅ ตรวจสอบฝั่งไคลเอนต์
function validatePassword(password) {
  const minLength = 8;
  const hasUpperCase = /[A-Z]/.test(password);
  const hasLowerCase = /[a-z]/.test(password);
  const hasNumbers = /\d/.test(password);
  const hasSpecialChar = /[!@#$%^&*]/.test(password);

  if (password.length < minLength) {
    return { valid: false, message: 'รหัสผ่านต้องมีความยาวอย่างน้อย 8 ตัวอักษร' };
  }

  if (!hasUpperCase || !hasLowerCase || !hasNumbers) {
    return {
      valid: false,
      message: 'รหัสผ่านต้องมีทั้งตัวพิมพ์ใหญ่ ตัวพิมพ์เล็ก และตัวเลข'
    };
  }

  return { valid: true };
}

// การแฮชรหัสผ่านฝั่งเซิร์ฟเวอร์ (ตัวอย่าง Node.js)
const bcrypt = require('bcrypt');
const saltRounds = 10;

// แฮชรหัสผ่าน
const hashedPassword = await bcrypt.hash(password, saltRounds);

// ตรวจสอบรหัสผ่าน
const match = await bcrypt.compare(password, hashedPassword);

4. ความปลอดภัยของเซสชัน

แนวทางที่ควรทำ:

  • กำหนดเวลา timeout ของเซสชันให้เหมาะสม
  • ล้างข้อมูลเซสชันทุกครั้งที่ออกจากระบบ
  • ตรวจสอบความถูกต้องของเซสชันก่อนดำเนินการที่สำคัญ
  • ใช้พื้นที่จัดเก็บเซสชันที่ปลอดภัย
// ✅ จัดการเวลา timeout ของเซสชัน
const SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes
let sessionTimer;

function resetSessionTimer() {
  clearTimeout(sessionTimer);

  sessionTimer = setTimeout(() => {
    console.log('เซสชันหมดเวลา - กำลังออกจากระบบ');
    AuthManager.logout();
  }, SESSION_TIMEOUT);
}

// รีเซ็ตตัวจับเวลาเมื่อผู้ใช้มีการใช้งาน
document.addEventListener('click', resetSessionTimer);
document.addEventListener('keypress', resetSessionTimer);

// ล้างตัวจับเวลาเมื่อออกจากระบบ
AuthManager.on('logout', () => {
  clearTimeout(sessionTimer);
});

5. ความปลอดภัยของ API

แนวทางที่ควรทำ:

  • ใช้ HTTPS สำหรับการสื่อสารทุกครั้ง
  • ตรวจสอบความถูกต้องของข้อมูลที่รับเข้ามา
  • ทำความสะอาดข้อมูลก่อนส่งออก
  • ตั้งค่า CORS อย่างเหมาะสม
// ✅ บังคับใช้งานผ่าน HTTPS
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
  location.replace(`https:${location.href.substring(location.protocol.length)}`);
}

// ✅ การตั้งค่า CORS (ตัวอย่างฝั่งเซิร์ฟเวอร์)
app.use(cors({
  origin: 'https://yourdomain.com',
  credentials: true,
  allowedHeaders: ['Content-Type', 'Authorization', 'X-CSRF-Token']
}));

// ✅ ตรวจสอบข้อมูลที่ส่งเข้ามา
function validateLoginInput(credentials) {
  if (!credentials.username || !credentials.password) {
    throw new Error('ต้องกรอกชื่อผู้ใช้และรหัสผ่าน');
  }

  // Email validation
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(credentials.username)) {
    throw new Error('รูปแบบอีเมลไม่ถูกต้อง');
  }

  return true;
}

เอกสารที่เกี่ยวข้อง

คอมโพเนนต์หลัก

  • AuthManager.md - ผู้จัดการการยืนยันตัวตนหลักของระบบ
  • TokenService.md - บริหารจัดการโทเค็น JWT
  • AuthGuard.md - ป้องกันเส้นทางและตรวจสอบสิทธิ์
  • AuthErrorHandler.md - จัดการข้อผิดพลาดด้านการยืนยันตัวตน
  • AuthLoadingManager.md - ควบคุมสถานะการโหลดสำหรับงานด้าน auth

ระบบที่เกี่ยวข้อง

  • Router.md - ระบบกำหนดเส้นทาง (ใช้งานร่วมกับ AuthGuard)
  • ApiService.md - บริการ HTTP ที่แนบโทเค็นให้อัตโนมัติ
  • HttpClient.md - คลาส HTTP ระดับล่างสำหรับปรับแต่งคำร้อง

คู่มือ

  • Quick Start Guide - คู่มือเริ่มต้นใช้งานอย่างรวดเร็ว
  • Social Login Guide - ขั้นตอนการตั้งค่าโซเชียลล็อกอิน
  • Security Best Practices - แนวทางรักษาความปลอดภัยเพิ่มเติม

ความเข้ากันได้กับเบราว์เซอร์

ระบบการยืนยันตัวตนรองรับเบราว์เซอร์สมัยใหม่ดังนี้:

  • ✅ Chrome 90 ขึ้นไป
  • ✅ Firefox 88 ขึ้นไป
  • ✅ Safari 14 ขึ้นไป
  • ✅ Edge 90 ขึ้นไป
  • ❌ IE 11 (ไม่รองรับ)

ฟีเจอร์ที่จำเป็นต้องใช้:

  • การรองรับ Promises และ async/await
  • Fetch API
  • ความสามารถของ JavaScript รุ่น ES6 ขึ้นไป
  • คุกกี้แบบ HttpOnly
  • Web Storage API

ข้อควรพิจารณาด้านประสิทธิภาพ

1. การตรวจสอบความถูกต้องของโทเค็น

// ❌ ตัวอย่างที่ไม่ดี - ตรวจสอบทุกคำร้อง
if (!TokenService.isTokenExpired(token)) {
  // Make request
}

// ✅ ตัวอย่างที่ดี - แคชผลการตรวจสอบไว้
let tokenValid = true;
let lastCheck = Date.now();

function isTokenValid() {
  const now = Date.now();
  if (now - lastCheck > 60000) { // ตรวจสอบทุก 1 นาที
    tokenValid = !TokenService.isTokenExpired(token);
    lastCheck = now;
  }
  return tokenValid;
}

2. ลดจำนวนการเรียก API

// ❌ ตัวอย่างที่ไม่ดี - ตรวจสอบทุกครั้งที่เปลี่ยนหน้า
Router.on('beforeNavigate', async () => {
  await AuthManager.checkAuthStatus(); // เรียก API ทุกครั้ง
});

// ✅ ตัวอย่างที่ดี - ตรวจสอบเป็นช่วงเวลา
const VERIFY_INTERVAL = 5 * 60 * 1000; // 5 นาที
let lastVerify = Date.now();

Router.on('beforeNavigate', async () => {
  const now = Date.now();
  if (now - lastVerify > VERIFY_INTERVAL) {
    await AuthManager.checkAuthStatus();
    lastVerify = now;
  }
});

3. ปรับปรุงการจัดเก็บข้อมูล

// ✅ เก็บข้อมูลผู้ใช้อย่างจำเป็นเท่านั้น
const minimalUser = {
  id: user.id,
  name: user.name,
  email: user.email,
  roles: user.roles,
  permissions: user.permissions
};

AuthManager.setAuthenticatedUser(minimalUser);

// ❌ อย่าเก็บอ็อบเจ็กต์ที่มีข้อมูลมากเกินไป
// AuthManager.setAuthenticatedUser(userWithAllData); // ข้อมูลใหญ่เกิน!

การแก้ไขปัญหา

1. โทเค็นไม่ถูกส่งไปกับคำร้อง

ปัญหา: ไม่พบ Authorization header ในคำร้องที่ส่งออกไป

วิธีแก้ไข:

// ตรวจสอบว่า AuthManager ถูกเริ่มต้นแล้วหรือยัง
console.log('สถานะการเริ่มต้นของ AuthManager:', AuthManager.isInitialized());

// ตรวจสอบว่ามีโทเค็นหรือไม่
const token = TokenService.getToken();
console.log('มีโทเค็นหรือไม่:', !!token);

// ตรวจสอบตัวดักคำร้องของ HttpClient
console.log('ตัวดักคำร้องของ HttpClient:', httpClient.interceptors);

2. เกิดการเปลี่ยนหน้าไม่รู้จบ (Redirect Loop)

ปัญหา: วนเปลี่ยนหน้าไปมาระหว่างหน้าล็อกอินและแดชบอร์ด

วิธีแก้ไข:

// ตรวจสอบการตั้งค่าเส้นทาง
await Router.init({
  routes: [
    {
      path: '/login',
      handler: 'login',
      metadata: {
        public: true,     // หรือใช้ guestOnly: true
        guestOnly: true   // หากล็อกอินแล้วให้พาไปแดชบอร์ด
      }
    },
    {
      path: '/dashboard',
      handler: 'dashboard',
      metadata: {
        requiresAuth: true  // หากยังไม่ล็อกอินให้พาไปหน้า login
      }
    }
  ]
});

3. รีเฟรชโทเค็นไม่สำเร็จ

ปัญหา: การรีเฟรชโทเค็นล้มเหลว

วิธีแก้ไข:

// ตรวจสอบว่ามี refresh token หรือไม่
const refreshToken = AuthManager.getRefreshToken();
console.log('Refresh token exists:', !!refreshToken);

// ตรวจสอบค่า endpoint สำหรับรีเฟรช
console.log('Refresh endpoint:', AuthManager.config.endpoints.refresh);

// รับมือเมื่อรีเฟรชล้มเหลว
document.addEventListener('auth:refresh:failed', (e) => {
  console.error('Refresh failed:', e.detail);

  // ออกจากระบบแล้วพาไปหน้า login
  AuthManager.logout(false);
  Router.navigate('/login');
});

4. ไม่พบ CSRF Token ในคำร้อง

ปัญหา: CSRF token ไม่ถูกแนบไปกับคำร้อง

วิธีแก้ไข:

// ตรวจสอบว่าเปิดใช้งาน CSRF หรือไม่
console.log('CSRF enabled:', AuthManager.config.security.csrf);

// ตรวจสอบค่าของ CSRF token
const csrfToken = AuthManager.getCSRFToken();
console.log('CSRF token:', csrfToken);

// ตรวจสอบ meta tag ในหน้า HTML
const metaTag = document.querySelector('meta[name="csrf-token"]');
console.log('Meta tag exists:', !!metaTag);
console.log('Meta tag content:', metaTag?.content);

แหล่งข้อมูลเพิ่มเติม

ตัวอย่างโค้ด

เอกสาร API

  • Server API Specification - สเปกของ endpoint ด้านการยืนยันตัวตน
  • JWT Structure - โครงสร้าง payload ของ JWT
  • Error Codes - รายการรหัสข้อผิดพลาดและคำอธิบาย