Now.js Framework Documentation

Now.js Framework Documentation

TokenService - JWT Token Management

EN 31 Oct 2025 01:23

TokenService - JWT Token Management

เอกสารสำหรับ TokenService ซึ่งเป็น Low-level JWT Token Management Service ของ Now.js Framework

📋 สารบัญ

  1. ภาพรวม
  2. การติดตั้งและนำเข้า
  3. การเริ่มต้นใช้งาน
  4. Configuration
  5. JWT Token Operations
  6. Token Validation
  7. Storage Management
  8. Cookie Operations
  9. User Data Extraction
  10. ตัวอย่างการใช้งาน
  11. API Reference
  12. Best Practices
  13. Common Pitfalls
  14. Security Considerations

ภาพรวม

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

ฟีเจอร์หลัก

  • JWT Parsing: แยก JWT token และ decode payload
  • Token Validation: ตรวจสอบความถูกต้องและ expiry
  • Storage Management: จัดการ token storage (Cookie/localStorage)
  • Cookie Operations: เขียน/อ่าน httpOnly cookies
  • User Data Extraction: ดึงข้อมูล user จาก token payload
  • Expiry Checking: ตรวจสอบว่า token หมดอายุหรือไม่
  • Base64URL Decoding: รองรับ Base64URL encoding standard
  • Multiple Storage Methods: Cookie, localStorage

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

ใช้ TokenService เมื่อ:

  • ต้องการ parse JWT token manually
  • ต้องการตรวจสอบ token validity
  • ต้องการ extract ข้อมูล user จาก token
  • ต้องการจัดการ token storage โดยตรง
  • สร้าง custom authentication logic

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

  • ใช้งานผ่าน AuthManager แล้ว (AuthManager ใช้ TokenService internally)
  • ไม่ต้องการจัดการ tokens ในระดับ low-level

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

TokenService โหลดมาพร้อมกับ Now.js Framework:

<!-- Load TokenService -->
<script src="/Now/js/TokenService.js"></script>

หลังจากโหลด TokenService จะพร้อมใช้งานผ่าน:

  • window.TokenService - Class definition
  • window.tokenService - Default instance (pre-initialized)
// Use default instance
console.log(window.tokenService); // TokenService instance

// Create custom instance
const customTokenService = new TokenService({
  storageMethod: 'cookie',
  cookieName: 'my_token'
});

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

Using Default Instance

// Default instance is ready to use
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

// Parse token
const payload = tokenService.parseToken(token);
console.log('User ID:', payload.sub);
console.log('Expiry:', new Date(payload.exp * 1000));

// Check if expired
const expired = tokenService.isTokenExpired(token);
console.log('Token expired:', expired);

Creating Custom Instance

// Create custom instance with options
const myTokenService = new TokenService({
  storageMethod: 'cookie',      // 'cookie' or 'localStorage'
  cookieName: 'auth_token',     // Cookie name for access token
  refreshCookieName: 'refresh_token',  // Cookie name for refresh token
  localStorageKey: 'auth',      // localStorage key

  cookieOptions: {
    path: '/',
    secure: true,                // HTTPS only
    sameSite: 'Strict'          // CSRF protection
  }
});

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

Configuration

Constructor Options

const tokenService = new TokenService({
  // Storage method: 'cookie' or 'localStorage'
  storageMethod: 'cookie',

  // Cookie names
  cookieName: 'auth_token',
  refreshCookieName: 'refresh_token',

  // localStorage key
  localStorageKey: 'auth',

  // Cookie options
  cookieOptions: {
    path: '/',                  // Cookie path
    secure: false,              // HTTPS only (auto-detect from location.protocol)
    sameSite: 'Lax'            // 'Strict', 'Lax', or 'None'
  }
});

Storage Methods

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

// Store token in cookie
tokenService.store(token, { user: userData });

// Note: For httpOnly cookies, the cookie must be set by the server
// JavaScript cannot read httpOnly cookies (this is a security feature)

Advantages:

  • ✅ Can use httpOnly flag (server-side)
  • ✅ Automatically sent with requests
  • ✅ CSRF protection with SameSite

Disadvantages:

  • ❌ Size limit (~4KB)
  • ❌ httpOnly cookies cannot be read by JavaScript

2. localStorage Storage

const tokenService = new TokenService({
  storageMethod: 'localStorage',
  localStorageKey: 'auth'
});

// Store token in localStorage
tokenService.store(token, { user: userData });

// Get token
const storedToken = tokenService.getToken();
console.log('Token:', storedToken);

