Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

win-portal-auth-sdk

Shared authentication SDK for Win Portal applications with JWT and OAuth support

auth, api-key, sdk, http-client, nestjs, typescript, event-logging, file-upload

readme

@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 accept X-API-Key header
  • Uses HybridAuthGuard which 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-sdk

Usage

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.com

Backend 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:

View Full Middleware Guide →

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 request
  • post<T>(url, data?, config?) - POST request
  • put<T>(url, data?, config?) - PUT request
  • patch<T>(url, data?, config?) - PATCH request
  • delete<T>(url, config?) - DELETE request

Convenience Methods

Authentication:

  • auth.login(email, password) - Login user
  • auth.logout() - Logout current session
  • auth.profile() - Get current user profile
  • auth.refresh(refreshToken) - Refresh access token

User Management:

  • user.search(params?) - Search users with pagination
  • user.get(userId) - Get user by ID
  • user.create(userData) - Create new user
  • user.update(userId, userData) - Update user
  • user.delete(userId) - Delete user
  • user.sync(params?) - Sync users for external applications (paginated)

LINE Messaging:

  • line.sendTextMessage({ userId, message }) - Send text message
  • line.sendSticker({ userId, packageId, stickerId }) - Send LINE sticker
  • line.sendImage({ userId, originalContentUrl, previewImageUrl }) - Send image
  • line.sendMessages({ userId, messages }) - Send multiple messages (max 5)
  • line.sendNotification({ userId, title, message, type?, action_url?, priority? }) - Send formatted notification
  • line.checkMessagingAvailability(userId) - Check if user can receive LINE messages

📖 LINE Messaging Guide → - Complete documentation with examples

Health & Validation:

  • health.check() - Check API health
  • health.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

OTP Service (for external applications):

  • otp.sendOTP({ method, recipient, context?, metadata? }) - Send OTP via SMS or Email

    • method: 'sms' | 'email' - Delivery method
    • recipient: Phone number or email address
    • context: 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 verification
      • ref_code: Short reference code (OTP-XXXXXX) for tracking/billing
      • expires_at: OTP expiration timestamp
      • masked_recipient: Masked phone number or email
    • Requires API Key authentication
  • otp.verifyOTP({ otp_id, code }) - Verify OTP code

    • otp_id: OTP ID from sendOTP response
    • code: 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 template
  • email.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 ID
  • workflow.createInstance(instanceData) - Create a new workflow instance
  • workflow.updateInstance(instanceId, instanceData) - Update workflow instance
  • workflow.searchInstances(params?) - Search workflow instances with pagination
  • workflow.getTask(taskId) - Get workflow task by ID
  • workflow.getUserTasks(params?) - Get user tasks with pagination and filters
  • workflow.performTaskAction(taskId, actionData) - Perform action on task (approve/reject/delegate/escalate)
  • workflow.getDefinition(definitionId) - Get workflow definition by ID
  • workflow.searchDefinitions(params?) - Search workflow definitions with pagination

Organization Management:

  • organization.search(params?) - Search organizations with pagination
  • organization.get(id) - Get organization by ID
  • organization.create(data) - Create new organization
  • organization.update(id, data) - Update organization
  • organization.delete(id) - Delete organization
  • organization.getUsers(id, includeDescendants?) - Get organization users
  • organization.getAncestors(id, includeRoot?) - Get parent organizations at all levels
  • organization.getDirectSupervisor(id) - Get immediate supervisor/parent organization
  • organization.getPath(id) - Get full path from root to organization
  • organization.getReportingLine(id) - Get reporting line (hierarchical and functional)
  • organization.getSiblings(id, includeSelf?) - Get sibling organizations
  • organization.getLevel(id) - Get organization level
  • organization.getCommonAncestor(orgA, orgB) - Get lowest common ancestor
  • organization.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

Utility Methods

  • setToken(token, type?) - Set authentication token
    • type: 'jwt' (default) | 'oauth' | 'hybrid'
    • Recommended to specify type for better performance
  • getAuthType() - Get current authentication type
  • setAuthType(type) - Change authentication type
  • clearToken() - Clear authentication token
  • getTokenMasked() - Get masked token for display
  • setApiKey(apiKey) - Update API key
  • getApiKeyMasked() - Get masked API key for display
  • getAxiosInstance() - 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:

// 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: jwt

Use JWT when:

  • User logs in with username/password
  • Internal application users
  • Need session-based authentication
// 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: oauth

Use 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 OAuth

Use 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

  1. Never commit API keys - Use environment variables
  2. Rotate keys regularly - Update API keys periodically through main API
  3. Use different keys per environment - Separate keys for dev, staging, production
  4. Monitor key usage - Track usage through main API dashboard
  5. Set expiration dates - Configure key expiration in main API
  6. 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

  1. Login to the main Win Portal application
  2. Navigate to Applications section
  3. Create or select your application
  4. Generate a new API key
  5. Copy and save the key (shown only once)
  6. Configure the key in your environment variables

Documentation

📚 Complete Documentation →

License

MIT