Now.js Framework Documentation
TokenService - การจัดการโทเค็น JWT
TokenService - การจัดการโทเค็น JWT
เอกสารนี้อธิบาย TokenService ซึ่งเป็นบริการระดับล่างสำหรับจัดการโทเค็น JWT ให้กับ Now.js Framework ครอบคลุมการแยก วิเคราะห์ ตรวจสอบ และจัดเก็บโทเค็น รวมถึงการดึงข้อมูลผู้ใช้จาก payload
📋 สารบัญ
- ภาพรวม
- การติดตั้งและนำเข้า
- การเริ่มต้นใช้งาน
- การตั้งค่า
- การทำงานกับโทเค็น-jwt
- การตรวจสอบโทเค็น
- การจัดการที่เก็บข้อมูล
- การทำงานกับคุกกี้
- การดึงข้อมูลผู้ใช้
- ตัวอย่างการใช้งาน
- เอกสารอ้างอิง api
- แนวทางปฏิบัติที่ดี
- ข้อผิดพลาดที่พบบ่อย
- ข้อควรคำนึงด้านความปลอดภัย
ภาพรวม
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 ที่ผูกไว้กับ windowstore(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); // null3. อ่านเวลาหมดอายุของโทเค็น
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); // null5. ล้างข้อมูลทั้งหมด
// ล้างข้อมูลการยืนยันตัวตนทั้งหมด
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));2. Token Storage with Cookie
// เริ่มต้นใช้งานโดยให้เก็บโทเค็นในคุกกี้
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 storeadditionalData(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 namevalue(string) - Cookie valueoptions(Object, optional) - Cookie optionspath(string) - Cookie pathsecure(boolean) - HTTPS onlysameSite(string) - 'Strict', 'Lax', or 'None'maxAge(number) - Max age in secondsexpires(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 - จัดการสถานะโหลดระหว่างยืนยันตัวตน