Now.js Framework Documentation

Now.js Framework Documentation

AuthManager - ระบบจัดการการยืนยันตัวตนหลัก

TH 31 Oct 2025 01:39

AuthManager - ระบบจัดการการยืนยันตัวตนหลัก

เอกสารฉบับนี้อธิบาย AuthManager ซึ่งเป็นระบบจัดการการยืนยันตัวตนหลักของ Now.js Framework ครอบคลุมการตั้งค่า การใช้งาน และแนวปฏิบัติสำคัญทั้งหมดสำหรับการดูแลวงจรการยืนยันตัวตนของผู้ใช้

📋 สารบัญ

  1. ภาพรวม
  2. การติดตั้งและนำเข้า
  3. การเริ่มต้นใช้งาน
  4. การตั้งค่า
  5. วิธีการยืนยันตัวตน
  6. การจัดการโทเค็น
  7. การจัดการเซสชัน
  8. ฟีเจอร์ด้านความปลอดภัย
  9. ระบบเหตุการณ์
  10. ตัวอย่างการใช้งาน
  11. เอกสารอ้างอิง API
  12. แนวทางปฏิบัติที่แนะนำ
  13. ข้อผิดพลาดที่พบบ่อย
  14. ประสิทธิภาพ
  15. ความปลอดภัยเชิงลึก

ภาพรวม

AuthManager เป็นระบบจัดการการยืนยันตัวตนที่ควบคุมวงจรครบถ้วนตั้งแต่การเข้าสู่ระบบ ออกจากระบบ การจัดการโทเค็น การดูแลเซสชัน ไปจนถึงการปกป้องด้านความปลอดภัยทั้งหมดที่เกี่ยวข้องกับผู้ใช้ใน Now.js

ฟีเจอร์หลัก

  • เข้าสู่ระบบ/ออกจากระบบ: มีขั้นตอนการเข้าสู่ระบบและออกจากระบบที่ครบถ้วนเสร็จสรรพ
  • จัดการโทเค็น: ดูแล JWT ทั้ง access token และ refresh token อย่างเป็นระบบ
  • รีเฟรชโทเค็นอัตโนมัติ: ต่ออายุโทเค็นก่อนหมดอายุโดยไม่กระทบผู้ใช้
  • หลายรูปแบบการยืนยันตัวตน: รองรับ Bearer, Basic, OAuth และโหมดแบบผสม
  • คุกกี้แบบ HttpOnly: เก็บโทเค็นในคุกกี้ HttpOnly เพื่อป้องกัน XSS
  • ป้องกัน CSRF: แนบและตรวจสอบ CSRF token ให้โดยอัตโนมัติ
  • จำกัดความถี่การเข้าสู่ระบบ: ป้องกันการเดารหัสผ่านด้วย rate limit
  • สถานะเซสชัน: เก็บสถานะผู้ใช้และเซสชันให้พร้อมใช้งานตลอดเวลา
  • โซเชียลล็อกอิน: เชื่อมต่อผู้ให้บริการ OAuth ภายนอกได้ทันที
  • เชื่อมต่อ HTTP Client: ทำงานร่วมกับ HttpClient/ApiService แบบไร้รอยต่อ
  • ระบบเหตุการณ์: กระจายเหตุการณ์สำคัญให้จัดการ hook ได้ตามต้องการ
  • จัดการข้อผิดพลาด: ประสานงานกับ AuthErrorHandler เพื่อตอบสนองสถานการณ์ผิดพลาด
  • สถานะการโหลด: ผนึกกำลังกับ AuthLoadingManager เพื่อสื่อสารกับผู้ใช้

เมื่อควรเลือกใช้ AuthManager

เลือกใช้ AuthManager เมื่อ:

  • แอปพลิเคชันต้องรองรับการยืนยันตัวตนผู้ใช้แบบครบวงจร
  • ต้องการระบบจัดการ JWT ที่ปลอดภัยและมีการรีเฟรชอัตโนมัติ
  • ต้องการระบบป้องกัน CSRF, Rate Limit และการจัดการเซสชันร่วมกัน
  • ต้องการโซเชียลล็อกอินหรือรูปแบบการยืนยันตัวตนหลายประเภท
  • ต้องการควบคุมสิทธิ์ใช้งานตามบทบาทและสิทธิ์ของผู้ใช้

หลีกเลี่ยงการใช้เมื่อ:

  • แอปพลิเคชันไม่ต้องยืนยันตัวตนผู้ใช้
  • ต้องการโค้ดที่เรียบง่ายมาก ใช้เพียงการยืนยันพื้นฐานเท่านั้น
  • มีระบบยืนยันตัวตนภายนอกที่จัดการเองอยู่แล้ว

การติดตั้งและนำเข้า

AuthManager โหลดมาพร้อมกับ Now.js Framework และพร้อมใช้งานทันทีผ่าน window object:

// ไม่ต้อง import - พร้อมใช้งานทันที
console.log(window.AuthManager); // อ็อบเจ็กต์ AuthManager ที่ผูกไว้กับ window

การเริ่มต้นใช้งาน

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

// เตรียมใช้งานด้วยค่าตั้งต้น
await AuthManager.init();

console.log('AuthManager initialized!');
console.log('Authenticated:', AuthManager.isAuthenticated());

การตั้งค่าพร้อมกำหนดค่าเอง

await AuthManager.init({
  enabled: true,
  type: 'jwt-httponly',
  autoInit: true,

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

  security: {
    csrf: true,
    autoRefresh: true,
    rateLimiting: true,
    maxLoginAttempts: 5
  },

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

  token: {
    cookieName: 'auth_token',
    refreshCookieName: 'refresh_token',
    storageKey: 'auth_user'
  }
});

console.log('AuthManager initialized with custom config!');

ตรวจสอบสถานะการตั้งค่า

// ตรวจสอบว่า AuthManager ถูกตั้งค่าเรียบร้อยหรือยัง
if (AuthManager.isInitialized()) {
  console.log('AuthManager is ready');
} else {
  console.log('AuthManager not initialized yet');
  await AuthManager.init();
}

การตั้งค่า

AuthManager มีพารามิเตอร์การตั้งค่าหลายหมวดหมู่ที่สามารถกำหนดให้เหมาะกับระบบได้

1. การตั้งค่าทั่วไป

{
  enabled: false,           // เปิด/ปิดฟังก์ชันการยืนยันตัวตน
  type: 'jwt-httponly',     // ประเภทการยืนยัน: jwt-httponly, bearer, basic, oauth, hybrid
  autoInit: true            // ให้ AuthManager เริ่มทำงานอัตโนมัติเมื่อโหลดหน้า
}

ประเภทการยืนยันที่รองรับ:

ประเภท คำอธิบาย กรณีใช้งาน
jwt-httponly เก็บ JWT ไว้ในคุกกี้ HttpOnly แนะนำ ปลอดภัยที่สุด
bearer แนบ JWT ในส่วนหัว Authorization ลูกค้า API, แอปมือถือ
basic ใช้ Basic Authentication ระบบเรียบง่ายหรือระบบเก่า
oauth ใช้ขั้นตอน OAuth 2.0 การล็อกอินผ่านผู้ให้บริการภายนอก
hybrid ผสมผสานหลายรูปแบบ สถานการณ์ซับซ้อนที่ต้องรองรับหลายช่องทาง

ตัวอย่าง:

// ค่าตั้งต้น - เก็บ JWT ในคุกกี้ HttpOnly (แนะนำ)
await AuthManager.init({
  type: 'jwt-httponly'
});

// ใช้ Bearer token (เหมาะสำหรับ API)
await AuthManager.init({
  type: 'bearer'
});

// โหมด Hybrid (รองรับหลายรูปแบบพร้อมกัน)
await AuthManager.init({
  type: 'hybrid'
});

2. Endpoints Configuration

{
  endpoints: {
    login: 'api/v1/auth/login',           // ปลายทางสำหรับล็อกอิน
    logout: 'api/v1/auth/logout',         // ปลายทางสำหรับออกจากระบบ
    verify: 'api/v1/auth/verify',         // ปลายทางสำหรับตรวจสอบโทเค็น
    refresh: 'api/v1/auth/refresh',       // ปลายทางสำหรับรีเฟรชโทเค็น
    me: 'api/v1/auth/me',                 // ปลายทางดึงข้อมูลผู้ใช้ปัจจุบัน
    social: 'api/v1/auth/social/{provider}',  // ปลายทางสำหรับโซเชียลล็อกอิน
    callback: 'api/v1/auth/callback'      // ปลายทางรับ callback ของ OAuth
  }
}

รายละเอียดปลายทาง:

1. ปลายทางล็อกอิน

// POST /api/v1/auth/login
// ตัวอย่างคำขอ:
{
  "username": "user@example.com",
  "password": "secret123"
}

// ตัวอย่างคำตอบ:
{
  "success": true,
  "token": "eyJhbGciOiJIUzI1...",      // Access token ที่ออกใหม่
  "refresh_token": "eyJhbGciOiJIUz...",  // Refresh token (ถ้ามี)
  "user": {
    "id": 1,
    "name": "John Doe",
    "email": "user@example.com",
    "roles": ["user"],
    "permissions": ["read:posts"]
  }
}

2. ปลายทางออกจากระบบ

// POST /api/v1/auth/logout
// ตัวอย่างคำขอ: (ไม่ต้องมีเนื้อหาใน body)
// ส่วนหัวที่เกี่ยวข้อง:
{
  "Authorization": "Bearer eyJhbGciOiJIUzI1...",
  "X-CSRF-Token": "csrf-token-here"
}

// ตัวอย่างคำตอบ:
{
  "success": true,
  "message": "Logged out successfully"
}

3. ปลายทางตรวจสอบโทเค็น

// GET /api/v1/auth/verify
// ส่วนหัวที่ต้องแนบ:
{
  "Authorization": "Bearer eyJhbGciOiJIUzI1..."
}

// ตัวอย่างคำตอบ:
{
  "valid": true,
  "user": {
    "id": 1,
    "name": "John Doe"
  }
}

4. ปลายทางรีเฟรชโทเค็น

// POST /api/v1/auth/refresh
// ตัวอย่างคำขอ:
{
  "refresh_token": "eyJhbGciOiJIUzI1..."  // หรือส่งผ่านคุกกี้ HttpOnly
}

