@win-portal/auth-sdk
Shared authentication SDK for Win Portal applications using API Key authentication.
Note: The main Win Portal API already supports API Key authentication through
HybridAuthGuard. This SDK provides convenient client libraries for external applications.
Features
- 🔐 API Key Authentication - Secure authentication using application API keys
- 🎯 Frontend Client - HTTP client with automatic API key injection for Next.js/React
- 🛡️ Backend Middleware - Express & NestJS middleware for easy authentication
- 📦 Lightweight - Minimal dependencies
- 🔧 TypeScript Support - Full type definitions included
- ✨ Type Safety - Express Request augmentation with full IntelliSense
How It Works
The Win Portal main API (apps/api) has built-in API key support:
- All endpoints with
@Auth()decorator acceptX-API-Keyheader - Uses
HybridAuthGuardwhich tries API Key → JWT → OAuth - No additional validation endpoint needed
This SDK helps external apps (template-web, template-api) easily integrate with the main API.
Installation
# Using pnpm (recommended for monorepo)
pnpm add @win-portal/auth-sdk
# Using npm
npm install @win-portal/auth-sdk
# Using yarn
yarn add @win-portal/auth-sdkUsage
Frontend (Next.js / React)
1. Setup Client
import { AuthClient } from '@win-portal/auth-sdk';
// Initialize the client
const authClient = new AuthClient({
apiKey: process.env.NEXT_PUBLIC_API_KEY!,
baseURL: process.env.NEXT_PUBLIC_API_URL,
});
export default authClient;2. Make Authenticated Requests
The SDK uses namespaced methods for better organization:
import authClient from '@/lib/auth-client';
// Authentication
const loginResult = await authClient.auth.login('user@example.com', 'password');
// ✅ Set JWT token with explicit type (recommended for better performance)
authClient.setToken(loginResult.token, 'jwt');
// Or use hybrid mode (backward compatible)
authClient.setToken(loginResult.token); // defaults to 'jwt'
authClient.setAuthType('hybrid'); // will try JWT first, then OAuth
const profile = await authClient.auth.profile();
const refreshed = await authClient.auth.refresh(refreshToken);
await authClient.auth.logout();
// OAuth Token Usage
// ✅ Set OAuth token with explicit type (recommended for better performance)
const oauthToken = await authClient.oauth.exchangeCode(code, codeVerifier);
authClient.setToken(oauthToken.access_token, 'oauth');
// TOTP (Two-Factor Authentication)
// 1. Setup TOTP for user
const totpSetup = await authClient.auth.setupTotp('My App');
console.log('QR Code:', totpSetup.qr_code);
console.log('Backup Codes:', totpSetup.backup_codes);
// 2. Verify TOTP setup with code from authenticator app
await authClient.auth.verifyTotpSetup('123456');
// 3. Get TOTP status
const status = await authClient.auth.getTotpStatus();
console.log('TOTP Enabled:', status.is_enabled);
// 4. Login with TOTP (2-step authentication)
const loginResult = await authClient.auth.login('user@example.com', 'password');
if (loginResult.totp_required) {
// User has TOTP enabled, need to verify
const totpToken = '123456'; // From authenticator app
const session = await authClient.auth.verifyTotpLogin(
totpToken,
undefined, // backup_code (optional)
loginResult.access_token, // temp_token from initial login
);
console.log('Login successful:', session.user);
}
// 5. Disable TOTP
await authClient.auth.disableTotp();
// 6. Regenerate backup codes
const newCodes = await authClient.auth.regenerateTotpBackupCodes();
console.log('New backup codes:', newCodes.backup_codes);
// User Management
const users = await authClient.user.search({
search: 'john',
page: 1,
page_size: 20,
});
const user = await authClient.user.get(userId);
const newUser = await authClient.user.create(userData);
const updated = await authClient.user.update(userId, userData);
await authClient.user.delete(userId);
// Health Check
const health = await authClient.health.check();
const isValid = await authClient.health.validateApiKey();
// License Information
const licenseResponse = await authClient.license.getInfo();
if (licenseResponse.data.success && licenseResponse.data.data) {
const license = licenseResponse.data.data;
console.log('License:', license.name);
console.log('Expires:', license.expires_at);
console.log('Modules:', license.module_codes);
}
// LINE Messaging
await authClient.line.sendTextMessage({
userId: 'user-123',
message: 'สวัสดีครับ!',
});
await authClient.line.sendNotification({
userId: 'user-123',
title: 'งานใหม่',
message: 'คุณมีงานใหม่ที่ต้องดำเนินการ',
type: 'info',
action_url: 'https://app.example.com/tasks/123',
priority: 'high',
});
// OTP Service (for external applications)
// Send OTP via SMS
const otpResult = await authClient.otp.sendOTP({
method: 'sms',
recipient: '0812345678',
context: 'login',
});
console.log('OTP ID:', otpResult.otp_id); // UUID for verification
console.log('Ref Code:', otpResult.ref_code); // OTP-XXXXXX (for tracking/billing)
console.log('Expires at:', otpResult.expires_at);
console.log('Masked recipient:', otpResult.masked_recipient);
// Send OTP via Email
const emailOtpResult = await authClient.otp.sendOTP({
method: 'email',
recipient: 'user@example.com',
context: 'verification',
});
// Verify OTP
const verifyResult = await authClient.otp.verifyOTP({
otp_id: otpResult.otp_id,
code: '123456', // OTP code from user
});
if (verifyResult.verified) {
console.log('OTP verified successfully');
}
// Email Service (for external applications)
// Send email with template
await authClient.email.sendEmailWithTemplate(
'welcome_email',
{ userName: 'สมชาย ใจดี', activationLink: 'https://app.com/activate?token=xxx' },
'user@example.com',
{ recipient_name: 'สมชาย ใจดี', language: 'th' }
);
// Send email with raw HTML
await authClient.email.sendRawEmail(
'<h1>ยินดีต้อนรับ</h1><p>ขอบคุณที่สมัครสมาชิก</p>',
'ยินดีต้อนรับสู่ระบบ',
'user@example.com',
{ text_body: 'ยินดีต้อนรับ ขอบคุณที่สมัครสมาชิก' }
);
// Todo Management
const todo = await authClient.todo.create({
title: 'ซื้อของใช้ในบ้าน',
priority: 'normal',
user_id: 'user-123',
description: 'ซื้อน้ำยาล้างจาน, กระดาษทิชชู่',
due_date: new Date('2024-01-20'),
category: 'shopping',
labels: ['บ้าน', 'จำเป็น'],
is_send_notification: true,
});
// Workflow Management
// Create workflow instance
const workflowInstance = await authClient.workflow.createInstance({
workflow_definition_id: 'wf-def-123',
document_id: 'doc-123',
document_type: 'purchase_request',
document_title: 'ขอซื้อคอมพิวเตอร์',
priority: 'high',
form_data: { amount: 150000, items: ['Laptop', 'Monitor'] },
});
// Get user tasks
const userTasks = await authClient.workflow.getUserTasks({
status: ['assigned', 'in_progress'],
page: 1,
limit: 20,
});
// Perform task action (approve/reject/delegate)
await authClient.workflow.performTaskAction('task-123', {
action_key: 'approve',
comments: 'อนุมัติตามขั้นตอน',
});
// Organization Management
// Search organizations
const organizations = await authClient.organization.search({
search: 'แผนก',
page: 1,
limit: 20,
});
// Get organization hierarchy
const ancestors = await authClient.organization.getAncestors('org-123');
const reportingLine = await authClient.organization.getReportingLine('org-123');
const users = await authClient.organization.getUsers('org-123', true); // include descendants
// Webhook Proxy (for external applications)
// Send webhook through proxy
const webhookResult = await authClient.webhook.proxy({
webhook_url: 'https://api.example.com/webhooks/users',
event_type: 'user.created',
payload: { data: { id: 'user-123', email: 'user@example.com' } },
headers: { Authorization: 'Bearer token' },
secret: 'webhook-secret',
timeout_ms: 30000,
max_retries: 3,
});
// Check webhook status
const webhookLog = await authClient.webhook.getStatus(webhookResult.webhook_log_id);
console.log('Status:', webhookLog.status); // 'success', 'failed', 'pending'For custom endpoints, use direct HTTP methods:
// GET request
const response = await authClient.get('/api/v1/custom/endpoint');
// POST request
const response = await authClient.post('/api/v1/custom/endpoint', data);3. Use with Next.js API Routes
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import authClient from '@/lib/auth-client';
export async function GET(request: NextRequest) {
try {
const response = await authClient.post('/api/v1/users/search', {
page: 1,
page_size: 20,
});
return NextResponse.json(response.data);
} catch (error: any) {
return NextResponse.json(
{ error: error.response?.data?.message || 'Failed to fetch users' },
{ status: error.response?.status || 500 },
);
}
}4. Environment Variables
# .env.local
NEXT_PUBLIC_API_KEY=app_your_api_key_here
NEXT_PUBLIC_API_URL=https://api.yourdomain.comBackend Middleware (Express & NestJS)
The SDK provides middleware for Express and Guards for NestJS to easily authenticate requests using JWT tokens.
Quick Start - Express
import express from 'express';
import { authMiddleware } from '@win-portal/auth-sdk';
const app = express();
// Apply middleware globally
app.use(
authMiddleware({
baseURL: 'https://api.example.com',
apiKey: 'your-api-key',
}),
);
// Now all routes have access to req.user and req.token
app.get('/profile', (req, res) => {
const user = req.user; // User
res.json({
email: user.email,
permissions: user.permissions,
});
});Quick Start - NestJS
// auth/guards/auth.guard.ts
import { createAuthGuard } from '@win-portal/auth-sdk';
export const AuthGuard = createAuthGuard({
baseURL: process.env.API_BASE_URL!,
apiKey: process.env.API_KEY!,
});
// In controller
@Controller('users')
export class UsersController {
@Get('profile')
@UseGuards(AuthGuard)
getProfile(@CurrentUser() user: User) {
return user;
}
}📖 Complete Middleware Documentation
For complete documentation, examples, and advanced usage:
Includes:
- Express middleware configuration
- NestJS guards and decorators
- Optional authentication
- Custom token extractors
- Cache management
- Permission checking patterns
- Full working examples
Backend (NestJS - API Client)
For template-api that needs to call main API:
import { AuthClient } from '@win-portal/auth-sdk';
import { Injectable } from '@nestjs/common';
@Injectable()
export class ExternalApiService {
private authClient: AuthClient;
constructor() {
this.authClient = new AuthClient({
apiKey: process.env.MAIN_API_KEY!,
baseURL: process.env.MAIN_API_URL,
});
}
async fetchFromMainApi() {
const response = await this.authClient.get('/api/v1/resources');
return response.data;
}
}API Reference
AuthClient
HTTP client with automatic API key injection.
const client = new AuthClient({
apiKey: string; // Required: Your API key
baseURL?: string; // Optional: Base URL for requests
apiKeyHeader?: string; // Optional: Custom header name (default: 'X-API-Key')
timeout?: number; // Optional: Request timeout (default: 30000ms)
advanced?: { // Optional: Advanced configuration
activityThrottleMs?: number; // Activity handler throttle (default: 1000ms)
refreshTimeoutMs?: number; // Refresh token timeout (default: 5000ms)
}
})Advanced Configuration:
// ปรับแต่ง performance และ timeout settings
const client = new AuthClient({
apiKey: 'your-api-key',
baseURL: 'https://api.example.com',
advanced: {
activityThrottleMs: 500, // ลด throttle time สำหรับ responsive มากขึ้น (แต่ใช้ CPU มากขึ้น)
refreshTimeoutMs: 10000, // เพิ่ม timeout สำหรับ slow network
},
});HTTP Methods
get<T>(url, config?)- GET requestpost<T>(url, data?, config?)- POST requestput<T>(url, data?, config?)- PUT requestpatch<T>(url, data?, config?)- PATCH requestdelete<T>(url, config?)- DELETE request
Convenience Methods
Authentication:
auth.login(email, password)- Login userauth.logout()- Logout current sessionauth.profile()- Get current user profileauth.refresh(refreshToken)- Refresh access token
User Management:
user.search(params?)- Search users with paginationuser.get(userId)- Get user by IDuser.create(userData)- Create new useruser.update(userId, userData)- Update useruser.delete(userId)- Delete useruser.sync(params?)- Sync users for external applications (paginated)
LINE Messaging:
line.sendTextMessage({ userId, message })- Send text messageline.sendSticker({ userId, packageId, stickerId })- Send LINE stickerline.sendImage({ userId, originalContentUrl, previewImageUrl })- Send imageline.sendMessages({ userId, messages })- Send multiple messages (max 5)line.sendNotification({ userId, title, message, type?, action_url?, priority? })- Send formatted notificationline.checkMessagingAvailability(userId)- Check if user can receive LINE messages
📖 LINE Messaging Guide → - Complete documentation with examples
Health & Validation:
health.check()- Check API healthhealth.validateApiKey()- Validate if API key is still active
License Management:
license.getInfo()- Get current application license information (requires API Key)- Returns license details including
module_codes,expires_at,is_expired, etc. - Automatically uses application from API key context
- No need to provide application code or ID
- Returns license details including
OTP Service (for external applications):
otp.sendOTP({ method, recipient, context?, metadata? })- Send OTP via SMS or Emailmethod:'sms'|'email'- Delivery methodrecipient: Phone number or email addresscontext: Optional context (e.g., 'login', 'verification')metadata: Optional JSON string for additional data- Returns:
{ otp_id, ref_code, expires_at, masked_recipient }otp_id: UUID for verificationref_code: Short reference code (OTP-XXXXXX) for tracking/billingexpires_at: OTP expiration timestampmasked_recipient: Masked phone number or email
- Requires API Key authentication
otp.verifyOTP({ otp_id, code })- Verify OTP codeotp_id: OTP ID from sendOTP responsecode: 6-digit OTP code- Returns:
{ verified: boolean } - Requires API Key authentication
Email Service (for external applications):
email.sendEmail(request)- Send email (auto-detect Template Mode or Raw HTML Mode)email.sendEmailWithTemplate(templateCode, templateData, recipient, options?)- Send email with templateemail.sendRawEmail(htmlBody, subject, recipient, options?)- Send email with raw HTML- Supports CC, BCC, attachments, priority, scheduled delivery
- Requires API Key authentication
Todo Management:
todo.create(todoData)- Create a new todo item
Workflow Management:
workflow.getInstance(instanceId)- Get workflow instance by IDworkflow.createInstance(instanceData)- Create a new workflow instanceworkflow.updateInstance(instanceId, instanceData)- Update workflow instanceworkflow.searchInstances(params?)- Search workflow instances with paginationworkflow.getTask(taskId)- Get workflow task by IDworkflow.getUserTasks(params?)- Get user tasks with pagination and filtersworkflow.performTaskAction(taskId, actionData)- Perform action on task (approve/reject/delegate/escalate)workflow.getDefinition(definitionId)- Get workflow definition by IDworkflow.searchDefinitions(params?)- Search workflow definitions with pagination
Organization Management:
organization.search(params?)- Search organizations with paginationorganization.get(id)- Get organization by IDorganization.create(data)- Create new organizationorganization.update(id, data)- Update organizationorganization.delete(id)- Delete organizationorganization.getUsers(id, includeDescendants?)- Get organization usersorganization.getAncestors(id, includeRoot?)- Get parent organizations at all levelsorganization.getDirectSupervisor(id)- Get immediate supervisor/parent organizationorganization.getPath(id)- Get full path from root to organizationorganization.getReportingLine(id)- Get reporting line (hierarchical and functional)organization.getSiblings(id, includeSelf?)- Get sibling organizationsorganization.getLevel(id)- Get organization levelorganization.getCommonAncestor(orgA, orgB)- Get lowest common ancestororganization.isAncestorOf(ancestorId, descendantId)- Check if organization is ancestor
Webhook Proxy (for external applications):
webhook.proxy(request)- Send webhook request through proxy endpoint- Supports retry mechanism, custom headers, timeout configuration
- Returns:
{ webhook_log_id, status } - Requires API Key authentication
webhook.getStatus(webhookLogId)- Get webhook log detail by ID- Returns:
{ status, http_status, response_body, error_message, execution_time_ms, retry_count, ... } - Requires API Key authentication
- Returns:
Utility Methods
setToken(token, type?)- Set authentication tokentype:'jwt'(default) |'oauth'|'hybrid'- Recommended to specify type for better performance
getAuthType()- Get current authentication typesetAuthType(type)- Change authentication typeclearToken()- Clear authentication tokengetTokenMasked()- Get masked token for displaysetApiKey(apiKey)- Update API keygetApiKeyMasked()- Get masked API key for displaygetAxiosInstance()- Get underlying axios instance
Session Management
Inactivity Detection:
enableInactivityDetection(options)- Enable inactivity detection ที่จับ user activity (mouse/keyboard/touch/scroll)- จับ user activity และ trigger callback เมื่อ inactivity timeout
- ใช้ timeout และ warning time จาก session management config เป็น default
- Support SSR (Next.js) โดยไม่ error
// Enable inactivity detection
await authClient.enableInactivityDetection({
callbacks: {
onInactivityWarning: (remainingSeconds) => {
console.log(`Inactivity warning: ${remainingSeconds} seconds remaining`);
// แสดง notification หรือ dialog
alert(`Session will expire in ${remainingSeconds} seconds due to inactivity`);
},
onInactivityTimeout: () => {
console.log('Inactivity timeout');
// Redirect to login หรือ logout
authClient.clearToken();
window.location.href = '/login';
},
},
timeoutSeconds: 1800, // 30 minutes (optional, default: จาก session management config)
warningSeconds: 300, // 5 minutes warning (optional, default: จาก session management config)
events: ['mousemove', 'keydown', 'touchstart', 'scroll'], // (optional, default: ทั้งหมด)
});
// Disable inactivity detection
authClient.disableInactivityDetection();Session Expiration Monitoring:
enableSessionExpirationMonitoring(options)- Enable session expiration monitoring- ตรวจสอบ session expiration ตาม config จาก settings/sessions
- เรียก callback เมื่อใกล้หมดอายุ
await authClient.enableSessionExpirationMonitoring({
callbacks: {
onSessionExpiring: (remainingMinutes, expirationType) => {
console.log(`Session will expire in ${remainingMinutes} minutes (${expirationType})`);
// แสดง notification หรือ dialog
},
onSessionExpired: () => {
console.log('Session expired');
// Redirect to login
},
},
checkIntervalSeconds: 30, // ตรวจสอบทุก 30 วินาที
});Authentication Types
The SDK supports three authentication types through the X-Auth-Type header:
JWT Authentication (Recommended for internal users)
// Login and get JWT token
const session = await authClient.auth.login('user@example.com', 'password');
// ✅ Specify 'jwt' for best performance (fast path validation)
authClient.setToken(session.token, 'jwt');
// All subsequent requests will include:
// Authorization: Bearer <jwt_token>
// X-Auth-Type: jwtUse JWT when:
- User logs in with username/password
- Internal application users
- Need session-based authentication
OAuth Authentication (Recommended for external apps)
// Exchange authorization code for tokens
const tokens = await authClient.oauth.exchangeCode(code, codeVerifier);
// ✅ Specify 'oauth' for best performance (fast path validation)
authClient.setToken(tokens.access_token, 'oauth');
// All subsequent requests will include:
// Authorization: Bearer <oauth_access_token>
// X-Auth-Type: oauthUse OAuth when:
- Third-party application integration
- User consent-based access
- Need scope-based permissions
Hybrid Mode (Backward compatible)
// Don't know token type or want automatic detection
authClient.setToken(someToken, 'hybrid');
// All subsequent requests will include:
// Authorization: Bearer <token>
// X-Auth-Type: hybrid
// API will try JWT first, then fallback to OAuthUse Hybrid when:
- Backward compatibility needed
- Token type is unknown
- Migrating from old implementation
Performance Comparison
| Auth Type | Validation | Performance | Use Case |
|---|---|---|---|
jwt |
JWT only | ⚡ Fast (1 validation) | Internal users, password login |
oauth |
OAuth only | ⚡ Fast (1 validation) | External apps, OAuth flow |
hybrid |
JWT → OAuth fallback | 🐌 Slower (2 validations) | Unknown token type |
💡 Tip: Always specify 'jwt' or 'oauth' explicitly for ~50% faster authentication!
Security Best Practices
- Never commit API keys - Use environment variables
- Rotate keys regularly - Update API keys periodically through main API
- Use different keys per environment - Separate keys for dev, staging, production
- Monitor key usage - Track usage through main API dashboard
- Set expiration dates - Configure key expiration in main API
- Whitelist IPs - Configure IP restrictions in main API
Error Handling
try {
const response = await authClient.get('/api/v1/data');
} catch (error: any) {
if (error.response?.status === 401) {
console.error('Invalid or expired API key');
} else if (error.response?.status === 429) {
console.error('Rate limit exceeded');
} else {
console.error('Request failed:', error.message);
}
}How to Get API Key
- Login to the main Win Portal application
- Navigate to Applications section
- Create or select your application
- Generate a new API key
- Copy and save the key (shown only once)
- Configure the key in your environment variables
Documentation
- API Functions Reference - สรุปฟังก์ชันทั้งหมดที่ SDK รองรับ 🆕
- Frontend Examples - Next.js, React usage
- Middleware Guide - Express & NestJS
- Type Safety Guide - Express type augmentation 🆕
- Type Names Guide - SDK-friendly type names 🆕
- Next.js Guide - คู่มือ Next.js ภาษาไทย
- OAuth Next.js Guide - คู่มือ OAuth กับ Next.js
- NestJS Guide - คู่มือ NestJS
- Thai Documentation - คู่มือภาษาไทย
- Publishing Guide - How to publish updates
License
MIT