Now.js Framework Documentation

Now.js Framework Documentation

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

TH 31 Oct 2025 01:40

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

เอกสารนี้อธิบาย TokenService ซึ่งเป็นบริการระดับล่างสำหรับจัดการโทเค็น JWT ให้กับ Now.js Framework ครอบคลุมการแยก วิเคราะห์ ตรวจสอบ และจัดเก็บโทเค็น รวมถึงการดึงข้อมูลผู้ใช้จาก payload

📋 สารบัญ

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

ภาพรวม

TokenService เป็น Low-level service ที่จัดการ JWT tokens ทั้งหมด รวมถึง parsing, validation, storage, และ extraction ของข้อมูลจาก token payload

ฟีเจอร์หลัก

  • การแยกโทเค็น (JWT Parsing): แยกสตริง JWT ออกเป็น header/payload/signature พร้อมถอดรหัส payload
  • การตรวจสอบโทเค็น (Token Validation): ตรวจสอบความถูกต้อง อายุการใช้งาน และโครงสร้างของโทเค็น
  • การจัดการที่เก็บข้อมูล: ควบคุมการเก็บโทเค็นทั้งในคุกกี้และ localStorage
  • การทำงานกับคุกกี้: สร้าง อ่าน และลบคุกกี้ (รองรับการตั้งค่า httpOnly ทางฝั่งเซิร์ฟเวอร์)
  • การดึงข้อมูลผู้ใช้: อ่านข้อมูลผู้ใช้จาก payload ตามมาตรฐาน JWT เช่น sub, roles
  • การตรวจสอบวันหมดอายุ: เช็กเวลาที่โทเค็นจะหมดอายุเพื่อสั่งรีเฟรชได้ทัน
  • รองรับ Base64URL: ถอดรหัส/เข้ารหัสรูปแบบ Base64URL ที่ JWT ใช้
  • เลือกวิธีเก็บข้อมูลได้หลายแบบ: สลับระหว่างคุกกี้หรือ localStorage ตามความเหมาะสม

เมื่อไรควรใช้ TokenService

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

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

ไม่จำเป็นต้องใช้เมื่อ:

  • ใช้งานผ่าน AuthManager อยู่แล้ว (ภายใน AuthManager เรียกใช้ TokenService ให้)
  • ไม่ต้องจัดการโทเค็นในระดับ low-level ด้วยตนเอง

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

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

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

store(token, additionalData)

จัดเก็บโทเค็นตามวิธีที่ตั้งค่าไว้

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

  • token (string) - โทเค็น JWT ที่ต้องการเก็บ
  • additionalData (Object, ไม่บังคับ) - ข้อมูลเพิ่มเติม (เช่น อ็อบเจ็กต์ผู้ใช้)

คืนค่า: boolean - true หากจัดเก็บสำเร็จ

ตัวอย่าง:
storageMethod: 'cookie',
cookieName: 'my_token'
});


---

## การเริ่มต้นใช้งาน
#### `remove()`
ลบโทเค็นออกจากที่เก็บ

**คืนค่า:** `boolean` - `true` หากลบสำเร็จ

**ตัวอย่าง:**

// แยกโทเค็น
const payload = tokenService.parseToken(token);
console.log('User ID:', payload.sub);
console.log('Expiry:', new Date(payload.exp * 1000));

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

**คืนค่า:** `boolean` - `true` หากล้างสำเร็จ