// ตัวอย่างคำตอบ:
{
  "success": true,
  "token": "eyJhbGciOiJIUzI1...",  // Access token ที่สร้างใหม่
  "expires_in": 900                 // อายุของโทเค็น (วินาที)
}

5. ปลายทางดึงข้อมูลผู้ใช้

// GET /api/v1/auth/me
// ส่วนหัวที่ต้องแนบ:
{
  "Authorization": "Bearer eyJhbGciOiJIUzI1..."
}

// ตัวอย่างคำตอบ:
{
  "id": 1,
  "name": "John Doe",
  "email": "user@example.com",
  "roles": ["user", "admin"],
  "permissions": ["read:posts", "write:posts"]
}

6. ปลายทางล็อกอินด้วยโซเชียล

// GET /api/v1/auth/social/google
// พารามิเตอร์ใน query:
{
  "redirect_uri": "http://localhost/auth/callback"
}

// ตัวอย่างคำตอบ: อาจเปลี่ยนเส้นทางไปยังผู้ให้บริการ OAuth
// หรือคืน URL สำหรับอนุญาตให้ผู้ใช้คลิกต่อ
{
  "authorization_url": "https://accounts.google.com/o/oauth2/v2/auth?..."
}

7. ปลายทางรับ Callback

// GET /api/v1/auth/callback
// พารามิเตอร์ใน query:
{
  "code": "authorization_code",
  "state": "random_state",
  "provider": "google"
}

// ตัวอย่างคำตอบ:
{
  "success": true,
  "token": "eyJhbGciOiJIUzI1...",
  "user": {
    "id": 1,
    "name": "John Doe",
    "email": "user@example.com"
  }
}

ปรับปลายทางเอง:

await AuthManager.init({
  endpoints: {
    login: '/auth/signin',          // เส้นทางล็อกอินที่กำหนดเอง
    logout: '/auth/signout',        // เส้นทางออกจากระบบที่กำหนดเอง
    verify: '/auth/check',          // เส้นทางตรวจสอบโทเค็นที่กำหนดเอง
    refresh: '/auth/token/refresh', // เส้นทางรีเฟรชโทเค็นที่กำหนดเอง
    me: '/auth/user/profile'        // เส้นทางดึงข้อมูลผู้ใช้ที่กำหนดเอง
  }
});

3. การตั้งค่าความปลอดภัย

{
  security: {
    // ป้องกัน CSRF
    csrf: true,                          // เปิดใช้งานการป้องกัน CSRF
    csrfIncludeSafeMethods: true,        // แนบ CSRF token กับคำขอ GET/HEAD ด้วย

    // จำกัดอัตราการลองเข้าสู่ระบบ
    rateLimiting: true,                  // เปิดใช้งานการจำกัดจำนวนครั้ง
    maxLoginAttempts: 5,                 // จำนวนความพยายามสูงสุดก่อนล็อก
    lockoutTime: 30 * 60 * 1000,        // ระยะเวลาล็อก (30 นาที)

    // รีเฟรชโทเค็นอัตโนมัติ
    autoRefresh: true,                   // เปิดการรีเฟรชอัตโนมัติ
    refreshBeforeExpiry: 5 * 60 * 1000  // รีเฟรชก่อนหมดอายุ 5 นาที
  }
}

การป้องกัน CSRF:

// เปิดใช้งานการป้องกัน CSRF
await AuthManager.init({
  security: {
    csrf: true,
    csrfIncludeSafeMethods: true  // แนบกับคำขอ GET/HEAD ด้วย
  }
});

// CSRF token จะถูกจัดการอัตโนมัติ:
// 1. อ่านจากแท็ก <meta name="csrf-token">
// 2. แนบในส่วนหัว X-CSRF-Token
// 3. ตรวจสอบความถูกต้องโดยเซิร์ฟเวอร์

การจำกัดอัตราการเข้าสู่ระบบ:

// จำกัดจำนวนครั้งการเข้าสู่ระบบ
await AuthManager.init({
  security: {
    rateLimiting: true,
    maxLoginAttempts: 5,           // ให้ลองได้สูงสุด 5 ครั้ง
    lockoutTime: 30 * 60 * 1000   // ล็อก 30 นาที
  }
});

// ตรวจสอบสถานะ rate limit ด้วยตนเอง
const canLogin = AuthManager.checkRateLimit('user@example.com');
if (!canLogin) {
  console.log('Too many login attempts. Please try again later.');
}

การรีเฟรชอัตโนมัติ:

// รีเฟรชโทเค็นก่อนหมดอายุ
await AuthManager.init({
  security: {
    autoRefresh: true,
    refreshBeforeExpiry: 5 * 60 * 1000  // รีเฟรชก่อนหมดอายุ 5 นาที
  }
});

// สั่งรีเฟรชเอง
const refreshed = await AuthManager.refreshToken();
if (refreshed) {
  console.log('Token refreshed successfully');
}

// ปิดการรีเฟรชอัตโนมัติ
await AuthManager.init({
  security: {
    autoRefresh: false
  }
});

4. การตั้งค่าการเปลี่ยนเส้นทาง

{
  redirects: {
    afterLogin: '/',            // เส้นทางเปลี่ยนหลังล็อกอินสำเร็จ
    afterLogout: '/login',      // เส้นทางหลังออกจากระบบ
    unauthorized: '/login',     // เส้นทางเมื่อไม่ผ่านการยืนยันตัวตน
    forbidden: '/403'           // เส้นทางเมื่อไม่มีสิทธิ์
  }
}

ตัวอย่าง:

await AuthManager.init({
  redirects: {
    afterLogin: '/dashboard',     // ส่งไปแดชบอร์ดหลังล็อกอิน
    afterLogout: '/login',        // ส่งไปหน้าเข้าสู่ระบบหลังออกจากระบบ
    unauthorized: '/login',       // ส่งไปหน้าล็อกอินเมื่อยังไม่ยืนยันตัวตน
    forbidden: '/403'             // ส่งไปหน้าข้อจำกัดสิทธิ์เมื่อไม่มีสิทธิ์เข้าถึง
  }
});

// เปลี่ยนเส้นทางด้วยตนเอง
AuthManager.redirectTo('/dashboard');

// บันทึก URL ปลายทางไว้ก่อนเปลี่ยนหน้า
AuthManager.saveIntendedUrl('/admin/settings');

// หลังล็อกอิน เรียกคืน URL ที่ตั้งใจไว้
const intendedUrl = AuthManager.getIntendedUrl();
if (intendedUrl) {
  AuthManager.redirectTo(intendedUrl);
}

5. การตั้งค่าโทเค็น

{
  token: {
    headerName: 'Authorization',        // ชื่อส่วนหัวที่ใช้แนบโทเค็น
    cookieName: 'auth_token',          // ชื่อคุกกี้สำหรับ access token
    refreshCookieName: 'refresh_token', // ชื่อคุกกี้สำหรับ refresh token
    storageKey: 'auth_user',           // คีย์เก็บข้อมูลผู้ใช้ใน LocalStorage

    cookieOptions: {
      path: '/',                       // ขอบเขตการใช้งานคุกกี้
      secure: true,                    // ใช้เฉพาะ HTTPS (ตรวจจับอัตโนมัติ)
      sameSite: 'Strict'              // ช่วยป้องกัน CSRF
    },

    cookieMaxAge: null                 // อายุคุกกี้ (null = หมดเมื่อปิดเบราว์เซอร์)
  }
}

การตั้งค่าคุกกี้:

await AuthManager.init({
  token: {
    cookieOptions: {
      path: '/',              // ใช้ได้ทุกเส้นทางภายในโดเมน
      secure: true,           // ทำงานเฉพาะ HTTPS
      sameSite: 'Strict',     // ป้องกัน CSRF อย่างเข้มงวด
      httpOnly: true          // ป้องกันไม่ให้สคริปต์เข้าถึง
    },
    cookieMaxAge: 7 * 24 * 60 * 60  // 7 วัน
  }
});

การเก็บข้อมูลผู้ใช้:

// เก็บข้อมูลผู้ใช้ใน LocalStorage
await AuthManager.init({
  token: {
    storageKey: 'auth_user'
  }
});

// อ่านข้อมูลผู้ใช้
const user = JSON.parse(localStorage.getItem('auth_user'));
console.log(user);

// ลบข้อมูลผู้ใช้
localStorage.removeItem('auth_user');

วิธีการยืนยันตัวตน

1. การเข้าสู่ระบบ

ตัวอย่างการเข้าสู่ระบบพื้นฐาน

// ล็อกอินพื้นฐาน
const result = await AuthManager.login({
  username: 'user@example.com',
  password: 'secret123'
});

if (result.success) {
  console.log('Login successful!');
  console.log('User:', result.user);
  // จะเปลี่ยนเส้นทางตามค่าที่ตั้งไว้ใน afterLogin อัตโนมัติ
} else {
  console.error('Login failed:', result.error);
}

การเข้าสู่ระบบพร้อมตัวเลือกเพิ่มเติม

// ล็อกอินพร้อมกำหนดตัวเลือก
const result = await AuthManager.login(
  {
    username: 'user@example.com',
    password: 'secret123'
  },
  {
    remember: true,              // จำผู้ใช้ (อายุโทเค็นจะยาวขึ้น)
    redirect: false,             // ไม่ให้เปลี่ยนเส้นทางอัตโนมัติ
    saveIntendedUrl: true       // บันทึก URL ปัจจุบันเพื่อใช้หลังล็อกอิน
  }
);

if (result.success) {
  // เปลี่ยนเส้นทางด้วยตนเอง
  window.location.href = '/custom-dashboard';
}

โครงสร้างผลลัพธ์จากการเข้าสู่ระบบ

{
  success: boolean,           // ระบุว่าสำเร็จหรือไม่
  user: {                     // อ็อบเจ็กต์ผู้ใช้ (เมื่อสำเร็จ)
    id: number,
    name: string,
    email: string,
    roles: string[],
    permissions: string[]
  },
  token: string,              // Access token (ถ้ามี)
  error: string,              // ข้อความผิดพลาด (เมื่อไม่สำเร็จ)
  code: string               // รหัสข้อผิดพลาด (เมื่อไม่สำเร็จ)
}

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