Advantages:

  • ✅ Larger storage (~5-10MB)
  • ✅ Can be accessed by JavaScript
  • ✅ Persists across browser sessions

Disadvantages:

  • ❌ Vulnerable to XSS attacks
  • ❌ Not automatically sent with requests
  • ❌ Shared across tabs
{
  path: '/',              // Cookie available on all paths
  secure: true,           // Send over HTTPS only
  sameSite: 'Strict',     // 'Strict', 'Lax', or 'None'
  maxAge: 3600,          // Max age in seconds
  expires: new Date()     // Expiration date
}

SameSite Values:

Value Description Use Case
Strict Cookie only sent to same-site requests Recommended - Strongest CSRF protection
Lax Cookie sent on top-level navigation Good balance between security and usability
None Cookie sent on all requests (requires secure: true) Cross-site requests, APIs

Example:

const tokenService = new TokenService({
  storageMethod: 'cookie',
  cookieOptions: {
    path: '/',
    secure: location.protocol === 'https:',  // Auto-detect HTTPS
    sameSite: 'Strict',                       // Prevent CSRF
    maxAge: 15 * 60                          // 15 minutes
  }
});

JWT Token Operations

1. Parse JWT Token

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';

// Parse token
const payload = tokenService.parseToken(token);

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

Token Structure:

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

Payload Example:

{
  "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. Validate Token Format

// Check if token is valid JWT format
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');
}

// Invalid tokens return null
const invalidToken = 'not-a-jwt-token';
const result = tokenService.parseToken(invalidToken);
console.log(result); // null

3. Get Token Expiry

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

// Get expiry timestamp (seconds)
const expiry = tokenService.getTokenExpiry(token);

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

  // Calculate time remaining
  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');
}

Token Validation

1. Check Token Expiry

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

// Check if token is expired
const expired = tokenService.isTokenExpired(token);

if (expired) {
  console.log('Token has expired');
  // Need to refresh or re-login
} else {
  console.log('Token is still valid');
  // Can use token for requests
}

Behind the scenes:

// How isTokenExpired works:
const payload = tokenService.parseToken(token);
if (!payload || !payload.exp) {
  return true; // No expiry = treat as expired
}

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

2. Check Token Near Expiry

// Check if token expires within 5 minutes
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');
  // Trigger token refresh
}

3. Validate Token Structure

// Comprehensive token validation
function validateToken(token) {
  // Check token format
  if (!token || typeof token !== 'string') {
    return { valid: false, reason: 'Invalid token format' };
  }

  // Check JWT structure (3 parts)
  const parts = token.split('.');
  if (parts.length !== 3) {
    return { valid: false, reason: 'Invalid JWT structure' };
  }

  // Parse token
  const payload = tokenService.parseToken(token);
  if (!payload) {
    return { valid: false, reason: 'Cannot parse token' };
  }

  // Check required fields
  if (!payload.sub && !payload.id) {
    return { valid: false, reason: 'Missing user ID' };
  }

  // Check expiry
  if (tokenService.isTokenExpired(token)) {
    return { valid: false, reason: 'Token expired' };
  }

  return { valid: true, payload };
}

// Usage
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);
}

Storage Management

1. Store Token

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

// Store token with user data
const stored = tokenService.store(token, { user: userData });

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

Storage Behavior:

Cookie Storage:

// Stores token in cookie (not httpOnly from JS)
// Structure: auth_token=eyJhbGc...
tokenService.setCookie('auth_token', token, {
  path: '/',
  secure: true,
  sameSite: 'Strict',
  maxAge: 900  // 15 minutes
});

localStorage Storage:

// Stores token and user data in localStorage
// Structure: { token: '...', user: {...} }
localStorage.setItem('auth', JSON.stringify({
  token: token,
  user: userData
}));

2. Get Token

// Get stored token
const token = tokenService.getToken();

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

  // Validate token
  if (!tokenService.isTokenExpired(token)) {
    console.log('Token is valid');
  } else {
    console.log('Token expired');
  }
} else {
  console.log('No token found');
}

Note: For httpOnly cookies, getToken() will return null because JavaScript cannot access httpOnly cookies. The server must validate the token from the cookie header.

3. Get User Data

// Get stored user data (localStorage only)
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. Remove Token

// Remove token from storage
const removed = tokenService.remove();

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

// Verify removal
const token = tokenService.getToken();
console.log('Token after removal:', token); // null

5. Clear All Data

// Clear all authentication data
const cleared = tokenService.clear();

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

// This will clear:
// - Access token
// - Refresh token
// - User data
// - Any other stored auth data
// Set cookie with options
tokenService.setCookie('auth_token', token, {
  path: '/',
  secure: true,
  sameSite: 'Strict',
  maxAge: 900  // 15 minutes
});