**ตัวอย่าง:**
const myTokenService = new TokenService({
  storageMethod: 'cookie',      // เลือก 'cookie' หรือ 'localStorage'
  cookieName: 'auth_token',     // ชื่อคุกกี้ของ access token
  refreshCookieName: 'refresh_token',  // ชื่อคุกกี้ของ refresh token
  localStorageKey: 'auth',      // ชื่อ key ใน localStorage

#### `getUser()`
ดึงข้อมูลผู้ใช้ที่จัดเก็บไว้ (ใช้ได้เมื่อเก็บใน `localStorage`)

**คืนค่า:** `Object|null` - อ็อบเจ็กต์ผู้ใช้ หรือ `null`

**ตัวอย่าง:**

// Use custom instance
const payload = myTokenService.parseToken(token);

การตั้งค่า

isAuthenticated()

ตรวจสอบว่าผู้ใช้ล็อกอินอยู่หรือไม่ (มีโทเค็นที่ยังใช้ได้)

คืนค่า: boolean - true หากยังเข้าสู่ระบบอยู่

ตัวอย่าง:

// ชื่อคุกกี้
cookieName: 'auth_token',
refreshCookieName: 'refresh_token',

// ชื่อ key ใน localStorage
localStorageKey: 'auth',

setCookie(name, value, options)

สร้างหรืออัปเดตคุกกี้

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

  • name (string) - ชื่อคุกกี้
  • value (string) - ค่าที่ต้องการเก็บ
  • options (Object, ไม่บังคับ) - การตั้งค่าเพิ่มเติมของคุกกี้
    • path (string) - ขอบเขตเส้นทางที่คุกกี้มีผล
    • secure (boolean) - ส่งผ่าน HTTPS เท่านั้นหรือไม่
    • sameSite (string) - 'Strict', 'Lax', หรือ 'None'
    • maxAge (number) - อายุสูงสุด (วินาที)
    • expires (Date) - วันที่หมดอายุ

คืนค่า: void

ตัวอย่าง:

2. ดึงบทบาทผู้ใช้ (Roles)

cookieOptions: {
// ดึงรายชื่อบทบาทจากโทเค็น
secure: true, // ใช้ผ่าน HTTPS เท่านั้น
sameSite: 'Strict' // ป้องกัน CSRF
// อาจได้ค่าตัวอย่าง: ['user', 'admin']
// หากไม่มีข้อมูลจะได้อาร์เรย์ว่าง []

// ตรวจสอบว่ามีบทบาทใดบทบาทหนึ่งหรือไม่
tokenService.store(token, { user: userData });

getCookie(name)

อ่านค่าคุกกี้

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

  • name (string) - ชื่อคุกกี้

คืนค่า: string|null - ค่าของคุกกี้ หรือ null

ตัวอย่าง:
ข้อเสีย:
// ฟิลด์มาตรฐาน

  • ❌ หากเป็น httpOnly จะอ่านด้วย JavaScript ไม่ได้

2. การเก็บด้วย localStorage

// ฟิลด์ที่กำหนดเอง
const tokenService = new TokenService({
storageMethod: 'localStorage',
localStorageKey: 'auth'
});

// ฟิลด์อื่น ๆ ที่กำหนดใน JWT
tokenService.store(token, { user: userData });

// ดึงโทเค็นออกมาใช้งาน
const storedToken = tokenService.getToken();
console.log('Token:', storedToken);


**ข้อดี:**
- ✅ มีพื้นที่มากกว่า (~5–10 MB)
- ✅ เข้าถึงได้โดย JavaScript ทุกเมื่อ
- ✅ ข้อมูลอยู่คงอยู่ข้ามการเปิดปิดเบราว์เซอร์

**ข้อเสีย:**
- ❌ เสี่ยงต่อการถูกโจมตีแบบ XSS
- ❌ ไม่ถูกส่งไปพร้อมคำร้องขอโดยอัตโนมัติ
- ❌ ใช้ร่วมกันได้ทุกแท็บของโดเมนเดียวกัน

---

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

```javascript
{
  path: '/',              // ให้คุกกี้ใช้งานได้ทุกเส้นทาง
  secure: true,           // ส่งผ่าน HTTPS เท่านั้น
  sameSite: 'Strict',     // เลือก 'Strict', 'Lax', หรือ 'None'
  maxAge: 3600,          // อายุสูงสุด (วินาที)
  expires: new Date()     // วันหมดอายุ
}

ค่า SameSite ที่รองรับ:

ค่า คำอธิบาย กรณีใช้งาน
Strict ส่งคุกกี้เฉพาะคำร้องจากโดเมนเดียวกัน แนะนำ ป้องกัน CSRF สูงสุด
Lax ส่งคุกกี้เมื่อมีการนำทางระดับบน สมดุลระหว่างความปลอดภัยกับการใช้งาน
None ส่งคุกกี้ทุกคำร้อง (ต้องตั้ง secure: true) เหมาะกับคำร้อง cross-site หรือ API

ตัวอย่าง:

const tokenService = new TokenService({
  storageMethod: 'cookie',
  cookieOptions: {
    path: '/',
    secure: location.protocol === 'https:',  // ตรวจว่าเป็น HTTPS อัตโนมัติ
    sameSite: 'Strict',                       // ลดความเสี่ยง CSRF
    maxAge: 15 * 60                          // อายุ 15 นาที
  }
});

การทำงานกับโทเค็น JWT

1. แยกโทเค็น JWT

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';

// แยกโทเค็นเพื่ออ่าน payload
const payload = tokenService.parseToken(token);

console.log(payload);
// {
//   sub: "1234567890",
//   name: "John Doe",
//   iat: 1516239022,
//   exp: 1516242622
// }

โครงสร้างของโทเค็น:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9  <- Header (Base64URL)
.
eyJzdWIiOiIxMjM0NTY3ODkwIiwi...        <- Payload (Base64URL)
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJ...  <- Signature (Base64URL)

ตัวอย่าง Payload:

{
  "sub": "1234567890",      // Subject (User ID)
  "name": "John Doe",       // User name
  "email": "john@example.com",
  "roles": ["user", "admin"],
  "permissions": ["read:posts", "write:posts"],
  "iat": 1516239022,        // Issued at (timestamp)
  "exp": 1516242622         // Expiry (timestamp)
}

2. ตรวจสอบรูปแบบโทเค็น

// ตรวจสอบว่าเป็นโทเค็น JWT ที่ถูกต้องหรือไม่
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

const payload = tokenService.parseToken(token);

if (payload) {
  console.log('Valid JWT token');
  console.log('User:', payload.name);
} else {
  console.log('Invalid JWT token');
}

// หากไม่ใช่โทเค็นที่ถูกต้องจะคืนค่า null
const invalidToken = 'not-a-jwt-token';
const result = tokenService.parseToken(invalidToken);
console.log(result); // null

3. อ่านเวลาหมดอายุของโทเค็น

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

// ดึงค่า timestamp (วินาที) ที่โทเค็นจะหมดอายุ
const expiry = tokenService.getTokenExpiry(token);

if (expiry) {
  const expiryDate = new Date(expiry * 1000);
  console.log('Token expires at:', expiryDate);

  // คำนวณเวลาที่เหลือ
  const now = Date.now();
  const remaining = (expiry * 1000) - now;
  const minutes = Math.floor(remaining / 1000 / 60);

  console.log('Time remaining:', minutes, 'minutes');
} else {
  console.log('Token has no expiry or is invalid');
}

การตรวจสอบโทเค็น

1. ตรวจสอบว่าโทเค็นหมดอายุหรือไม่

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

// ตรวจสอบว่าโทเค็นหมดอายุแล้วหรือยัง
const expired = tokenService.isTokenExpired(token);

if (expired) {
  console.log('Token has expired');
  // ควรรีเฟรชโทเค็นหรือให้ผู้ใช้ล็อกอินใหม่
} else {
  console.log('Token is still valid');
  // สามารถนำโทเค็นไปใช้เรียก API ได้
}

โค้ดภายในที่ใช้ตรวจสอบ:

// การทำงานของ isTokenExpired
const payload = tokenService.parseToken(token);
if (!payload || !payload.exp) {
  return true; // ถ้าไม่มี exp ให้ถือว่าหมดอายุ
}

const now = Math.floor(Date.now() / 1000);
return now >= payload.exp;

2. ตรวจสอบว่าโทเค็นใกล้หมดอายุหรือยัง

// ตรวจสอบว่าโทเค็นจะหมดอายุภายใน 5 นาทีหรือไม่
function isTokenNearExpiry(token, minutes = 5) {
  const expiry = tokenService.getTokenExpiry(token);

  if (!expiry) {
    return true;
  }

  const now = Math.floor(Date.now() / 1000);
  const remaining = expiry - now;
  const threshold = minutes * 60;

  return remaining <= threshold;
}

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

if (isTokenNearExpiry(token, 5)) {
  console.log('Token expires soon - refresh recommended');
  // เรียกกระบวนการรีเฟรชโทเค็น
}

3. ตรวจสอบโครงสร้างของโทเค็น

// ตรวจสอบโทเค็นอย่างครบถ้วน
function validateToken(token) {
  // ตรวจสอบรูปแบบพื้นฐาน
  if (!token || typeof token !== 'string') {
    return { valid: false, reason: 'Invalid token format' };
  }

  // ตรวจจำนวนส่วนของ JWT (ต้องมี 3 ส่วน)
  const parts = token.split('.');
  if (parts.length !== 3) {
    return { valid: false, reason: 'Invalid JWT structure' };
  }

  // แยกโทเค็นอ่านค่า payload
  const payload = tokenService.parseToken(token);
  if (!payload) {
    return { valid: false, reason: 'Cannot parse token' };
  }

  // ตรวจสอบว่ามีฟิลด์สำคัญหรือไม่
  if (!payload.sub && !payload.id) {
    return { valid: false, reason: 'Missing user ID' };
  }

  // ตรวจวันหมดอายุ
  if (tokenService.isTokenExpired(token)) {
    return { valid: false, reason: 'Token expired' };
  }

  return { valid: true, payload };
}

// การใช้งาน
const result = validateToken(token);

if (result.valid) {
  console.log('Token is valid');
  console.log('User ID:', result.payload.sub);
} else {
  console.error('Token validation failed:', result.reason);
}

การจัดการที่เก็บข้อมูล

1. จัดเก็บโทเค็น

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
const userData = {
  id: 1,
  name: 'John Doe',
  email: 'john@example.com',
  roles: ['user']
};

// จัดเก็บโทเค็นพร้อมข้อมูลผู้ใช้เพิ่มเติม
const stored = tokenService.store(token, { user: userData });

if (stored) {
  console.log('Token stored successfully');
} else {
  console.error('Failed to store token');
}

ลักษณะการจัดเก็บ:

กรณีเก็บด้วยคุกกี้:

// เก็บโทเค็นลงคุกกี้ (ฝั่ง JS จะสร้างคุกกี้แบบไม่ใช่ httpOnly)
// โครงสร้างโดยประมาณ: auth_token=eyJhbGc...
tokenService.setCookie('auth_token', token, {
  path: '/',
  secure: true,
  sameSite: 'Strict',
  maxAge: 900  // 15 นาที
});

กรณีเก็บด้วย localStorage:

// เก็บทั้งโทเค็นและข้อมูลผู้ใช้ไว้ใน localStorage
// โครงสร้าง: { token: '...', user: {...} }
localStorage.setItem('auth', JSON.stringify({
  token: token,
  user: userData
}));

2. ดึงโทเค็นที่จัดเก็บไว้

// ดึงโทเค็นที่มีอยู่
const token = tokenService.getToken();

if (token) {
  console.log('Token found:', token);

  // ตรวจสอบว่าโทเค็นยังใช้ได้หรือไม่
  if (!tokenService.isTokenExpired(token)) {
    console.log('Token is valid');
  } else {
    console.log('Token expired');
  }
} else {
  console.log('No token found');
}

หมายเหตุ: หากใช้คุกกี้แบบ httpOnly เมธอด getToken() จะคืนค่า null เพราะ JavaScript ไม่สามารถอ่านคุกกี้ประเภทนี้ได้ ต้องให้ฝั่งเซิร์ฟเวอร์ตรวจสอบจากค่าใน header

3. ดึงข้อมูลผู้ใช้ที่จัดเก็บไว้

// ดึงข้อมูลผู้ใช้ (ใช้ได้เมื่อเก็บใน localStorage)
const user = tokenService.getUser();

if (user) {
  console.log('User:', user.name);
  console.log('Email:', user.email);
  console.log('Roles:', user.roles);
} else {
  console.log('No user data found');
}

4. ลบโทเค็นออกจากที่เก็บ

// ลบโทเค็นออกจากที่เก็บ
const removed = tokenService.remove();

if (removed) {
  console.log('Token removed successfully');
} else {
  console.error('Failed to remove token');
}

// ตรวจสอบอีกครั้งว่าโทเค็นถูกลบแล้ว
const token = tokenService.getToken();
console.log('Token after removal:', token); // null

5. ล้างข้อมูลทั้งหมด

// ล้างข้อมูลการยืนยันตัวตนทั้งหมด
const cleared = tokenService.clear();

if (cleared) {
  console.log('All data cleared successfully');
}

// เมธอดนี้จะล้าง:
// - access token
// - refresh token
// - ข้อมูลผู้ใช้
// - ข้อมูลการยืนยันตัวตนอื่น ๆ ที่เก็บไว้

การทำงานกับคุกกี้

1. ตั้งค่าคุกกี้

// ตั้งค่าคุกกี้พร้อมออปชัน
tokenService.setCookie('auth_token', token, {
  path: '/',
  secure: true,
  sameSite: 'Strict',
  maxAge: 900  // 15 นาที
});

// หรือกำหนดวันหมดอายุแบบระบุวันที่
tokenService.setCookie('auth_token', token, {
  path: '/',
  secure: true,
  sameSite: 'Strict',
  expires: new Date(Date.now() + 15 * 60 * 1000)
});

2. อ่านค่าคุกกี้

// อ่านค่าจากคุกกี้
const token = tokenService.getCookie('auth_token');

if (token) {
  console.log('Token:', token);
} else {
  console.log('Cookie not found');
}

// หมายเหตุ: ไม่สามารถอ่านคุกกี้ที่ตั้งค่า httpOnly ได้
const httpOnlyToken = tokenService.getCookie('httponly_token');
console.log(httpOnlyToken); // null (if httpOnly flag is set)

3. ลบคุกกี้

// ลบคุกกี้ด้วยการตั้งค่า maxAge = 0
tokenService.setCookie('auth_token', '', {
  path: '/',
  maxAge: 0
});

// หรือกำหนดวันหมดอายุเป็นอดีต
tokenService.setCookie('auth_token', '', {
  path: '/',
  expires: new Date(0)
});

// ตรวจสอบว่าลบแล้วจริงหรือไม่
const token = tokenService.getCookie('auth_token');
console.log('Token after deletion:', token); // null

การดึงข้อมูลผู้ใช้

1. ดึงรหัสผู้ใช้ (User ID)

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

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

console.log('User ID:', userId);
// ตามมาตรฐาน JWT จะใช้ฟิลด์ 'sub'
// หากไม่มีจะลองใช้ฟิลด์ 'id'

2. ดึงบทบาทผู้ใช้ (Roles)

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

// ดึงรายชื่อบทบาทจากโทเค็น
const roles = tokenService.getUserRoles(token);

console.log('User roles:', roles);
// ตัวอย่างค่าที่อาจได้รับ: ['user', 'admin']
// หากไม่มีข้อมูลจะได้อาร์เรย์ว่าง []

// ตรวจสอบว่ามีบทบาทที่ต้องการหรือไม่
if (roles.includes('admin')) {
  console.log('User is admin');
}

3. ดึงข้อมูล Claims อื่น ๆ

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

// แยกโทเค็นเพื่ออ่านค่าสิทธิ์ทั้งหมด
const payload = tokenService.parseToken(token);

if (payload) {
  // ฟิลด์มาตรฐาน
  console.log('User ID:', payload.sub);
  console.log('Name:', payload.name);
  console.log('Email:', payload.email);
  console.log('Issued at:', new Date(payload.iat * 1000));
  console.log('Expires at:', new Date(payload.exp * 1000));

  // ฟิลด์ที่กำหนดเอง
  console.log('Roles:', payload.roles);
  console.log('Permissions:', payload.permissions);
  console.log('Subscription:', payload.subscription);
  console.log('Organization:', payload.org);

  // ฟิลด์อื่น ๆ ที่กำหนดใน JWT
  console.log('Custom field:', payload.customField);
}

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

1. Basic Token Validation

// ตรวจสอบโทเค็นก่อนนำไปใช้งาน
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

// แยกโทเค็นเพื่อตรวจสอบรูปแบบ
const payload = tokenService.parseToken(token);

if (!payload) {
  console.error('Invalid token format');
  return;
}

// ตรวจสอบวันหมดอายุ
if (tokenService.isTokenExpired(token)) {
  console.error('Token has expired');
  return;
}

// ดึงข้อมูลผู้ใช้ที่จำเป็น
const userId = tokenService.getUserId(token);
const roles = tokenService.getUserRoles(token);

console.log('Valid token!');
console.log('User ID:', userId);
console.log('Roles:', roles);

// ใช้โทเค็นในการเรียก API
fetch('/api/users', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
})
.then(response => response.json())
.then(data => console.log('API response:', data));
// เริ่มต้นใช้งานโดยให้เก็บโทเค็นในคุกกี้
const tokenService = new TokenService({
  storageMethod: 'cookie',
  cookieName: 'auth_token',
  cookieOptions: {
    path: '/',
    secure: true,
    sameSite: 'Strict'
  }
});

// ฟังก์ชันล็อกอินและจัดเก็บโทเค็น
async function handleLogin(credentials) {
  const response = await fetch('/api/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(credentials)
  });

  const data = await response.json();

  if (data.success) {
    // เก็บโทเค็นลงคุกกี้
    tokenService.store(data.token, { user: data.user });

    console.log('Login successful!');
    console.log('Token stored in cookie');

    // เปลี่ยนหน้าไปแดชบอร์ด
    window.location.href = '/dashboard';
  }
}

// ตรวจสอบสถานะล็อกอินเมื่อโหลดหน้า
function checkAuth() {
  const token = tokenService.getToken();

  if (!token) {
    console.log('No token found - user not logged in');
    window.location.href = '/login';
    return;
  }

  if (tokenService.isTokenExpired(token)) {
    console.log('Token expired - please login again');
    tokenService.clear();
    window.location.href = '/login';
    return;
  }

  console.log('User is authenticated');
}

// เรียกใช้งานเมื่อหน้าโหลดเสร็จ
checkAuth();

3. Token Storage with localStorage

// เริ่มต้นใช้งานโดยเก็บโทเค็นใน localStorage
const tokenService = new TokenService({
  storageMethod: 'localStorage',
  localStorageKey: 'auth'
});

// ล็อกอินแล้วจัดเก็บโทเค็น
async function handleLogin(credentials) {
  const response = await fetch('/api/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(credentials)
  });

  const data = await response.json();

  if (data.success) {
    // เก็บโทเค็นไว้ใน localStorage
    tokenService.store(data.token, { user: data.user });

    console.log('Login successful!');
    console.log('Token stored in localStorage');

    // ทดสอบดึงข้อมูลที่จัดเก็บไว้
    const storedToken = tokenService.getToken();
    const storedUser = tokenService.getUser();

    console.log('Stored token:', storedToken);
    console.log('Stored user:', storedUser);
  }
}

// โหลดข้อมูลผู้ใช้เมื่อหน้าเพจพร้อม
function loadUserData() {
  const token = tokenService.getToken();
  const user = tokenService.getUser();

  if (!token || !user) {
    window.location.href = '/login';
    return;
  }

  if (tokenService.isTokenExpired(token)) {
    tokenService.clear();
    window.location.href = '/login';
    return;
  }

  // แสดงข้อมูลผู้ใช้บนหน้าเพจ
  document.getElementById('userName').textContent = user.name;
  document.getElementById('userEmail').textContent = user.email;
}

// เรียกใช้งานเมื่อหน้าโหลดเสร็จ
loadUserData();

4. Token Refresh Logic

// รีเฟรชโทเค็นอัตโนมัติ
async function ensureValidToken() {
  let token = tokenService.getToken();

  // ไม่มีโทเค็นเลย ให้ผู้ใช้ล็อกอินใหม่
  if (!token) {
    window.location.href = '/login';
    return null;
  }

  // หากโทเค็นหมดอายุให้ลองรีเฟรช
  if (tokenService.isTokenExpired(token)) {
    console.log('Token expired - refreshing...');

    // ดึง refresh token
    const refreshToken = tokenService.getCookie('refresh_token');

    if (!refreshToken) {
      console.log('No refresh token - need to login');
      tokenService.clear();
      window.location.href = '/login';
      return null;
    }

    try {
      // เรียก API สำหรับรีเฟรชโทเค็น
      const response = await fetch('/api/auth/refresh', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ refresh_token: refreshToken })
      });

      const data = await response.json();

      if (data.success) {
        // เก็บโทเค็นใหม่ที่ได้รับ
        tokenService.store(data.token);
        token = data.token;

        console.log('Token refreshed successfully');
      } else {
        console.log('Token refresh failed - need to login');
        tokenService.clear();
        window.location.href = '/login';
        return null;
      }
    } catch (error) {
      console.error('Token refresh error:', error);
      tokenService.clear();
      window.location.href = '/login';
      return null;
    }
  }

  return token;
}

// เรียกใช้ก่อนยิง API ที่ต้องการโทเค็น
async function makeAuthenticatedRequest(url) {
  const token = await ensureValidToken();

  if (!token) {
    return;
  }

  const response = await fetch(url, {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });

  return response.json();
}

// ตัวอย่างการใช้งาน
const users = await makeAuthenticatedRequest('/api/users');
console.log('Users:', users);

5. Role-Based Access

// ตรวจสอบสิทธิ์ของผู้ใช้จากโทเค็น
function checkPermissions(token, requiredRoles = []) {
  // ตรวจสอบว่าโทเค็นถูกต้องก่อน
  if (!token || tokenService.isTokenExpired(token)) {
    return false;
  }

  // ดึงบทบาทผู้ใช้จากโทเค็น
  const userRoles = tokenService.getUserRoles(token);

  // หากไม่กำหนดบทบาทที่ต้องการ ให้ผ่านได้เลย
  if (requiredRoles.length === 0) {
    return true;
  }

  // ตรวจสอบว่าผู้ใช้มีบทบาทที่กำหนดไว้หรือไม่
  return requiredRoles.some(role => userRoles.includes(role));
}

// ตัวอย่างการใช้งาน
const token = tokenService.getToken();

// ตรวจสอบสิทธิ์ผู้ดูแล (admin)
if (checkPermissions(token, ['admin'])) {
  console.log('User has admin access');
  document.getElementById('adminPanel').style.display = 'block';
}

// ตรวจสอบสิทธิ์ moderator หรือ admin
if (checkPermissions(token, ['admin', 'moderator'])) {
  console.log('User has moderator or admin access');
  document.getElementById('modPanel').style.display = 'block';
}

// ตรวจสอบสิทธิ์ผู้ใช้ทั่วไป
if (checkPermissions(token, ['user', 'admin', 'moderator'])) {
  console.log('User has access');
  document.getElementById('content').style.display = 'block';
}

6. Token Debugging Tool

// ตัวช่วยตรวจสอบข้อมูลโทเค็น
function debugToken(token) {
  console.group('Token Debug Info');

  // ข้อมูลพื้นฐาน
  console.log('Token:', token);
  console.log('Token length:', token.length);

  // โครงสร้างของ JWT
  const parts = token.split('.');
  console.log('Parts:', parts.length);

  // แยกโทเค็น
  const payload = tokenService.parseToken(token);

  if (!payload) {
    console.error('❌ Invalid token format');
    console.groupEnd();
    return;
  }

  console.log('✅ Valid JWT format');

  // ข้อมูลใน payload
  console.group('Payload');
  console.log('User ID:', tokenService.getUserId(token));
  console.log('Roles:', tokenService.getUserRoles(token));
  console.log('Full payload:', payload);
  console.groupEnd();

  // ข้อมูลเกี่ยวกับวันหมดอายุ
  const expiry = tokenService.getTokenExpiry(token);
  const expired = tokenService.isTokenExpired(token);

  console.group('Expiry');
  console.log('Expiry timestamp:', expiry);
  console.log('Expiry date:', new Date(expiry * 1000));
  console.log('Is expired:', expired);

  if (!expired) {
    const now = Date.now();
    const remaining = (expiry * 1000) - now;
    const minutes = Math.floor(remaining / 1000 / 60);
    console.log('Time remaining:', minutes, 'minutes');
  }
  console.groupEnd();

  // สถานะข้อมูลที่จัดเก็บไว้
  console.group('Storage');
  const storedToken = tokenService.getToken();
  console.log('Stored token:', storedToken ? 'Found' : 'Not found');

  const user = tokenService.getUser();
  console.log('Stored user:', user ? user.name : 'Not found');
  console.groupEnd();

  console.groupEnd();
}

// การใช้งาน
const token = tokenService.getToken();
if (token) {
  debugToken(token);
}

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

คอนสตรักเตอร์

new TokenService(options)

สร้างอินสแตนซ์ TokenService ใหม่

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

  • options (Object, ไม่บังคับ) - ออปชันการตั้งค่า
    • storageMethod (string) - 'cookie' หรือ 'localStorage' (ค่าเริ่มต้น: 'cookie')
    • cookieName (string) - ชื่อคุกกี้ของ access token (ค่าเริ่มต้น: 'auth_token')
    • refreshCookieName (string) - ชื่อคุกกี้ของ refresh token (ค่าเริ่มต้น: 'refresh_token')
    • localStorageKey (string) - ชื่อ key ใน localStorage (ค่าเริ่มต้น: 'auth')
    • cookieOptions (Object) - การตั้งค่าเพิ่มเติมของคุกกี้

คืนค่า: อินสแตนซ์ TokenService

ตัวอย่าง:

const tokenService = new TokenService({
  storageMethod: 'cookie',
  cookieName: 'auth_token',
  cookieOptions: {
    path: '/',
    secure: true,
    sameSite: 'Strict'
  }
});

Methods

parseToken(token)

Parse JWT token and extract payload.

Parameters:

  • token (string) - JWT token

Returns: Object|null - Token payload or null if invalid

Example:

const payload = tokenService.parseToken(token);
if (payload) {
  console.log('User ID:', payload.sub);
  console.log('Expiry:', payload.exp);
}

เมธอด

parseToken(token)

แยกโทเค็น JWT และคืนค่า payload

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

  • token (string) - โทเค็น JWT

    คืนค่า: Object|null - Payload ของโทเค็น หรือ null หากไม่ถูกต้อง

    ตัวอย่าง:
    Example:

    if (tokenService.isTokenExpired(token)) {
    console.log('Token expired');
    } else {
    console.log('Token valid');
    }

isTokenExpired(token)

ตรวจสอบว่าโทเค็นหมดอายุหรือไม่

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

  • token (string) - โทเค็น JWT

    คืนค่า: boolean - คืน true หากโทเค็นหมดอายุหรือไม่ถูกต้อง

    ตัวอย่าง:
    Example:

    const expiry = tokenService.getTokenExpiry(token);
    if (expiry) {
    const date = new Date(expiry * 1000);
    console.log('Expires at:', date);
    }

getTokenExpiry(token)

ดึงค่าเวลาหมดอายุของโทเค็น (timestamp วินาที)

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

  • token (string) - โทเค็น JWT

    คืนค่า: number|null - เวลาหมดอายุในหน่วยวินาที หรือ null

    ตัวอย่าง:
    Example:

    const userId = tokenService.getUserId(token);
    console.log('User ID:', userId);

getUserRoles(token)

Extract user roles from token.

getUserId(token)

ดึงรหัสผู้ใช้จากโทเค็น

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

  • token (string) - โทเค็น JWT

    คืนค่า: string|number|null - รหัสผู้ใช้ หรือ null

    ตัวอย่าง:
    console.log('Roles:', roles);

getToken()

Get stored token from storage.

getUserRoles(token)

ดึงบทบาท (roles) ของผู้ใช้จากโทเค็น

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

  • token (string) - โทเค็น JWT

    คืนค่า: Array<string> - อาร์เรย์ของบทบาท (หากไม่มีจะได้อาร์เรย์ว่าง)

    ตัวอย่าง:
    }

get()

Alias for getToken().

getToken()

ดึงโทเค็นที่จัดเก็บไว้

คืนค่า: string|null - โทเค็น หรือ null

หมายเหตุ: หากเป็นคุกกี้แบบ httpOnly จะคืน null เพราะ JavaScript เข้าถึงไม่ได้

ตัวอย่าง:

store(token, additionalData)

Store token in configured storage.

Parameters:

  • token (string) - JWT token to store
  • additionalData (Object, optional) - Additional data to store (e.g., user object)

Returns: boolean - True if stored successfully

get()

ชื่อย่อที่เรียก getToken()

คืนค่า: string|null - โทเค็น หรือ null

ตัวอย่าง:


---

#### `remove()`
Remove token from storage.

**Returns:** `boolean` - True if removed successfully

**Example:**
```javascript
const removed = tokenService.remove();
if (removed) {
  console.log('Token removed');
}

clear()

Clear all stored authentication data.

Returns: boolean - True if cleared successfully

Example:

tokenService.clear();

getUser()

Get stored user data (localStorage only).

Returns: Object|null - User object or null

Example:

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

isAuthenticated()

Check if user is authenticated (has valid token).

Returns: boolean - True if authenticated

Example:

if (tokenService.isAuthenticated()) {
  console.log('User is authenticated');
}

setCookie(name, value, options)

Set a cookie.

Parameters:

  • name (string) - Cookie name
  • value (string) - Cookie value
  • options (Object, optional) - Cookie options
    • path (string) - Cookie path
    • secure (boolean) - HTTPS only
    • sameSite (string) - 'Strict', 'Lax', or 'None'
    • maxAge (number) - Max age in seconds
    • expires (Date) - Expiration date

Returns: void

Example:

tokenService.setCookie('auth_token', token, {
  path: '/',
  secure: true,
  sameSite: 'Strict',
  maxAge: 900
});

getCookie(name)

Get a cookie value.

Parameters:

  • name (string) - Cookie name

Returns: string|null - Cookie value or null

Example:

const token = tokenService.getCookie('auth_token');

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

1. ตรวจสอบโทเค็นทุกครั้งก่อนใช้งาน

// ✅ ดี - ตรวจสอบก่อนใช้งานเสมอ
const token = tokenService.getToken();

if (token && !tokenService.isTokenExpired(token)) {
  // Use token
  makeAuthenticatedRequest(token);
} else {
  // Token invalid or expired
  console.log('Please login');
}

// ❌ ไม่ดี - ใช้โดยไม่ตรวจสอบ
const token = tokenService.getToken();
makeAuthenticatedRequest(token); // May fail!

2. เลือกใช้คุกกี้สำหรับเว็บแอป

// ✅ ดี - ใช้คุกกี้ (ฝั่งเซิร์ฟเวอร์ตั้ง httpOnly ได้)
const tokenService = new TokenService({
  storageMethod: 'cookie',
  cookieOptions: {
    path: '/',
    secure: true,
    sameSite: 'Strict'
  }
});

// ❌ ไม่ดี - ใช้ localStorage (เสี่ยง XSS)
const tokenService = new TokenService({
  storageMethod: 'localStorage'
});

3. จัดการกรณีแยกโทเค็นไม่สำเร็จ

// ✅ ดี - ตรวจสอบผลลัพธ์จากการ parse
const payload = tokenService.parseToken(token);

if (!payload) {
  console.error('Invalid token format');
  return;
}

console.log('User ID:', payload.sub);

// ❌ ไม่ดี - คิดว่า parse สำเร็จเสมอ
const payload = tokenService.parseToken(token);
console.log('User ID:', payload.sub); // May throw error!

4. ตรวจสอบวันหมดอายุก่อนยิง API

// ✅ ดี - ตรวจสอบก่อนเรียกใช้
async function makeRequest(url) {
  const token = tokenService.getToken();

  if (!token || tokenService.isTokenExpired(token)) {
    // Token expired - refresh or login
    await refreshToken();
  }

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

// ❌ ไม่ดี - ไม่ตรวจสอบวันหมดอายุ
async function makeRequest(url) {
  const token = tokenService.getToken();

  return fetch(url, {
    headers: { 'Authorization': `Bearer ${token}` }
  }); // Will get 401 if expired
}

5. ล้างข้อมูลเมื่อผู้ใช้ออกจากระบบ

// ✅ ดี - ล้างข้อมูลทั้งหมด
function logout() {
  tokenService.clear();
  console.log('All data cleared');
  window.location.href = '/login';
}

// ❌ ไม่ดี - ลบไม่ครบทุกส่วน
function logout() {
  tokenService.remove(); // Only removes token, not user data
  window.location.href = '/login';
}

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

1. พยายามอ่านคุกกี้แบบ httpOnly

// ❌ ไม่ดี - JavaScript อ่าน httpOnly cookie ไม่ได้
const token = tokenService.getCookie('auth_token');
console.log(token); // null (if httpOnly)

// ✅ ดี - ปล่อยให้เบราว์เซอร์จัดการคุกกี้ httpOnly ให้อัตโนมัติ
// ฝั่งเซิร์ฟเวอร์ตั้งค่า: Set-Cookie: auth_token=...; HttpOnly; Secure
// เบราว์เซอร์จะส่งคุกกี้ไปกับคำร้องเอง
fetch('/api/users', {
  credentials: 'include' // Include cookies
})
.then(response => response.json());

2. ไม่ตรวจสอบว่าโทเค็นยังใช้ได้หรือไม่

// ❌ ไม่ดี - ใช้โทเค็นโดยไม่ตรวจสอบ
const token = tokenService.getToken();

fetch('/api/users', {
  headers: { 'Authorization': `Bearer ${token}` }
});

// ✅ ดี - ตรวจสอบก่อนใช้งาน
const token = tokenService.getToken();

if (!token || tokenService.isTokenExpired(token)) {
  console.log('Invalid token');
  return;
}

fetch('/api/users', {
  headers: { 'Authorization': `Bearer ${token}` }
});

3. เก็บข้อมูลสำคัญไว้ใน JWT

// ❌ ไม่ดี - ใส่ข้อมูลสำคัญลงใน JWT
// ฝั่งเซิร์ฟเวอร์ไม่ควรเก็บข้อมูลเหล่านี้ไว้ใน JWT
{
  sub: "123",
  name: "John Doe",
  password: "secret123",     // DON'T DO THIS!
  creditCard: "1234-5678",   // DON'T DO THIS!
  ssn: "123-45-6789"         // DON'T DO THIS!
}

// ✅ ดี - เก็บข้อมูลเท่าที่จำเป็น
{
  sub: "123",
  name: "John Doe",
  email: "john@example.com",
  roles: ["user"],
  iat: 1516239022,
  exp: 1516242622
}

4. ใช้โทเค็นที่มีอายุยาวเกินไป

// ❌ ไม่ดี - ตั้ง access token ให้อยู่ได้นานเกินไป (เช่น 30 วัน)
const payload = {
  sub: "123",
  exp: Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60) // 30 days
};

// ✅ ดี - ใช้ access token อายุสั้น (เช่น 15 นาที)
const payload = {
  sub: "123",
  exp: Math.floor(Date.now() / 1000) + (15 * 60) // 15 minutes
};

// ใช้ refresh token เพื่อยืดอายุเซสชันแทน

5. ไม่รองรับโทเค็นที่เสียหรือไม่ครบถ้วน

// ❌ ไม่ดี - ไม่มีการจัดการข้อผิดพลาด
const payload = tokenService.parseToken(malformedToken);
console.log(payload.sub); // Error if token is invalid!

// ✅ ดี - ตรวจสอบก่อนใช้งาน
const payload = tokenService.parseToken(malformedToken);

if (!payload) {
  console.error('Invalid token');
  return;
}

console.log('User ID:', payload.sub);

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

1. ป้องกัน XSS

// ✅ ใช้คุกกี้ httpOnly (ตั้งจากฝั่งเซิร์ฟเวอร์)
// ฝั่งเซิร์ฟเวอร์: Set-Cookie: auth_token=...; HttpOnly; Secure; SameSite=Strict

// ✅ ทำความสะอาดข้อมูลที่ผู้ใช้ป้อน
function sanitize(str) {
  const div = document.createElement('div');
  div.textContent = str;
  return div.innerHTML;
}

const payload = tokenService.parseToken(token);
if (payload) {
  document.getElementById('userName').textContent = sanitize(payload.name);
}

// ❌ หลีกเลี่ยงการใช้ innerHTML กับข้อมูลจากโทเค็น
// document.getElementById('userName').innerHTML = payload.name; // เสี่ยงถูกโจมตีแบบ XSS!

2. การเก็บโทเค็นอย่างปลอดภัย

// ✅ ดีที่สุด - เก็บในคุกกี้ httpOnly (ตั้งจากฝั่งเซิร์ฟเวอร์)
// ฝั่งเซิร์ฟเวอร์: Set-Cookie: auth_token=...; HttpOnly

// ✅ ดี - ใช้คุกกี้ปกติแต่ตั้งค่าให้ปลอดภัย
const tokenService = new TokenService({
  storageMethod: 'cookie',
  cookieOptions: {
    secure: true,
    sameSite: 'Strict'
  }
});

// ❌ หลีกเลี่ยง - localStorage (เสี่ยงถูกโจมตีแบบ XSS)
// const tokenService = new TokenService({
//   storageMethod: 'localStorage'
// });

3. ตรวจสอบโทเค็นทุกครั้งก่อนใช้งาน

// ✅ ตรวจสอบโทเค็นทุกครั้ง
function useToken(token) {
  // ตรวจสอบรูปแบบโทเค็น
  const payload = tokenService.parseToken(token);
  if (!payload) {
    return false;
  }

  // ตรวจสอบวันหมดอายุ
  if (tokenService.isTokenExpired(token)) {
    return false;
  }

  // ตรวจสอบว่ามีฟิลด์สำคัญครบ
  if (!payload.sub && !payload.id) {
    return false;
  }

  return true;
}

// ❌ อย่าคิดว่าโทเค็นถูกต้องเสมอ
// แค่มีโทเค็นไม่ได้หมายความว่าใช้งานได้จริง

4. ตั้งค่าคุกกี้ให้ปลอดภัย

// ✅ การตั้งค่าเหมาะสำหรับโปรดักชัน
const tokenService = new TokenService({
  storageMethod: 'cookie',
  cookieOptions: {
    secure: true,        // ใช้ผ่าน HTTPS เท่านั้น
    sameSite: 'Strict',  // ลดความเสี่ยง CSRF
    path: '/'            // ใช้ได้ทุกเส้นทาง
  }
});

// ❌ การตั้งค่าที่ไม่ปลอดภัย
// const tokenService = new TokenService({
//   cookieOptions: {
//     secure: false,      // ยอมให้ใช้ผ่าน HTTP
//     sameSite: 'None'    // ยอมให้ส่งข้ามโดเมน
//   }
// });

5. วันหมดอายุของโทเค็น

// ✅ ตรวจสอบวันหมดอายุก่อนใช้งานทุกครั้ง
const token = tokenService.getToken();

if (!token || tokenService.isTokenExpired(token)) {
  // รีเฟรชโทเค็นหรือให้ผู้ใช้ล็อกอินใหม่
  await refreshToken();
}

// ❌ ห้ามใช้โทเค็นที่หมดอายุแล้ว
// const token = tokenService.getToken();
// makeRequest(token); // อาจหมดอายุแล้ว!

6. ขนาด payload เท่าที่จำเป็น

// ✅ ฝั่งเซิร์ฟเวอร์ควรสร้าง payload ที่กระชับ
const payload = {
  sub: userId,           // User ID only
  roles: userRoles,      // Essential roles
  exp: expiry            // Expiration
};

// ❌ อย่าใส่ข้อมูลมากเกินไป
// const payload = {
//   sub: userId,
//   user: fullUserObject,      // ข้อมูลเยอะเกินไป!
//   profile: fullProfile,      // ข้อมูลเยอะเกินไป!
//   settings: allSettings      // ข้อมูลเยอะเกินไป!
// };

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

TokenService รองรับเบราว์เซอร์รุ่นใหม่:

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

คุณสมบัติที่จำเป็น:

  • ฟีเจอร์ ES6+ (arrow function, destructuring ฯลฯ)
  • Web Storage API (localStorage)
  • ความสามารถเข้ารหัส/ถอดรหัส Base64
  • Cookies API

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

  • Authentication.md - ภาพรวมระบบการยืนยันตัวตน
  • AuthManager.md - ตัวจัดการการยืนยันตัวตนหลัก
  • AuthGuard.md - การป้องกันเส้นทาง (Route Protection)
  • AuthErrorHandler.md - การจัดการข้อผิดพลาดด้านการยืนยันตัวตน
  • AuthLoadingManager.md - จัดการสถานะโหลดระหว่างยืนยันตัวตน