ตัวอย่างการออกจากระบบพื้นฐาน

// ออกจากระบบอย่างง่าย
await AuthManager.logout();

console.log('Logged out successfully');
// จะเปลี่ยนเส้นทางไปหน้าที่ตั้งไว้ใน afterLogout อัตโนมัติ

การออกจากระบบพร้อมตัวเลือก

// ออกจากระบบโดยไม่เรียกเซิร์ฟเวอร์
await AuthManager.logout(false);

// ออกจากระบบพร้อมกำหนดเส้นทางเอง
await AuthManager.logout(true, {
  redirect: '/goodbye'
});

// ออกจากระบบโดยไม่เปลี่ยนเส้นทาง
await AuthManager.logout(true, {
  redirect: false
});

พฤติกรรมเมื่อออกจากระบบ

// เมื่อออกจากระบบ AuthManager จะ:
// 1. เรียกปลายทาง logout (ถ้า callServer = true)
// 2. ลบโทเค็นออกจากคุกกี้
// 3. ล้างข้อมูลผู้ใช้จาก localStorage
// 4. รีเซ็ตสถานะการยืนยันตัวตน
// 5. ส่งเหตุการณ์ 'logout'
// 6. เปลี่ยนเส้นทางไปยัง afterLogout (ถ้า redirect !== false)
// 7. หยุดตัวจับเวลารีเฟรชอัตโนมัติ

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

// ตรวจสอบว่าผู้ใช้ล็อกอินอยู่หรือไม่
if (AuthManager.isAuthenticated()) {
  console.log('User is logged in');
} else {
  console.log('User is not logged in');
}

// ดึงข้อมูลผู้ใช้ปัจจุบัน
const user = AuthManager.getUser();
if (user) {
  console.log('Welcome,', user.name);
  console.log('Roles:', user.roles);
  console.log('Permissions:', user.permissions);
}

// ตรวจสอบสถานะกับเซิร์ฟเวอร์เพื่อยืนยันเพิ่มเติม
const isValid = await AuthManager.checkAuthStatus();
if (isValid) {
  console.log('Session is valid');
} else {
  console.log('Session expired or invalid');
}

4. การเข้าสู่ระบบด้วยโซเชียล

ล็อกอินผ่าน Google

// ล็อกอินด้วย Google
await AuthManager.socialLogin('google', {
  redirect_uri: 'http://localhost/auth/callback',
  scope: 'email profile'
});

// จะเปิดหน้าต่างหรือเปลี่ยนเส้นทางไปยังหน้า OAuth ของ Google

ล็อกอินผ่าน Facebook

// ล็อกอินด้วย Facebook
await AuthManager.socialLogin('facebook', {
  redirect_uri: 'http://localhost/auth/callback',
  scope: 'email public_profile'
});

จัดการ OAuth Callback

// ภายในหน้ารับ callback (เช่น /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,
    state: params.get('state')
  });

  if (result.success) {
    console.log('Social login successful!');
    window.location.href = '/dashboard';
  } else {
    console.error('Social login failed:', result.error);
    window.location.href = '/login';
  }
}

ผู้ให้บริการที่รองรับ

  • ✅ Google
  • ✅ Facebook
  • ✅ ผู้ให้บริการ OAuth ที่ปรับแต่งเอง

5. การเข้าสู่ระบบด้วยโทเค็นที่มีอยู่

// ใช้โทเค็นที่มีอยู่ (เช่น จากอีเมลยืนยัน)
const result = await AuthManager.loginWithToken('eyJhbGciOiJIUzI1...', {
  fetchUser: true  // ดึงข้อมูลผู้ใช้จากเซิร์ฟเวอร์หลังเข้าสู่ระบบ
});

if (result.success) {
  console.log('Logged in with token!');
  console.log('User:', result.user);
}

การจัดการโทเค็น

1. ดึง Access Token

// ดึง Access Token ปัจจุบัน
const token = await AuthManager.fetchAccessToken();

if (token) {
  console.log('Access token:', token);
} else {
  console.log('No access token available');
}

// ตรวจสอบให้แน่ใจว่าโทเค็นยังใช้ได้
const validToken = await AuthManager.ensureAccessToken(token);
console.log('Valid token:', validToken);

2. รีเฟรชโทเค็น

// รีเฟรชโทเค็นด้วยตนเอง
const refreshed = await AuthManager.refreshToken();

if (refreshed) {
  console.log('Token refreshed successfully');

  // ดึงโทเค็นใหม่
  const newToken = await AuthManager.fetchAccessToken();
  console.log('New token:', newToken);
} else {
  console.error('Token refresh failed');

  // จัดการออกจากระบบเมื่อรีเฟรชไม่สำเร็จ
  await AuthManager.logout(false);
  window.location.href = '/login';
}

3. ดึง Refresh Token

// ดึง Refresh Token (ถ้ามี)
const refreshToken = AuthManager.getRefreshToken();

if (refreshToken) {
  console.log('Refresh token available');
} else {
  console.log('No refresh token');
}

4. รีเฟรชอัตโนมัติ

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

// AuthManager จะ:
// 1. แยกข้อมูลโทเค็นเพื่อหาวันหมดอายุ
// 2. ตั้งตัวจับเวลาเพื่อรีเฟรชก่อนหมดอายุ
// 3. เรียก refreshToken() อัตโนมัติเมื่อถึงเวลา
// 4. อัปเดตโทเค็นในคุกกี้
// 5. ตั้งตัวจับเวลาใหม่สำหรับโทเค็นที่เพิ่งออก

// ปิดการรีเฟรชอัตโนมัติ
await AuthManager.init({
  security: {
    autoRefresh: false
  }
});

5. การทำงานร่วมกับ TokenService

// ภายใน AuthManager ใช้ TokenService จัดการรายละเอียดโทเค็น

// ดึงอินสแตนซ์ TokenService
const tokenService = AuthManager.tokenService;

// แยกข้อมูลจากโทเค็น
const payload = tokenService.parseToken(token);
console.log('Token payload:', payload);

// ตรวจสอบว่าโทเค็นหมดอายุหรือยัง
const expired = tokenService.isTokenExpired(token);
console.log('Token expired:', expired);

// ตรวจสอบเวลาหมดอายุ
const expiry = tokenService.getTokenExpiry(token);
console.log('Token expires at:', new Date(expiry * 1000));

// ดึงรหัสผู้ใช้จากโทเค็น
const userId = tokenService.getUserId(token);
console.log('User ID:', userId);

// ดึงบทบาทผู้ใช้จากโทเค็น
const roles = tokenService.getUserRoles(token);
console.log('User roles:', roles);

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

1. สถานะผู้ใช้

// ดึงผู้ใช้ปัจจุบัน
const user = AuthManager.getUser();

if (user) {
  console.log('User ID:', user.id);
  console.log('Name:', user.name);
  console.log('Email:', user.email);
  console.log('Roles:', user.roles);
  console.log('Permissions:', user.permissions);
}

// ตรวจสอบสถานะการยืนยันตัวตน
const authenticated = AuthManager.isAuthenticated();
console.log('Authenticated:', authenticated);

// ตรวจสอบสถานะภายในที่ AuthManager จัดเก็บ
console.log('Auth state:', AuthManager.state);
// {
//   initialized: true,
//   authenticated: true,
//   user: {...},
//   loading: false,
//   error: null,
//   loginAttempts: 0,
//   lockedUntil: null,
//   refreshTimer: 123
// }

2. การตรวจสอบสิทธิ์และบทบาท

// ตรวจสอบว่าสามารถใช้สิทธิ์ที่ระบุได้หรือไม่
if (AuthManager.hasPermission('edit:posts')) {
  console.log('User can edit posts');
  document.getElementById('editButton').style.display = 'block';
}

// ตรวจสอบว่ามีบทบาทที่ระบุหรือไม่
if (AuthManager.hasRole('admin')) {
  console.log('User is admin');
  document.getElementById('adminPanel').style.display = 'block';
}

// ตรวจสอบหลายสิทธิ์พร้อมกัน (ต้องได้ครบทุกสิทธิ์)
if (AuthManager.hasPermission(['edit:posts', 'delete:posts'])) {
  console.log('User can edit and delete posts');
}

// ตรวจสอบหลายบทบาท (มีบทบาทใดบทบาทหนึ่งก็พอ)
if (AuthManager.hasRole(['admin', 'moderator'])) {
  console.log('User is admin or moderator');
}

3. การจัดการ URL ที่ตั้งใจจะไป

// บันทึก URL ปลายทางก่อนเปลี่ยนหน้า
AuthManager.saveIntendedUrl('/admin/settings');

// หลังล็อกอินให้กลับไปยัง URL ที่ตั้งใจไว้
const intendedUrl = AuthManager.getIntendedUrl();
if (intendedUrl) {
  console.log('Redirecting to:', intendedUrl);
  AuthManager.redirectTo(intendedUrl);
} else {
  // หากไม่มีให้ใช้เส้นทางมาตรฐาน
  AuthManager.redirectTo('/dashboard');
}

// เคลียร์ค่า URL ที่ตั้งใจไว้เมื่อไม่ต้องใช้แล้ว
localStorage.removeItem('intended_url');

4. การล้างข้อมูลการยืนยันตัวตน

// ล้างข้อมูลยืนยันตัวตนทั้งหมด
AuthManager.clearAuthData();

// คำสั่งนี้จะ:
// 1. ลบโทเค็นออกจากคุกกี้
// 2. ลบข้อมูลผู้ใช้ออกจาก localStorage
// 3. รีเซ็ตสถานะกลับเป็นค่าเริ่มต้น
// 4. หยุดตัวจับเวลารีเฟรชอัตโนมัติ
// 5. ส่งเหตุการณ์ 'auth:cleared'

ฟีเจอร์ด้านความปลอดภัย

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

// การป้องกัน CSRF เปิดใช้งานเป็นค่าเริ่มต้น
await AuthManager.init({
  security: {
    csrf: true
  }
});

// ดึง CSRF token
const csrfToken = AuthManager.getCSRFToken();
console.log('CSRF Token:', csrfToken);

// อัปเดต CSRF token (จากการตอบกลับของเซิร์ฟเวอร์)
AuthManager.updateCSRFToken('new-csrf-token');