// Set cookie with expiry date
tokenService.setCookie('auth_token', token, {
  path: '/',
  secure: true,
  sameSite: 'Strict',
  expires: new Date(Date.now() + 15 * 60 * 1000)
});
// Get cookie value
const token = tokenService.getCookie('auth_token');

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

// Note: Cannot read httpOnly cookies
const httpOnlyToken = tokenService.getCookie('httponly_token');
console.log(httpOnlyToken); // null (if httpOnly flag is set)
// Delete cookie by setting maxAge to 0
tokenService.setCookie('auth_token', '', {
  path: '/',
  maxAge: 0
});

// Or set expiry to past date
tokenService.setCookie('auth_token', '', {
  path: '/',
  expires: new Date(0)
});

// Verify deletion
const token = tokenService.getCookie('auth_token');
console.log('Token after deletion:', token); // null

User Data Extraction

1. Extract User ID

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

// Get user ID from token
const userId = tokenService.getUserId(token);

console.log('User ID:', userId);
// Standard JWT uses 'sub' field
// Falls back to 'id' field if 'sub' not found

2. Extract User Roles

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

// Get user roles from token
const roles = tokenService.getUserRoles(token);

console.log('User roles:', roles);
// Returns: ['user', 'admin']
// Returns: [] if no roles found

// Check specific role
if (roles.includes('admin')) {
  console.log('User is admin');
}

3. Extract Custom Claims

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

// Parse token to get all claims
const payload = tokenService.parseToken(token);

if (payload) {
  // Standard claims
  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));

  // Custom claims
  console.log('Roles:', payload.roles);
  console.log('Permissions:', payload.permissions);
  console.log('Subscription:', payload.subscription);
  console.log('Organization:', payload.org);

  // Any other custom fields in your JWT
  console.log('Custom field:', payload.customField);
}

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

1. Basic Token Validation

// Validate and use token
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

// Parse token
const payload = tokenService.parseToken(token);

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

// Check expiry
if (tokenService.isTokenExpired(token)) {
  console.error('Token has expired');
  return;
}

// Extract user data
const userId = tokenService.getUserId(token);
const roles = tokenService.getUserRoles(token);

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

// Use token for API request
fetch('/api/users', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
})
.then(response => response.json())
.then(data => console.log('API response:', data));
// Initialize with cookie storage
const tokenService = new TokenService({
  storageMethod: 'cookie',
  cookieName: 'auth_token',
  cookieOptions: {
    path: '/',
    secure: true,
    sameSite: 'Strict'
  }
});

// Login and store token
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) {
    // Store token in cookie
    tokenService.store(data.token, { user: data.user });

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

    // Redirect to dashboard
    window.location.href = '/dashboard';
  }
}

// Check authentication on page load
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');
}

// Run on page load
checkAuth();

3. Token Storage with localStorage

// Initialize with localStorage
const tokenService = new TokenService({
  storageMethod: 'localStorage',
  localStorageKey: 'auth'
});

// Login and store token
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) {
    // Store token in localStorage
    tokenService.store(data.token, { user: data.user });

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

    // Get stored data
    const storedToken = tokenService.getToken();
    const storedUser = tokenService.getUser();

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

// Get user data on page load
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;
  }

  // Display user info
  document.getElementById('userName').textContent = user.name;
  document.getElementById('userEmail').textContent = user.email;
}

// Run on page load
loadUserData();

4. Token Refresh Logic

