Skip to main content

Authentication

Hệ thống Boxme Scoring System API hỗ trợ 3 phương thức xác thực theo thứ tự ưu tiên:

#Phương thứcHeaderDùng choƯu tiên
1API TokenAuthorization: Bearer blv_... hoặc X-API-Key: blv_...Tích hợp hệ thống (S2S), Script, CI/CD⭐ Khuyên dùng
2Supabase SessionAuthorization: Bearer <jwt>Frontend web appCho frontend
3Legacy HeaderX-User-Id: <id>Hệ thống cũ⚠️ Sẽ bị gỡ

Luồng xác thực (Authentication Flow)

flowchart TD
A[Request đến API] --> B{Có header Authorization hoặc X-API-Key?}
B -- "Bearer blv_..." --> C[Hash SHA-256 → Lookup api_tokens]
B -- "X-API-Key: blv_..." --> C
C --> D{Token tồn tại & is_active?}
D -- Không --> E["❌ 401 Token không hợp lệ"]
D -- Có --> F{Hết hạn?}
F -- Có --> G["❌ 401 Token đã hết hạn"]
F -- Không --> H["✅ Auth OK — set context"]

B -- Không có token --> I{Có X-User-Id?}
I -- Có --> J[Lookup users by ID]
J --> K{User active?}
K -- Có --> H
K -- Không --> L["❌ 401 User không tồn tại"]

I -- Không --> M{Bearer JWT (không phải blv_)?}
M -- Có --> N[Supabase getUser]
N --> O{Valid?}
O -- Có --> H
O -- Không --> P["❌ 401 Xác thực không hợp lệ"]
M -- Không --> P

1. API Token (Khuyên dùng cho Integration)

Phương thức an toàn nhất để tích hợp giữa các hệ thống. Token có tiền tố blv_ (52 ký tự) và được lưu dưới dạng hash SHA-256 — server không bao giờ lưu token gốc.

1.1. Tạo Token

Gọi API với quyền xác thực hiện tại (lần đầu có thể dùng X-User-Id):

# Tạo User Token
curl -X POST https://scoring.boxme.tech/api/tokens \
-H "Content-Type: application/json" \
-H "X-User-Id: 1" \
-d '{
"name": "WMS Integration",
"expires_in_days": 90
}'

Response (⚠️ Token chỉ hiển thị MỘT LẦN DUY NHẤT):

{
"success": true,
"id": 1,
"token": "blv_a1b2c3d4e5f6789012345678901234567890abcdef1234",
"prefix": "blv_a1b2",
"name": "WMS Integration",
"scope": "user",
"expires_at": "2026-05-27T07:00:00.000Z",
"message": "⚠️ Lưu lại token này ngay! Token chỉ hiển thị một lần duy nhất."
}
Quan trọng

Hãy lưu token ngay lập tức vào biến môi trường hoặc secret manager. Sau khi response được trả về, không có cách nào lấy lại token gốc.

1.2. Sử dụng Token

Có 2 cách gửi token trong request:

# Cách 1: Authorization Bearer (Khuyên dùng — chuẩn OAuth 2)
curl https://scoring.boxme.tech/api/employees?status=active \
-H "Authorization: Bearer blv_a1b2c3d4e5f6..."

# Cách 2: X-API-Key header
curl https://scoring.boxme.tech/api/employees?status=active \
-H "X-API-Key: blv_a1b2c3d4e5f6..."

1.3. Master Token vs User Token

Thuộc tínhUser TokenMaster Token
Scopeusermaster
QuyềnTheo role của user tạo tokenFull quyền (tương đương BOD)
Ai tạo được?Mọi user đã xác thựcChỉ user có role bod
user_idGắn với user cụ thểNULL (system-wide)
EndpointPOST /api/tokensPOST /api/tokens/master
Use caseScript cá nhân, integration riêngSystem-to-system, CronJob toàn hệ thống
# Tạo Master Token (chỉ BOD)
curl -X POST https://scoring.boxme.tech/api/tokens/master \
-H "Content-Type: application/json" \
-H "Authorization: Bearer blv_existing_bod_token" \
-d '{ "name": "CronJob Daily PPH Sync" }'

1.4. Quản lý Token

# Liệt kê token của bạn
curl https://scoring.boxme.tech/api/tokens \
-H "Authorization: Bearer blv_your_token"

# Thu hồi token (soft delete — set is_active = false)
curl -X DELETE https://scoring.boxme.tech/api/tokens/3 \
-H "Authorization: Bearer blv_your_token"

# Xác minh token hợp lệ (public endpoint — không cần auth header)
curl -X POST https://scoring.boxme.tech/api/tokens/verify \
-H "Content-Type: application/json" \
-d '{ "token": "blv_a1b2c3d4e5f6..." }'

Response /tokens/verify:

{
"valid": true,
"token_info": {
"id": 1,
"name": "WMS Integration",
"prefix": "blv_a1b2",
"scope": "user",
"user_id": 1,
"username": "admin",
"role": "bod",
"warehouse_id": null,
"expires_at": "2026-05-27T07:00:00.000Z"
}
}

2. Supabase Session (Frontend)

Dành cho ứng dụng frontend gọi API trực tiếp sau khi user đăng nhập qua Supabase Auth.

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)

// Bước 1: Đăng nhập
const { data } = await supabase.auth.signInWithPassword({
email: 'admin@boxme.asia',
password: '...'
})