// CSRF token จะถูกจัดการอัตโนมัติ:
// 1. อ่านจากแท็ก <meta name="csrf-token">
// 2. แนบในส่วนหัว X-CSRF-Token ของทุกคำขอ
// 3. อัปเดตหลังคำขอสำเร็จ

ตัวอย่าง HTML:

<head>
  <meta name="csrf-token" content="<?php echo $csrfToken; ?>">
</head>

การตรวจสอบฝั่งเซิร์ฟเวอร์ (ตัวอย่าง PHP):

// Get CSRF token from request
$requestToken = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';

// Get CSRF token from session
$sessionToken = $_SESSION['csrf_token'] ?? '';

// Validate
if ($requestToken !== $sessionToken) {
    http_response_code(403);
    echo json_encode(['error' => 'CSRF token mismatch']);
    exit;
}

2. การจำกัดความถี่

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

// ตรวจสอบว่าสามารถให้ผู้ใช้ล็อกอินได้หรือไม่
const canLogin = AuthManager.checkRateLimit('user@example.com');

if (!canLogin) {
  console.log('Too many login attempts');
  alert('Account temporarily locked. Please try again later.');
  return;
}

// บันทึกผลการพยายามล็อกอิน
AuthManager.recordLoginAttempt('user@example.com', success);

// ตรวจสอบข้อมูลการจำกัดความถี่
console.log('Rate limit data:', AuthManager.rateLimitData);
// Map {
//   'user@example.com' => {
//     attempts: 3,
//     lockedUntil: null,
//     lastAttempt: 1234567890
//   }
// }

3. คุกกี้ HttpOnly

// ค่าเริ่มต้นคือเก็บโทเค็นในคุกกี้ HttpOnly
await AuthManager.init({
  type: 'jwt-httponly',
  token: {
    cookieOptions: {
      httpOnly: true,    // ป้องกันการเข้าถึงจากสคริปต์ (ลดความเสี่ยง XSS)
      secure: true,      // ใช้เฉพาะ HTTPS
      sameSite: 'Strict' // ป้องกัน CSRF
    }
  }
});

// ข้อดี:
// ✅ ลดความเสี่ยง XSS เพราะสคริปต์ไม่เห็นค่าในคุกกี้
// ✅ ส่งไปพร้อมทุกคำขอโดยอัตโนมัติ
// ✅ ไม่ต้องแนบโทเค็นลงในส่วนหัวเอง
// ✅ เซิร์ฟเวอร์ตรวจสอบโทเค็นจากคุกกี้ได้ทันที

// ฝั่งเซิร์ฟเวอร์ควรกำหนด Set-Cookie เช่น:
// Set-Cookie: auth_token=eyJhbG...; HttpOnly; Secure; SameSite=Strict; Path=/

4. แนวทางจัดการโทเค็นอย่างปลอดภัย

// แนวทางปฏิบัติที่ควรใช้ในการจัดการโทเค็น

// ✅ ควร: ใช้คุกกี้ HttpOnly
await AuthManager.init({
  type: 'jwt-httponly'
});

// ✅ ควร: ใช้ HTTPS ในสภาพแวดล้อมจริง
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
  location.replace(`https:${location.href.substring(location.protocol.length)}`);
}

// ✅ ควร: กำหนดอายุโทเค็นไม่ให้นานเกินไป
// ฝั่งเซิร์ฟเวอร์: Access token อายุ ~15 นาที, Refresh token ~7 วัน

// ✅ ควร: ออก Refresh token ใหม่ทุกครั้งที่รีเฟรช
// ฝั่งเซิร์ฟเวอร์: ส่ง Refresh token ใหม่กลับทุกครั้งที่ต่ออายุ

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

// ❌ ไม่ควร: ส่งโทเค็นผ่าน URL
// fetch('/api/users?token=' + token); // อาจถูกบันทึกใน log หรือ referrer

// ❌ ไม่ควร: ใช้โทเค็นที่มีอายุยาวเกินไป
// JWT expiry = 30 days // เสี่ยงถูกนำกลับมาใช้ซ้ำ

ระบบเหตุการณ์

AuthManager ส่งเหตุการณ์ต่างๆ เพื่อให้ผูกการทำงานเพิ่มเติมตามวงจรการยืนยันตัวตนได้

เหตุการณ์ที่มีให้ใช้งาน

// เหตุการณ์ด้านการยืนยันตัวตน
document.addEventListener('auth:login', (e) => {
  console.log('User logged in:', e.detail.user);
});

document.addEventListener('auth:logout', (e) => {
  console.log('User logged out');
});

document.addEventListener('auth:error', (e) => {
  console.log('Auth error:', e.detail.error);
});

// เหตุการณ์ด้านโทเค็น
document.addEventListener('auth:token:refreshed', (e) => {
  console.log('Token refreshed:', e.detail.token);
});

document.addEventListener('auth:token:expired', (e) => {
  console.log('Token expired');
});

// เหตุการณ์ด้านเซสชัน
document.addEventListener('auth:session:verified', (e) => {
  console.log('Session verified:', e.detail.user);
});

document.addEventListener('auth:session:invalid', (e) => {
  console.log('Session invalid');
});

// เหตุการณ์ด้านสถานะรวม
document.addEventListener('auth:state:changed', (e) => {
  console.log('Auth state changed:', e.detail.state);
});

การรับฟังเหตุการณ์

// ตัวอย่างตัวดักฟังเหตุการณ์แบบง่าย
document.addEventListener('auth:login', (e) => {
  const { user, timestamp } = e.detail;
  console.log('Login at', new Date(timestamp));
  console.log('User:', user.name);
});

// รับฟังหลายเหตุการณ์พร้อมกัน
['auth:login', 'auth:logout'].forEach(eventName => {
  document.addEventListener(eventName, (e) => {
    console.log('Auth event:', eventName, e.detail);
  });
});

// ถอดตัวดักฟังเหตุการณ์ออก
const handler = (e) => console.log('Login event', e.detail);
document.addEventListener('auth:login', handler);

// Later...
document.removeEventListener('auth:login', handler);

การส่งเหตุการณ์กำหนดเอง

// AuthManager มีเหตุการณ์ภายในอยู่แล้ว
// แต่ยังสามารถส่งเหตุการณ์เพิ่มเติมเองได้

AuthManager.emit('auth:custom', {
  message: 'Custom event',
  data: { foo: 'bar' }
});

// รับฟังเหตุการณ์ที่กำหนดเอง
document.addEventListener('auth:custom', (e) => {
  console.log('Custom event:', e.detail.message);
  console.log('Data:', e.detail.data);
});

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

1. ฟอร์มล็อกอินพร้อมการตรวจสอบข้อมูล

<!DOCTYPE html>
<html>
<head>
  <title>Login</title>
  <style>
    .error { color: red; }
    .loading { opacity: 0.5; pointer-events: none; }
  </style>
</head>
<body>
  <form id="loginForm">
    <div>
      <label>Email:</label>
      <input type="email" id="username" required>
    </div>
    <div>
      <label>Password:</label>
      <input type="password" id="password" required>
    </div>
    <div>
      <label>
        <input type="checkbox" id="remember">
        Remember me
      </label>
    </div>
    <button type="submit" id="submitBtn">Login</button>
    <div id="error" class="error"></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>
    (async () => {
      // ตั้งค่า AuthManager
      await AuthManager.init({
        enabled: true,
        endpoints: {
          login: '/api/v1/auth/login'
        },
        redirects: {
          afterLogin: '/dashboard'
        },
        security: {
          rateLimiting: true,
          maxLoginAttempts: 5
        }
      });

      // ตรวจสอบว่าเข้าสู่ระบบแล้วหรือยัง
      if (AuthManager.isAuthenticated()) {
        window.location.href = '/dashboard';
        return;
      }

      const form = document.getElementById('loginForm');
      const submitBtn = document.getElementById('submitBtn');
      const errorDiv = document.getElementById('error');

      form.addEventListener('submit', async (e) => {
        e.preventDefault();

        // ล้างข้อความผิดพลาดก่อนหน้า
        errorDiv.textContent = '';

        // อ่านข้อมูลจากฟอร์ม
        const username = document.getElementById('username').value.trim();
        const password = document.getElementById('password').value;
        const remember = document.getElementById('remember').checked;

        // ตรวจสอบข้อมูลเบื้องต้น
        if (!username || !password) {
          errorDiv.textContent = 'Please fill in all fields';
          return;
        }

        // ตรวจสอบรูปแบบอีเมล
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!emailRegex.test(username)) {
          errorDiv.textContent = 'Please enter a valid email';
          return;
        }

        // ตรวจสอบ rate limit
        if (!AuthManager.checkRateLimit(username)) {
          errorDiv.textContent = 'Too many login attempts. Please try again later.';
          return;
        }

        // แสดงสถานะกำลังประมวลผล
        form.classList.add('loading');
        submitBtn.textContent = 'Logging in...';
        submitBtn.disabled = true;

        try {
          // เรียกล็อกอิน
          const result = await AuthManager.login(
            { username, password },
            { remember }
          );

          if (result.success) {
            // สำเร็จ
            submitBtn.textContent = 'Success! Redirecting...';
            // ระบบจะเปลี่ยนเส้นทางให้อัตโนมัติ
          } else {
            // ไม่สำเร็จ
            errorDiv.textContent = result.error || 'Login failed';
            submitBtn.textContent = 'Login';
            submitBtn.disabled = false;
          }
        } catch (error) {
          errorDiv.textContent = 'An error occurred. Please try again.';
          console.error('Login error:', error);
          submitBtn.textContent = 'Login';
          submitBtn.disabled = false;
        } finally {
          form.classList.remove('loading');
        }
      });

      // รับฟังเหตุการณ์การยืนยันตัวตน
      document.addEventListener('auth:error', (e) => {
        errorDiv.textContent = e.detail.message || 'Authentication error';
      });
    })();
  </script>
</body>
</html>

2. หน้าแดชบอร์ดที่ป้องกันพร้อมปุ่มออกจากระบบ

<!DOCTYPE html>
<html>
<head>
  <title>Dashboard</title>