// Automatic token refresh
async function ensureValidToken() {
  let token = tokenService.getToken();

  // No token - need to login
  if (!token) {
    window.location.href = '/login';
    return null;
  }

  // Token expired - try to refresh
  if (tokenService.isTokenExpired(token)) {
    console.log('Token expired - refreshing...');

    // Get 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 {
      // Call refresh endpoint
      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) {
        // Store new token
        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;
}

// Use before API requests
async function makeAuthenticatedRequest(url) {
  const token = await ensureValidToken();

  if (!token) {
    return;
  }

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

  return response.json();
}

// Usage
const users = await makeAuthenticatedRequest('/api/users');
console.log('Users:', users);

5. Role-Based Access

// Check user permissions from token
function checkPermissions(token, requiredRoles = []) {
  // Validate token
  if (!token || tokenService.isTokenExpired(token)) {
    return false;
  }

  // Get user roles from token
  const userRoles = tokenService.getUserRoles(token);

  // No required roles - allow access
  if (requiredRoles.length === 0) {
    return true;
  }

  // Check if user has any required role
  return requiredRoles.some(role => userRoles.includes(role));
}

// Usage
const token = tokenService.getToken();

// Check admin access
if (checkPermissions(token, ['admin'])) {
  console.log('User has admin access');
  document.getElementById('adminPanel').style.display = 'block';
}

// Check moderator or admin access
if (checkPermissions(token, ['admin', 'moderator'])) {
  console.log('User has moderator or admin access');
  document.getElementById('modPanel').style.display = 'block';
}

// Check regular user access
if (checkPermissions(token, ['user', 'admin', 'moderator'])) {
  console.log('User has access');
  document.getElementById('content').style.display = 'block';
}

6. Token Debugging Tool

// Debug token information
function debugToken(token) {
  console.group('Token Debug Info');

  // Basic info
  console.log('Token:', token);
  console.log('Token length:', token.length);

  // Structure
  const parts = token.split('.');
  console.log('Parts:', parts.length);

  // Parse token
  const payload = tokenService.parseToken(token);

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

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

  // Payload info
  console.group('Payload');
  console.log('User ID:', tokenService.getUserId(token));
  console.log('Roles:', tokenService.getUserRoles(token));
  console.log('Full payload:', payload);
  console.groupEnd();

  // Expiry info
  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();

  // Storage info
  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();
}

// Usage
const token = tokenService.getToken();
if (token) {
  debugToken(token);
}

API Reference

Constructor

new TokenService(options)

Create a new TokenService instance.

Parameters:

  • options (Object, optional) - Configuration options
    • storageMethod (string) - 'cookie' or 'localStorage' (default: 'cookie')
    • cookieName (string) - Cookie name for access token (default: 'auth_token')
    • refreshCookieName (string) - Cookie name for refresh token (default: 'refresh_token')
    • localStorageKey (string) - localStorage key (default: 'auth')
    • cookieOptions (Object) - Cookie options

Returns: TokenService instance

Example:

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);
}

isTokenExpired(token)

Check if token is expired.

Parameters:

  • token (string) - JWT token

Returns: boolean - True if expired or invalid

Example:

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

getTokenExpiry(token)

Get token expiration timestamp.

Parameters:

  • token (string) - JWT token

Returns: number|null - Expiry timestamp in seconds or null

Example:

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

getUserId(token)

Extract user ID from token.

Parameters:

  • token (string) - JWT token

Returns: string|number|null - User ID or null

Example:

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

getUserRoles(token)

Extract user roles from token.

Parameters:

  • token (string) - JWT token

Returns: Array<string> - Array of roles (empty array if none)

Example:

const roles = tokenService.getUserRoles(token);
console.log('Roles:', roles);

getToken()

Get stored token from storage.

Returns: string|null - Token or null

Note: Returns null for httpOnly cookies (cannot be accessed by JavaScript)

Example:

const token = tokenService.getToken();
if (token) {
  console.log('Token found');
}

get()

Alias for getToken().

Returns: string|null - Token or null

Example:

const token = tokenService.get();

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

Example:

const stored = tokenService.store(token, {
  user: { id: 1, name: 'John' }
});

remove()

Remove token from storage.

Returns: boolean - True if removed successfully

Example:

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');

Best Practices

1. Always Validate Tokens

// ✅ Good - validate before use
const token = tokenService.getToken();

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

// ❌ Bad - use without validation
const token = tokenService.getToken();
makeAuthenticatedRequest(token); // May fail!
// ✅ Good - cookie storage (can be httpOnly on server)
const tokenService = new TokenService({
  storageMethod: 'cookie',
  cookieOptions: {
    path: '/',
    secure: true,
    sameSite: 'Strict'
  }
});

// ❌ Bad - localStorage (vulnerable to XSS)
const tokenService = new TokenService({
  storageMethod: 'localStorage'
});

3. Handle Parsing Errors

// ✅ Good - handle parsing errors
const payload = tokenService.parseToken(token);

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

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

// ❌ Bad - assume parsing succeeds
const payload = tokenService.parseToken(token);
console.log('User ID:', payload.sub); // May throw error!

4. Check Expiry Before API Calls