// Bước 2: Lấy access token
const token = data.session.access_token

// Bước 3: Gọi API
const response = await fetch('https://scoring.boxme.tech/api/employees', {
headers: { 'Authorization': `Bearer ${token}` }
})
info

Middleware tự động xác thực JWT qua supabase.auth.getUser() và ánh xạ supabase_uidusers.id trong database.


3. Legacy Header (X-User-Id)

Phương thức đơn giản cho hệ thống cũ hoặc môi trường dev.

curl https://scoring.boxme.tech/api/employees \
-H "X-User-Id: 1"
Deprecated

Phương thức này không an toàn (không verificate danh tính thực) và sẽ bị gỡ trong phiên bản tương lai. Hãy chuyển sang API Token.


AuthContext (cho Developer Backend)

Sau khi middleware xác thực thành công, request handler nhận được AuthContext qua c.get('auth'):

interface AuthContext {
user_id: number // ID user trong bảng users
role: string // 'bod' | 'warehouse_manager' | 'warehouse_leader' | 'hr' | 'staff'
warehouse_id: number | null // null = toàn hệ thống (BOD/HR)
scope: 'user' | 'master' | 'session' | 'legacy'
token_id: number | null // ID trong bảng api_tokens (null nếu legacy/session)
}

Phân quyền theo role — sử dụng requireRole() middleware:

import { requireRole } from '../lib/auth-middleware'

// Chỉ BOD và HR mới được export payroll
app.get('/api/export/payroll', requireRole('bod', 'hr'), async (c) => {
const auth = c.get('auth')
// auth.user_id, auth.role, auth.warehouse_id sẵn sàng
})
Master Token

Token có scope master sẽ bypass tất cả role check. Sử dụng cẩn thận cho CronJob và integration system-wide.


Ví dụ tích hợp End-to-End

Kịch bản: WMS đồng bộ PPH hàng ngày

# wms_sync.py — CronJob chạy lúc 1:00 AM mỗi ngày
import requests
import os

API_URL = "https://scoring.boxme.tech/api"
API_TOKEN = os.environ["BOXME_SCORING_TOKEN"] # blv_...

headers = {
"Authorization": f"Bearer {API_TOKEN}",
"Content-Type": "application/json"
}

# 1. Lấy danh sách nhân viên active
employees = requests.get(
f"{API_URL}/employees?status=active&limit=500",
headers=headers
).json()

# 2. Tính PPH từ WMS data
for emp in employees["data"]:
pph_data = calculate_pph_from_wms(emp["employee_code"])

# 3. Push violations tự động (nếu có)
if pph_data.get("violations"):
for v in pph_data["violations"]:
requests.post(f"{API_URL}/violations", headers=headers, json={
"employee_id": emp["id"],
"violation_type_id": v["type_id"],
"date": v["date"],
"penalty_points": v["points"],
"created_by": 1 # system user
})

Kịch bản: Webhook handler nhận token

// webhook-handler.js — Xác minh token trước khi xử lý webhook
app.post('/webhook/scoring', async (req, res) => {
const token = req.headers['x-api-key']

// Verify token với Scoring System
const verify = await fetch('https://scoring.boxme.tech/api/tokens/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token })
})

const result = await verify.json()
if (!result.valid) {
return res.status(401).json({ error: 'Invalid token' })
}

// Token hợp lệ — xử lý webhook
console.log(`Received from user: ${result.token_info.username}`)
})

Bảng mã lỗi Authentication

HTTP StatusError MessageNguyên nhânCách xử lý
401Token không hợp lệ hoặc đã bị thu hồiToken sai, đã revoke, hoặc không tồn tạiKiểm tra lại token, tạo token mới nếu cần
401Token đã hết hạnToken quá expires_atTạo token mới với POST /api/tokens
401User không tồn tại hoặc đã bị vô hiệu hóaUser bị deactivate hoặc ID saiLiên hệ admin để kích hoạt lại
401Xác thực không hợp lệKhông có header auth nào hợp lệThêm header Authorization hoặc X-API-Key
403Tài khoản đã bị vô hiệu hóaToken hợp lệ nhưng user bị disableLiên hệ admin
403Bạn cần quyền ... để thực hiện thao tác nàyRole không đủ quyềnDùng token của user có role phù hợp hoặc master token

Database Schema: api_tokens

CREATE TABLE api_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER REFERENCES users(id), -- NULL cho master token
name TEXT NOT NULL, -- Tên mô tả token
token_hash TEXT NOT NULL UNIQUE, -- SHA-256 hash
token_prefix TEXT NOT NULL, -- 8 ký tự đầu (hiển thị)
scope TEXT DEFAULT 'user', -- 'user' | 'master'
permissions TEXT DEFAULT '{}', -- JSON permissions (reserved)
is_active BOOLEAN DEFAULT TRUE, -- FALSE = đã thu hồi
last_used_at DATETIME, -- Cập nhật mỗi lần sử dụng
expires_at DATETIME, -- NULL = không hết hạn
created_by INTEGER REFERENCES users(id),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

Checklist tích hợp nhanh

  • Tạo token qua POST /api/tokens (hoặc /tokens/master cho system token)
  • Lưu token vào Secret Manager / biến môi trường
  • Gửi token qua Authorization: Bearer blv_... trong mọi request
  • Xử lý HTTP 401 / 403 trong error handler
  • Định kỳ rotate token (tạo mới → revoke cũ)