Now.js Framework Documentation
AuthManager - ระบบจัดการการยืนยันตัวตนหลัก
AuthManager - ระบบจัดการการยืนยันตัวตนหลัก
เอกสารฉบับนี้อธิบาย AuthManager ซึ่งเป็นระบบจัดการการยืนยันตัวตนหลักของ Now.js Framework ครอบคลุมการตั้งค่า การใช้งาน และแนวปฏิบัติสำคัญทั้งหมดสำหรับการดูแลวงจรการยืนยันตัวตนของผู้ใช้
📋 สารบัญ
- ภาพรวม
- การติดตั้งและนำเข้า
- การเริ่มต้นใช้งาน
- การตั้งค่า
- วิธีการยืนยันตัวตน
- การจัดการโทเค็น
- การจัดการเซสชัน
- ฟีเจอร์ด้านความปลอดภัย
- ระบบเหตุการณ์
- ตัวอย่างการใช้งาน
- เอกสารอ้างอิง API
- แนวทางปฏิบัติที่แนะนำ
- ข้อผิดพลาดที่พบบ่อย
- ประสิทธิภาพ
- ความปลอดภัยเชิงลึก
ภาพรวม
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';
}
}ผู้ให้บริการที่รองรับ
- ✅ ผู้ให้บริการ 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, githuboptions(Object, ไม่บังคับ) – ตัวเลือกเพิ่มเติมredirect_uri(string) – URL ปลายทางสำหรับ callbackscope(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
เอกสารที่เกี่ยวข้อง
- Authentication.md – ภาพรวมระบบยืนยันตัวตน
- TokenService.md – การจัดการ JWT
- AuthGuard.md – การปกป้องเส้นทาง
- AuthErrorHandler.md – การจัดการข้อผิดพลาด
- AuthLoadingManager.md – การจัดการสถานะการโหลด
- Router.md – ระบบเส้นทางของ Now.js
- ApiService.md – บริการเรียก HTTP