</head>
<body>
  <div id="dashboard">
    <h1>Welcome, <span id="userName"></span>!</h1>
    <p>Email: <span id="userEmail"></span></p>
    <p>Roles: <span id="userRoles"></span></p>

    <div id="adminPanel" style="display: none;">
      <h2>Admin Panel</h2>
      <p>You have admin access</p>
    </div>

    <button id="logoutBtn">Logout</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()) {
        // บันทึก URL ปัจจุบันไว้
        AuthManager.saveIntendedUrl(window.location.pathname);
        // ส่งไปหน้าล็อกอิน
        window.location.href = '/login';
        return;
      }

      // ตรวจสอบเซสชันกับเซิร์ฟเวอร์
      const valid = await AuthManager.checkAuthStatus();
      if (!valid) {
        alert('Your session has expired. Please login again.');
        await AuthManager.logout(false);
        window.location.href = '/login';
        return;
      }

      // ดึงข้อมูลผู้ใช้
      const user = AuthManager.getUser();

      // แสดงข้อมูลผู้ใช้บนหน้าแดชบอร์ด
      document.getElementById('userName').textContent = user.name;
      document.getElementById('userEmail').textContent = user.email;
      document.getElementById('userRoles').textContent = user.roles.join(', ');

      // แสดงส่วนผู้ดูแลเมื่อเป็นแอดมิน
      if (AuthManager.hasRole('admin')) {
        document.getElementById('adminPanel').style.display = 'block';
      }

      // จัดการปุ่มออกจากระบบ
      document.getElementById('logoutBtn').addEventListener('click', async () => {
        if (confirm('Are you sure you want to logout?')) {
          await AuthManager.logout();
          // ระบบจะส่งกลับไปหน้า /login ให้อัตโนมัติ
        }
      });

      // จัดการกรณีเซสชันหมดอายุ
      document.addEventListener('auth:token:expired', () => {
        alert('Your session has expired. Please login again.');
        window.location.href = '/login';
      });

      // จัดการข้อผิดพลาดเมื่อไม่มีสิทธิ์
      document.addEventListener('auth:error', (e) => {
        if (e.detail.errorType === 'UNAUTHORIZED') {
          alert('You are not authorized. Please login again.');
          window.location.href = '/login';
        }
      });
    })();
  </script>
</body>
</html>

3. การผสานการทำงานกับโซเชียลล็อกอิน