// ✅ Good - check expiry first
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}` }
  });
}

// ❌ Bad - don't check expiry
async function makeRequest(url) {
  const token = tokenService.getToken();

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

5. Clear Data on Logout

// ✅ Good - clear all data
function logout() {
  tokenService.clear();
  console.log('All data cleared');
  window.location.href = '/login';
}

// ❌ Bad - partial cleanup
function logout() {
  tokenService.remove(); // Only removes token, not user data
  window.location.href = '/login';
}

Common Pitfalls

1. Accessing httpOnly Cookies

// ❌ Bad - cannot access httpOnly cookies from JavaScript
const token = tokenService.getCookie('auth_token');
console.log(token); // null (if httpOnly)

// ✅ Good - httpOnly cookies are handled by browser automatically
// Server sets: Set-Cookie: auth_token=...; HttpOnly; Secure
// Browser sends cookie automatically with every request
fetch('/api/users', {
  credentials: 'include' // Include cookies
})
.then(response => response.json());

2. Not Checking Token Validity

// ❌ Bad - use token without checking
const token = tokenService.getToken();

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

// ✅ Good - validate token first
const token = tokenService.getToken();

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

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

3. Storing Sensitive Data in JWT

// ❌ Bad - sensitive data in JWT
// Server should NOT put this in 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!
}

// ✅ Good - minimal data in JWT
{
  sub: "123",
  name: "John Doe",
  email: "john@example.com",
  roles: ["user"],
  iat: 1516239022,
  exp: 1516242622
}

4. Using Long-Lived Tokens

// ❌ Bad - long-lived access tokens
// Server: JWT expiry = 30 days
const payload = {
  sub: "123",
  exp: Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60) // 30 days
};

// ✅ Good - short-lived access tokens
// Server: JWT expiry = 15 minutes
const payload = {
  sub: "123",
  exp: Math.floor(Date.now() / 1000) + (15 * 60) // 15 minutes
};

// Use refresh token for longer sessions

5. Not Handling Malformed Tokens

// ❌ Bad - no error handling
const payload = tokenService.parseToken(malformedToken);
console.log(payload.sub); // Error if token is invalid!

// ✅ Good - handle errors
const payload = tokenService.parseToken(malformedToken);

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

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

Security Considerations

1. XSS Protection

// ✅ Use httpOnly cookies (server-side)
// Server: Set-Cookie: auth_token=...; HttpOnly; Secure; SameSite=Strict

// ✅ Sanitize user input
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);
}

// ❌ DON'T use innerHTML with token data
// document.getElementById('userName').innerHTML = payload.name; // XSS risk!

2. Token Storage

// ✅ Best - httpOnly cookies (server-side)
// Server: Set-Cookie: auth_token=...; HttpOnly

// ✅ Good - regular cookies with secure flags
const tokenService = new TokenService({
  storageMethod: 'cookie',
  cookieOptions: {
    secure: true,
    sameSite: 'Strict'
  }
});

// ❌ Avoid - localStorage (XSS vulnerable)
// const tokenService = new TokenService({
//   storageMethod: 'localStorage'
// });

3. Token Validation

// ✅ Always validate tokens
function useToken(token) {
  // Validate format
  const payload = tokenService.parseToken(token);
  if (!payload) {
    return false;
  }

  // Validate expiry
  if (tokenService.isTokenExpired(token)) {
    return false;
  }

  // Validate required fields
  if (!payload.sub && !payload.id) {
    return false;
  }

  return true;
}

// ❌ Don't assume tokens are valid
// Just because a token exists doesn't mean it's valid
// ✅ Production settings
const tokenService = new TokenService({
  storageMethod: 'cookie',
  cookieOptions: {
    secure: true,        // HTTPS only
    sameSite: 'Strict',  // Prevent CSRF
    path: '/'            // Accessible on all paths
  }
});

// ❌ Insecure settings
// const tokenService = new TokenService({
//   cookieOptions: {
//     secure: false,      // Allow HTTP
//     sameSite: 'None'    // Allow cross-site
//   }
// });

5. Token Expiry

// ✅ Check expiry before use
const token = tokenService.getToken();

if (!token || tokenService.isTokenExpired(token)) {
  // Refresh or re-login
  await refreshToken();
}

// ❌ Don't use expired tokens
// const token = tokenService.getToken();
// makeRequest(token); // May be expired!

6. Minimal Token Payload

// ✅ Minimal payload (server-side)
const payload = {
  sub: userId,           // User ID only
  roles: userRoles,      // Essential roles
  exp: expiry            // Expiration
};

// ❌ Large payload
// const payload = {
//   sub: userId,
//   user: fullUserObject,      // Too much data!
//   profile: fullProfile,      // Too much data!
//   settings: allSettings      // Too much data!
// };

Browser Compatibility

TokenService รองรับ browsers สมัยใหม่:

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

Required Features:

  • ES6+ features (arrow functions, destructuring, etc.)
  • Web Storage API (localStorage)
  • Base64 encoding/decoding
  • Cookies API