<!DOCTYPE html>
<html>
<head>
  <title>Login with Social</title>
  <style>
    .social-btn {
      display: block;
      width: 200px;
      padding: 10px;
      margin: 10px 0;
      cursor: pointer;
    }
    .google { background: #4285F4; color: white; }
    .facebook { background: #1877F2; color: white; }
    .github { background: #333; color: white; }
  </style>
</head>
<body>
  <h1>Login</h1>

  <div>
    <button class="social-btn google" id="googleLogin">
      Login with Google
    </button>
    <button class="social-btn facebook" id="facebookLogin">
      Login with Facebook
    </button>
    <button class="social-btn github" id="githubLogin">
      Login with GitHub
    </button>
  </div>

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

  <script>
    (async () => {
      // ตั้งค่า AuthManager
      await AuthManager.init({
        enabled: true,
        endpoints: {
          social: '/api/v1/auth/social/{provider}',
          callback: '/api/v1/auth/callback'
        },
        redirects: {
          afterLogin: '/dashboard'
        }
      });

      // ล็อกอินผ่าน Google
      document.getElementById('googleLogin').addEventListener('click', async () => {
        await AuthManager.socialLogin('google', {
          redirect_uri: window.location.origin + '/auth/callback',
          scope: 'email profile'
        });
      });

      // ล็อกอินผ่าน Facebook
      document.getElementById('facebookLogin').addEventListener('click', async () => {
        await AuthManager.socialLogin('facebook', {
          redirect_uri: window.location.origin + '/auth/callback',
          scope: 'email public_profile'
        });
      });

      // ล็อกอินผ่าน GitHub
      document.getElementById('githubLogin').addEventListener('click', async () => {
        await AuthManager.socialLogin('github', {
          redirect_uri: window.location.origin + '/auth/callback',
          scope: 'user:email'
        });
      });

      // จัดการ OAuth 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) {
          console.log('Processing OAuth callback...');

          const result = await AuthManager.handleAuthCallback({
            code,
            provider,
            state: params.get('state')
          });

          if (result.success) {
            console.log('Social login successful!');
            window.location.href = '/dashboard';
          } else {
            console.error('Social login failed:', result.error);
            alert('Social login failed: ' + result.error);
            window.location.href = '/login';
          }
        }
      }
    })();
  </script>
</body>
</html>

4. การควบคุมสิทธิ์ตามบทบาท RBAC

// ตัวอย่าง RBAC แบบครบวงจร
(async () => {
  await AuthManager.init({ enabled: true });

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

  const user = AuthManager.getUser();

  // กำหนดส่วนต่างๆ ของ UI ตามบทบาท
  const rolePermissions = {
    admin: ['view:dashboard', 'edit:users', 'delete:users', 'view:reports'],
    moderator: ['view:dashboard', 'edit:posts', 'delete:posts'],
    user: ['view:dashboard', 'edit:own-posts']
  };

  // ฟังก์ชันช่วยตรวจสอบสิทธิ์
  function hasAnyPermission(permissions) {
    return permissions.some(perm => AuthManager.hasPermission(perm));
  }

  // ซ่อนหรือแสดงส่วนต่างๆ ตามบทบาท
  if (AuthManager.hasRole('admin')) {
    document.getElementById('adminPanel').style.display = 'block';
    document.getElementById('userManagement').style.display = 'block';
    document.getElementById('reports').style.display = 'block';
  } else if (AuthManager.hasRole('moderator')) {
    document.getElementById('moderatorPanel').style.display = 'block';
    document.getElementById('contentManagement').style.display = 'block';
  }

  // ตรวจสอบสิทธิ์เฉพาะทาง
  if (AuthManager.hasPermission('edit:users')) {
    document.querySelectorAll('.edit-user-btn').forEach(btn => {
      btn.style.display = 'inline-block';
    });
  }

  if (AuthManager.hasPermission('delete:users')) {
    document.querySelectorAll('.delete-user-btn').forEach(btn => {
      btn.style.display = 'inline-block';
    });
  }
})();

5. การเชื่อมต่อ API พร้อมการแนบการยืนยันตัวตนอัตโนมัติ

// AuthManager ทำงานร่วมกับ ApiService/HttpClient ให้โดยอัตโนมัติ
(async () => {
  await AuthManager.init({ enabled: true });

  // เรียก API ที่ต้องยืนยันตัวตน
  // โทเค็นจะถูกแนบในส่วนหัวให้อัตโนมัติ
  const response = await fetch('/api/users', {
    method: 'GET',
    credentials: 'include'  // แนบคุกกี้ไปด้วย
  });

  if (response.status === 401) {
    // โทเค็นหมดอายุ - AuthManager จะจัดการให้
    console.log('Token expired - refreshing...');

    // ตัวสกัดกั้นของ AuthManager จะ:
    // 1. พยายามรีเฟรชโทเค็น
    // 2. ส่งคำขอเดิมอีกครั้งด้วยโทเค็นใหม่
    // 3. หรือออกจากระบบหากรีเฟรชไม่สำเร็จ
  } else if (response.ok) {
    const data = await response.json();
    console.log('Users:', data);
  }
})();

// ตัวอย่าง ApiService (แนะนำ)
await ApiService.init({
  baseURL: '/api',
  auth: {
    enabled: true,
    manager: AuthManager
  }
});

// ทุกคำขอจะมีโทเค็นถูกแนบให้อัตโนมัติ
const users = await ApiService.get('/users');
console.log(users.data);

// สร้างโพสต์ (ต้องยืนยันตัวตน)
const post = await ApiService.post('/posts', {
  title: 'My Post',
  content: 'Post content'
});
console.log(post.data);

6. ตัวจัดการเซสชันหมดเวลา

// จัดการกรณีเซสชันหมดเวลา
(async () => {
  await AuthManager.init({ enabled: true });

  const SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes
  const WARNING_TIME = 5 * 60 * 1000;     // 5 minutes before timeout

  let sessionTimer;
  let warningTimer;

  function resetSessionTimer() {
    // ล้างตัวจับเวลาทั้งหมดก่อนเริ่มใหม่
    clearTimeout(sessionTimer);
    clearTimeout(warningTimer);

    // ตั้งเวลาสำหรับแจ้งเตือนก่อนหมดเวลา
    warningTimer = setTimeout(() => {
      const remaining = Math.floor(WARNING_TIME / 1000 / 60);
      const continueSession = confirm(
        `Your session will expire in ${remaining} minutes. Do you want to continue?`
      );

      if (continueSession) {
        // รีเฟรชโทเค็นเพื่อยืดอายุเซสชัน
        AuthManager.refreshToken();
        resetSessionTimer();
      }
    }, SESSION_TIMEOUT - WARNING_TIME);

    // ตั้งเวลาหมดอายุเซสชันจริง
    sessionTimer = setTimeout(() => {
      alert('Your session has expired. Please login again.');
      AuthManager.logout();
    }, SESSION_TIMEOUT);
  }

  // รีเซ็ตตัวจับเวลาเมื่อผู้ใช้มีการเคลื่อนไหว
  ['click', 'keypress', 'scroll', 'mousemove'].forEach(event => {
    document.addEventListener(event, () => {
      if (AuthManager.isAuthenticated()) {
        resetSessionTimer();
      }
    }, { passive: true });
  });

  // เริ่มจับเวลาเมื่อผู้ใช้ล็อกอิน
  if (AuthManager.isAuthenticated()) {
    resetSessionTimer();
  }

  // ล้างตัวจับเวลาทั้งหมดเมื่อออกจากระบบ
  document.addEventListener('auth:logout', () => {
    clearTimeout(sessionTimer);
    clearTimeout(warningTimer);
  });
})();

เอกสารอ้างอิง API

เมธอดที่ใช้งานบ่อย

init(options)

ตั้งค่า AuthManager พร้อมข้อมูลกำหนดค่า

พารามิเตอร์:

  • options (Object, ไม่บังคับ) – อ็อบเจ็กต์ค่ากำหนด

คืนค่า: Promise<void>

ตัวอย่าง:

await AuthManager.init({
  enabled: true,
  type: 'jwt-httponly',
  endpoints: {
    login: '/api/auth/login',
    logout: '/api/auth/logout'
  }
});

isInitialized()

ตรวจสอบว่า AuthManager ถูกตั้งค่าพร้อมใช้งานหรือยัง

คืนค่า: boolean – เป็นจริงเมื่อเตรียมพร้อมแล้ว

ตัวอย่าง:

if (AuthManager.isInitialized()) {
  console.log('AuthManager is ready');
}

login(credentials, options)

เข้าสู่ระบบด้วยข้อมูลประจำตัว

พารามิเตอร์:

  • credentials (Object) – ข้อมูลสำหรับล็อกอิน
    • username (string) – ชื่อผู้ใช้หรืออีเมล
    • password (string) – รหัสผ่าน
  • options (Object, ไม่บังคับ) – ตัวเลือกเพิ่มเติม
    • remember (boolean) – จำผู้ใช้ (ค่าเริ่มต้น: false)
    • redirect (boolean|string) – เปลี่ยนเส้นทางหลังล็อกอิน (ค่าเริ่มต้น: true)
    • saveIntendedUrl (boolean) – บันทึก URL ปลายทาง (ค่าเริ่มต้น: false)

คืนค่า: Promise<Object> – ผลลัพธ์การเข้าสู่ระบบ

{
  success: boolean,
  user: Object,
  token: string,
  error: string,
  code: string
}

ตัวอย่าง:

const result = await AuthManager.login({
  username: 'user@example.com',
  password: 'secret123'
}, {
  remember: true,
  redirect: '/dashboard'
});

if (result.success) {
  console.log('Login successful');
}

logout(callServer, options)

ออกจากระบบผู้ใช้ปัจจุบัน

พารามิเตอร์:

  • callServer (boolean, ไม่บังคับ) – เรียกปลายทางออกจากระบบหรือไม่ (ค่าเริ่มต้น: true)
  • options (Object, ไม่บังคับ) – ตัวเลือกเพิ่มเติม
    • redirect (boolean|string) – เปลี่ยนเส้นทางหลังออกจากระบบ (ค่าเริ่มต้น: true)

คืนค่า: Promise<void>

ตัวอย่าง:

// Logout with server call
await AuthManager.logout();

// Logout without server call
await AuthManager.logout(false);

// Logout with custom redirect
await AuthManager.logout(true, {
  redirect: '/goodbye'
});

isAuthenticated()

ตรวจสอบว่าผู้ใช้ล็อกอินอยู่หรือไม่

คืนค่า: boolean – เป็นจริงเมื่อผ่านการยืนยันตัวตน

ตัวอย่าง:

if (AuthManager.isAuthenticated()) {
  console.log('User is logged in');
}

getUser()

ดึงอ็อบเจ็กต์ข้อมูลผู้ใช้ปัจจุบัน

คืนค่า: Object|null – ข้อมูลผู้ใช้หรือ null เมื่อไม่พบ

ตัวอย่าง:

const user = AuthManager.getUser();
if (user) {
  console.log('User:', user.name);
  console.log('Roles:', user.roles);
}

checkAuthStatus()

ตรวจสอบสถานะการยืนยันตัวตนกับเซิร์ฟเวอร์

คืนค่า: Promise<boolean> – เป็นจริงเมื่อเซสชันยังถูกต้อง

ตัวอย่าง:

const valid = await AuthManager.checkAuthStatus();
if (!valid) {
  console.log('Session expired');
  await AuthManager.logout(false);
}

refreshToken()

รีเฟรช Access Token

คืนค่า: Promise<boolean> – เป็นจริงเมื่อรีเฟรชสำเร็จ

ตัวอย่าง:

const refreshed = await AuthManager.refreshToken();
if (refreshed) {
  console.log('Token refreshed');
} else {
  console.log('Refresh failed');
  await AuthManager.logout(false);
}

hasRole(role)

ตรวจสอบว่าผู้ใช้มีบทบาทที่ระบุหรือไม่

พารามิเตอร์:

  • role (string|Array) – รายชื่อบทบาทที่ต้องการตรวจสอบ

คืนค่า: boolean – เป็นจริงเมื่อผู้ใช้มีบทบาทอย่างน้อยหนึ่งรายการ

ตัวอย่าง:

// Single role
if (AuthManager.hasRole('admin')) {
  console.log('User is admin');
}

// Multiple roles (OR logic)
if (AuthManager.hasRole(['admin', 'moderator'])) {
  console.log('User is admin or moderator');
}

hasPermission(permission)

ตรวจสอบว่าผู้ใช้มีสิทธิ์ที่กำหนดหรือไม่

พารามิเตอร์:

  • permission (string|Array) – รายชื่อสิทธิ์ที่ต้องการตรวจสอบ

คืนค่า: boolean – เป็นจริงเมื่อผู้ใช้มีสิทธิ์ครบตามที่กำหนด

ตัวอย่าง:

// Single permission
if (AuthManager.hasPermission('edit:posts')) {
  console.log('User can edit posts');
}

// Multiple permissions (AND logic)
if (AuthManager.hasPermission(['edit:posts', 'delete:posts'])) {
  console.log('User can edit and delete posts');
}

socialLogin(provider, options)

เข้าสู่ระบบผ่านผู้ให้บริการโซเชียล

พารามิเตอร์:

  • provider (string) – ชื่อผู้ให้บริการ เช่น google, facebook, github
  • options (Object, ไม่บังคับ) – ตัวเลือกเพิ่มเติม
    • redirect_uri (string) – URL ปลายทางสำหรับ callback
    • scope (string) – ช่วงสิทธิ์ที่ร้องขอจากผู้ให้บริการ
    • state (string) – ค่าที่ใช้ตรวจสอบการตอบกลับ

คืนค่า: Promise<void>

ตัวอย่าง:

await AuthManager.socialLogin('google', {
  redirect_uri: 'http://localhost/auth/callback',
  scope: 'email profile'
});

handleAuthCallback(callbackData)

จัดการข้อมูลตอบกลับจาก OAuth

พารามิเตอร์:

  • callbackData (Object) – ข้อมูลที่ได้รับจากผู้ให้บริการ
    • code (string) – รหัสอนุญาต
    • provider (string) – ชื่อผู้ให้บริการ
    • state (string, ไม่บังคับ) – ค่าตรวจสอบความถูกต้อง

คืนค่า: Promise<Object> – ผลลัพธ์หลังประมวลผล

ตัวอย่าง:

const result = await AuthManager.handleAuthCallback({
  code: params.get('code'),
  provider: params.get('provider')
});

if (result.success) {
  window.location.href = '/dashboard';
}

loginWithToken(token, options)

เข้าสู่ระบบด้วยโทเค็นที่มีอยู่

พารามิเตอร์:

  • token (string) – Access token ที่ได้มาล่วงหน้า
    options (Object, ไม่บังคับ) – ตัวเลือกเพิ่มเติม
    • fetchUser (boolean) – ดึงข้อมูลผู้ใช้หลังเข้าสู่ระบบ (ค่าเริ่มต้น: true)
    • redirect (boolean|string) – เปลี่ยนเส้นทางหลังเข้าสู่ระบบ (ค่าเริ่มต้น: true)

คืนค่า: Promise<Object> – ผลการเข้าสู่ระบบ

ตัวอย่าง:

const result = await AuthManager.loginWithToken('eyJhbGciOiJIUzI1...', {
  fetchUser: true,
  redirect: '/dashboard'
});

getCSRFToken()

ดึง CSRF token ปัจจุบัน

คืนค่า: string|null – ค่า CSRF token หรือ null

ตัวอย่าง:

const csrfToken = AuthManager.getCSRFToken();
console.log('CSRF Token:', csrfToken);

updateCSRFToken(token)

อัปเดต CSRF token

พารามิเตอร์:

  • token (string) – ค่า CSRF token ใหม่

คืนค่า: void

ตัวอย่าง:

AuthManager.updateCSRFToken('new-csrf-token');

saveIntendedUrl(url)

บันทึก URL เพื่อกลับมาหลังล็อกอินสำเร็จ

พารามิเตอร์:

  • url (string, ไม่บังคับ) – URL ที่ต้องการบันทึก (ค่าเริ่มต้น: URL ปัจจุบัน)

คืนค่า: void

ตัวอย่าง:

// Save current URL
AuthManager.saveIntendedUrl();

// Save specific URL
AuthManager.saveIntendedUrl('/admin/settings');

getIntendedUrl()

อ่าน URL ที่บันทึกไว้

คืนค่า: string|null – URL ที่บันทึกหรือ null

ตัวอย่าง:

const intendedUrl = AuthManager.getIntendedUrl();
if (intendedUrl) {
  AuthManager.redirectTo(intendedUrl);
}

redirectTo(url)

เปลี่ยนหน้าไป URL ที่กำหนดด้วย Router หรือ window.location

พารามิเตอร์:

  • url (string) – เส้นทางที่ต้องการเปลี่ยนไป

คืนค่า: void

ตัวอย่าง:

AuthManager.redirectTo('/dashboard');

clearAuthData()

ล้างข้อมูลการยืนยันตัวตนทั้งหมด

คืนค่า: void

ตัวอย่าง:

AuthManager.clearAuthData();

emit(eventName, data)

ส่งเหตุการณ์กำหนดเอง

พารามิเตอร์:

  • eventName (string) – ชื่อเหตุการณ์ (ควรขึ้นต้นด้วย auth:)
  • data (Object, ไม่บังคับ) – ข้อมูลประกอบเหตุการณ์

คืนค่า: void

ตัวอย่าง:

AuthManager.emit('auth:custom', {
  message: 'Custom event',
  data: { foo: 'bar' }
});

checkRateLimit(identifier)

ตรวจสอบว่า identifier ถูกจำกัดความถี่หรือไม่

พารามิเตอร์:

  • identifier (string) – ตัวระบุ เช่น อีเมลหรือชื่อผู้ใช้

คืนค่า: boolean – เป็นจริงเมื่อยังสามารถดำเนินการได้

ตัวอย่าง:

const canLogin = AuthManager.checkRateLimit('user@example.com');
if (!canLogin) {
  alert('Too many login attempts');
}

recordLoginAttempt(identifier, success)

บันทึกความพยายามในการเข้าสู่ระบบ

พารามิเตอร์:

  • identifier (string) – ตัวระบุ เช่น อีเมลหรือชื่อผู้ใช้
  • success (boolean) – ผลลัพธ์ว่าล็อกอินสำเร็จหรือไม่

คืนค่า: void

ตัวอย่าง:

AuthManager.recordLoginAttempt('user@example.com', false);

Properties

config

อ็อบเจ็กต์ค่ากำหนดปัจจุบัน

ชนิด: Object

ตัวอย่าง:

console.log(AuthManager.config);
// {
//   enabled: true,
//   type: 'jwt-httponly',
//   endpoints: {...},
//   security: {...},
//   redirects: {...},
//   token: {...}
// }

state

สถานะการยืนยันตัวตนปัจจุบัน

ชนิด: Object

ตัวอย่าง:

console.log(AuthManager.state);
// {
//   initialized: true,
//   authenticated: true,
//   user: {...},
//   loading: false,
//   error: null,
//   loginAttempts: 0,
//   lockedUntil: null,
//   refreshTimer: 123
// }

tokenService

อินสแตนซ์ของ TokenService ที่ใช้ร่วมกัน

ชนิด: TokenService

ตัวอย่าง:

const tokenService = AuthManager.tokenService;
const payload = tokenService.parseToken(token);

เหตุการณ์ที่ส่งออก

ชื่อเหตุการณ์ คำอธิบาย ข้อมูลใน detail
auth:login ผู้ใช้เข้าสู่ระบบ user, timestamp
auth:logout ผู้ใช้ออกจากระบบ timestamp
auth:error เกิดข้อผิดพลาดด้านการยืนยัน error, errorType, message
auth:token:refreshed โทเค็นถูกรีเฟรช token, timestamp
auth:token:expired โทเค็นหมดอายุ timestamp
auth:session:verified เซสชันได้รับการยืนยัน user, timestamp
auth:session:invalid เซสชันไม่ถูกต้อง timestamp
auth:state:changed สถานะรวมเปลี่ยนไป state, timestamp

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

1. ตั้งค่าให้เรียบร้อยก่อนใช้งานเสมอ

// ✅ ดี: ตั้งค่า AuthManager ก่อนใช้งาน
(async () => {
  await AuthManager.init({ enabled: true });

  if (AuthManager.isAuthenticated()) {
    console.log('User is logged in');
  }
})();

// ❌ ไม่ดี: ใช้งานโดยยังไม่ได้ตั้งค่า
if (AuthManager.isAuthenticated()) {  // อาจทำงานผิดพลาด
  console.log('User is logged in');
}

2. จัดการข้อผิดพลาดอย่างเหมาะสม

// ✅ ดี: ครอบคลุมการจัดการข้อผิดพลาด
try {
  const result = await AuthManager.login(credentials);

  if (result.success) {
    console.log('Login successful');
  } else {
    // ตรวจสอบประเภทข้อผิดพลาด
    switch (result.code) {
      case 'INVALID_CREDENTIALS':
        alert('Invalid username or password');
        break;
      case 'ACCOUNT_LOCKED':
        alert('Your account has been locked');
        break;
      default:
        alert(result.error || 'Login failed');
    }
  }
} catch (error) {
  console.error('Login error:', error);
  alert('An error occurred. Please try again.');
}

// ❌ ไม่ดี: ไม่ตรวจสอบข้อผิดพลาดเลย
const result = await AuthManager.login(credentials);
if (result.success) {
  console.log('Success');
}

3. ตรวจสอบเซสชันก่อนทำงานที่สำคัญ

// ✅ ดี: ตรวจสอบก่อนดำเนินการสำคัญ
async function deleteUser(userId) {
  // Verify session first
  const valid = await AuthManager.checkAuthStatus();

  if (!valid) {
    alert('Your session has expired. Please login again.');
    await AuthManager.logout(false);
    return;
  }

  // Proceed with deletion
  const response = await ApiService.delete(`/users/${userId}`);

  if (response.ok) {
    console.log('User deleted');
  }
}

// ❌ ไม่ดี: ไม่ตรวจสอบเซสชัน
async function deleteUser(userId) {
  const response = await ApiService.delete(`/users/${userId}`);
  // May fail if session expired
}

4. ใช้ตัวดักฟังเหตุการณ์เพื่ออัปเดตสถานะ

// ✅ ดี: ฟังเหตุการณ์ของ AuthManager
document.addEventListener('auth:login', (e) => {
  console.log('User logged in:', e.detail.user);
  // Update UI
  updateUserInterface(e.detail.user);
});

document.addEventListener('auth:logout', () => {
  console.log('User logged out');
  // Clear UI
  clearUserInterface();
});

document.addEventListener('auth:token:expired', () => {
  alert('Your session has expired');
  window.location.href = '/login';
});

// ❌ ไม่ดี: ตรวจสอบบ่อยๆ โดยไม่จำเป็น
setInterval(() => {
  if (AuthManager.isAuthenticated()) {
    // Update UI
  }
}, 1000);  // Inefficient!

5. ควบคุมการลองเข้าสู่ระบบจากฝั่งไคลเอนต์

// ✅ ดี: ตรวจสอบ rate limit ก่อนเรียกล็อกอิน
async function handleLogin(credentials) {
  // Check rate limit
  if (!AuthManager.checkRateLimit(credentials.username)) {
    alert('Too many login attempts. Please try again later.');
    return;
  }

  const result = await AuthManager.login(credentials);

  // Record attempt
  AuthManager.recordLoginAttempt(credentials.username, result.success);

  if (result.success) {
    console.log('Login successful');
  }
}

// ❌ ไม่ดี: ไม่ควบคุมจำนวนความพยายาม
async function handleLogin(credentials) {
  const result = await AuthManager.login(credentials);
  // User can spam login attempts
}

6. ล้างข้อมูลสำคัญเมื่อออกจากระบบ

// ✅ ดี: ล้างข้อมูลทั้งหมดเมื่อออกจากระบบ
async function handleLogout() {
  // Clear form data
  document.querySelectorAll('input').forEach(input => {
    input.value = '';
  });

  // Clear cached data
  sessionStorage.clear();

  // Logout
  await AuthManager.logout();

  // Clear browser history
  if (history.replaceState) {
    history.replaceState(null, '', '/login');
  }
}

// ❌ Bad - leave sensitive data
async function handleLogout() {
  await AuthManager.logout();
  // Form data, cache, history still contain sensitive info
}

7. ใช้ HTTPS ในสภาพแวดล้อมจริงเสมอ

// ✅ ดี: บังคับให้ใช้ HTTPS
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
  location.replace(`https:${location.href.substring(location.protocol.length)}`);
}

await AuthManager.init({
  token: {
    cookieOptions: {
      secure: true  // ส่งคุกกี้เฉพาะผ่าน HTTPS
    }
  }
});

// ❌ ไม่ดี: อนุญาตให้ใช้ HTTP ในระบบจริง
await AuthManager.init({
  token: {
    cookieOptions: {
      secure: false  // เสี่ยงสูง!
    }
  }
});

8. กำหนดเวลาหมดอายุของเซสชัน

// ✅ ดี: มีการแจ้งเตือนก่อนหมดเวลา
const SESSION_TIMEOUT = 30 * 60 * 1000;  // 30 minutes
let sessionTimer;

function resetSessionTimer() {
  clearTimeout(sessionTimer);

  sessionTimer = setTimeout(() => {
    alert('Your session has expired');
    AuthManager.logout();
  }, SESSION_TIMEOUT);
}

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

// เริ่มจับเวลาเมื่อมีการล็อกอิน
if (AuthManager.isAuthenticated()) {
  resetSessionTimer();
}

// ❌ ไม่ดี: ไม่กำหนดหมดเวลา
// ทำให้เซสชันอยู่ตลอดและเสี่ยงด้านความปลอดภัย

ข้อผิดพลาดที่พบบ่อย

1. ไม่ตรวจสอบการตั้งค่าก่อนใช้งาน

// ❌ ไม่ดี: ใช้งานโดยไม่ตรวจสอบ
if (AuthManager.isAuthenticated()) {
  console.log('Logged in');
}

// ✅ ดี: ตรวจสอบสถานะการตั้งค่าก่อน
if (AuthManager.isInitialized() && AuthManager.isAuthenticated()) {
  console.log('Logged in');
}

// ✅ ดีกว่า: รอให้ตั้งค่าเสร็จก่อน
(async () => {
  await AuthManager.init();

  if (AuthManager.isAuthenticated()) {
    console.log('Logged in');
  }
})();

2. เก็บโทเค็นไว้ใน localStorage

// ❌ ไม่ดี: เสี่ยงถูกโจมตี XSS
localStorage.setItem('token', token);
localStorage.setItem('refresh_token', refreshToken);

// ✅ ดี: ใช้คุกกี้ HttpOnly
await AuthManager.init({
  type: 'jwt-httponly',
  token: {
    cookieOptions: {
      httpOnly: true,
      secure: true,
      sameSite: 'Strict'
    }
  }
});

3. ไม่จัดการกรณีโทเค็นหมดอายุ

// ❌ ไม่ดี: ไม่รองรับกรณีหมดอายุ
const response = await fetch('/api/users', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

if (response.status === 401) {
  alert('Unauthorized');
}

// ✅ ดี: ปล่อยให้ AuthManager รีเฟรชให้อัตโนมัติผ่าน interceptor
const response = await ApiService.get('/users');

if (!response.ok) {
  console.error('Error:', response.error);
}

4. เปลี่ยนเส้นทางวนลูปไม่รู้จบ

// ❌ ไม่ดี: ทำให้เกิดการวนลูป
if (!AuthManager.isAuthenticated()) {
  window.location.href = '/login';
}

// ภายในหน้าล็อกอิน:
if (AuthManager.isAuthenticated()) {
  window.location.href = '/dashboard';
}

// ✅ ดี: ตรวจสอบเส้นทางปัจจุบันก่อนเปลี่ยน
if (!AuthManager.isAuthenticated() && window.location.pathname !== '/login') {
  window.location.href = '/login';
}

if (AuthManager.isAuthenticated() && window.location.pathname === '/login') {
  window.location.href = '/dashboard';
}

5. ไม่ตรวจสอบข้อมูลที่ผู้ใช้กรอก

// ❌ ไม่ดี: ไม่ตรวจสอบเลย
async function handleLogin(e) {
  e.preventDefault();

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

// ✅ ดี: ตรวจสอบข้อมูลก่อนส่ง
async function handleLogin(e) {
  e.preventDefault();

  const username = document.getElementById('username').value.trim();
  const password = document.getElementById('password').value;

  // Validation
  if (!username || !password) {
    alert('Please fill in all fields');
    return;
  }

  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(username)) {
    alert('Please enter a valid email');
    return;
  }

  if (password.length < 8) {
    alert('Password must be at least 8 characters');
    return;
  }

  const result = await AuthManager.login({ username, password });
}

6. ไม่จัดการข้อผิดพลาดจากเครือข่าย

// ❌ ไม่ดี: ไม่รองรับข้อผิดพลาดจากเครือข่าย
const result = await AuthManager.login(credentials);

if (result.success) {
  console.log('Success');
} else {
  alert(result.error);
}

// ✅ ดี: รองรับข้อผิดพลาดเครือข่าย
try {
  const result = await AuthManager.login(credentials);

  if (result.success) {
    console.log('Success');
  } else {
    alert(result.error || 'Login failed');
  }
} catch (error) {
  if (error.message.includes('network') || error.message.includes('fetch')) {
    alert('Network error. Please check your connection.');
  } else {
    alert('An error occurred. Please try again.');
  }
  console.error('Login error:', error);
}

ข้อควรคำนึงเรื่องประสิทธิภาพ

1. ลดจำนวนครั้งที่ตรวจสอบสถานะการยืนยันตัวตน

// ❌ ไม่ดี: ตรวจสอบทุกคำขอ
async function makeRequest(url) {
  if (!AuthManager.isAuthenticated()) {
    window.location.href = '/login';
    return;
  }

  const valid = await AuthManager.checkAuthStatus();
  if (!valid) {
    await AuthManager.logout();
    return;
  }

  return fetch(url);
}

// ✅ ดี: ตรวจสอบตอนโหลดหน้าและให้ interceptor จัดการคำขอถัดไป
(async () => {
  await AuthManager.init();

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

  // No need to check again on every request
  // Interceptors handle 401 errors automatically
})();

2. แคชข้อมูลผู้ใช้

// ❌ ไม่ดี: เรียกข้อมูลผู้ใช้ทุกครั้งที่ต้องใช้
async function getUserName() {
  const response = await fetch('/api/auth/me');
  const user = await response.json();
  return user.name;
}

document.getElementById('userName').textContent = await getUserName();
document.getElementById('userEmail').textContent = await getUserName();  // Redundant!

// ✅ ดี: ใช้ข้อมูลผู้ใช้ที่เก็บไว้ใน AuthManager
const user = AuthManager.getUser();
document.getElementById('userName').textContent = user.name;
document.getElementById('userEmail').textContent = user.email;

3. ป้องกันการรีเฟรชโทเค็นซ้ำซ้อน

// ✅ ดี: AuthManager ป้องกันการรีเฟรชซ้ำให้อยู่แล้ว
// คำขอหลายรายการพร้อมกันจะไม่รีเฟรชซ้ำ

// ❌ ไม่ดี: รีเฟรชเองโดยไม่ป้องกันการเรียกซ้ำ
async function makeAuthenticatedRequest(url) {
  const token = await AuthManager.fetchAccessToken();

  if (TokenService.isTokenExpired(token)) {
    await AuthManager.refreshToken();  // Called multiple times!
  }

  return fetch(url, {
    headers: { 'Authorization': `Bearer ${token}` }
  });
}

4. จัดการตัวดักฟังเหตุการณ์ให้มีประสิทธิภาพ

// ❌ ไม่ดี: เพิ่มตัวดักฟังหลายตัวซ้ำๆ
function initAuthListeners() {
  document.addEventListener('auth:login', handleLogin);
  document.addEventListener('auth:login', updateUI);
  document.addEventListener('auth:login', logEvent);
  // Called multiple times = multiple listeners!
}

// ✅ ดี: ใช้ตัวดักฟังตัวเดียวแล้วกระจายงานภายใน
let authListenersInitialized = false;

function initAuthListeners() {
  if (authListenersInitialized) return;

  document.addEventListener('auth:login', (e) => {
    handleLogin(e);
    updateUI(e);
    logEvent(e);
  });

  authListenersInitialized = true;
}

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

1. ป้องกัน XSS

// ✅ ใช้คุกกี้ HttpOnly
await AuthManager.init({
  type: 'jwt-httponly',
  token: {
    cookieOptions: {
      httpOnly: true  // JavaScript cannot access
    }
  }
});

// ✅ กำจัดอักขระอันตรายก่อนแสดงผล
function sanitize(str) {
  const div = document.createElement('div');
  div.textContent = str;
  return div.innerHTML;
}

const user = AuthManager.getUser();
document.getElementById('userName').textContent = sanitize(user.name);

// ❌ ห้ามใช้ innerHTML กับข้อมูลผู้ใช้โดยตรง
// document.getElementById('userName').innerHTML = user.name;  // XSS risk!

2. ป้องกัน CSRF

// ✅ เปิดใช้การป้องกัน CSRF
await AuthManager.init({
  security: {
    csrf: true,
    csrfIncludeSafeMethods: true
  }
});

// ✅ ฝั่งเซิร์ฟเวอร์ต้องตรวจสอบ CSRF token
// ตัวอย่าง PHP:
// if ($_SERVER['HTTP_X_CSRF_TOKEN'] !== $_SESSION['csrf_token']) {
//   http_response_code(403);
//   die('CSRF token mismatch');
// }

// ❌ ห้ามปิดการป้องกัน CSRF
// await AuthManager.init({ security: { csrf: false } });  // เสี่ยงมาก!

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

// ✅ ใช้ Access Token ที่มีอายุสั้น
// เซิร์ฟเวอร์: JWT หมดอายุภายใน ~15 นาที

// ✅ ให้ Refresh Token มีอายุมากกว่า
// เซิร์ฟเวอร์: Refresh token หมดอายุ ~7 วัน

// ✅ ออก Refresh Token ใหม่ทุกครั้งที่รีเฟรช
// Server: ออก Token ใหม่ทุกครั้งที่รีเฟรช

// ✅ ตรวจสอบโทเค็นฝั่งเซิร์ฟเวอร์
// Server: ตรวจสอบ ลายเซ็น วันหมดอายุ ผู้ออก ฯลฯ

// ❌ ห้ามใช้อายุโทเค็นยาวเกินไป
// เซิร์ฟเวอร์: JWT หมดอายุ 30 วัน  // ยาวเกินไป!

// ❌ ห้ามใช้ Refresh Token ตัวเดิมซ้ำ
// เซิร์ฟเวอร์: ไม่ควรส่งค่าเดิมกลับมา  // เสี่ยงต่อการถูกนำกลับมาใช้

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

// ✅ บังคับใช้รหัสผ่านที่รัดกุม (ฝั่งไคลเอนต์)
function validatePassword(password) {
  if (password.length < 8) {
    return 'Password must be at least 8 characters';
  }

  if (!/[A-Z]/.test(password)) {
    return 'Password must contain uppercase letter';
  }

  if (!/[a-z]/.test(password)) {
    return 'Password must contain lowercase letter';
  }

  if (!/\d/.test(password)) {
    return 'Password must contain number';
  }

  if (!/[!@#$%^&*]/.test(password)) {
    return 'Password must contain special character';
  }

  return null;
}

// ✅ แฮ็ชรหัสผ่านบนเซิร์ฟเวอร์
// ตัวอย่าง (Node.js): const hashedPassword = await bcrypt.hash(password, 10);

// ❌ ห้ามส่งรหัสผ่านผ่าน GET
// fetch('/login?password=' + password);  // มีโอกาสถูกบันทึกใน log!

// ❌ ห้ามเก็บรหัสผ่านเป็นข้อความล้วน (ฝั่งเซิร์ฟเวอร์)
// db.query('INSERT INTO users (password) VALUES (?)', [password]);  // อันตราย!

5. การจำกัดความถี่

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

// ✅ เพิ่มการจำกัดความถี่ฝั่งเซิร์ฟเวอร์ (ตัวอย่าง Express)
// const rateLimit = require('express-rate-limit');
// const loginLimiter = rateLimit({
//   windowMs: 15 * 60 * 1000,
//   max: 5
// });
// app.post('/api/auth/login', loginLimiter, loginHandler);

// ❌ ห้ามพึ่งการจำกัดความถี่เฉพาะฝั่งไคลเอนต์
// เซิร์ฟเวอร์ต้องมีระบบป้องกันด้วย

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

// ✅ กำหนดเวลาหมดอายุของเซสชัน
const SESSION_TIMEOUT = 30 * 60 * 1000;
let sessionTimer;

function resetSessionTimer() {
  clearTimeout(sessionTimer);
  sessionTimer = setTimeout(() => {
    AuthManager.logout();
  }, SESSION_TIMEOUT);
}

document.addEventListener('click', resetSessionTimer);

// ✅ ล้างเซสชันเมื่อออกจากระบบ
await AuthManager.logout();  // Clears tokens, user data

// ✅ ตรวจสอบเซสชันก่อนงานสำคัญ
const valid = await AuthManager.checkAuthStatus();
if (!valid) {
  await AuthManager.logout(false);
}

// ❌ ห้ามปล่อยให้เซสชันอยู่ตลอดไป
// ไม่มีหมดเวลา = เสี่ยงถูกยึดเซสชัน

การรองรับเบราว์เซอร์

AuthManager รองรับเบราว์เซอร์รุ่นปัจจุบันดังนี้:

  • ✅ Chrome 90+
  • ✅ Firefox 88+
  • ✅ Safari 14+
  • ✅ Edge 90+
  • ❌ IE 11 (ไม่รองรับ)

ฟีเจอร์ที่จำเป็น:

  • Promises และ async/await
  • Fetch API
  • ฟีเจอร์ ES6+ (arrow function, destructuring ฯลฯ)
  • คุกกี้แบบ HttpOnly
  • Web Storage API (localStorage)
  • Custom Events

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