Xác thực
API SMSBAT ChatHub sử dụng hệ thống xác thực dựa trên mã thông báo JWT hai cấp với mã thông báo của công ty và mã thông báo của nhà điều hành.
Luồng xác thực
graph TD
A[Login Credentials] --> B[Get Company Token]
B --> C[Use Company Token]
C --> D[Get Operator Token]
D --> E[Use Operator Token]
E --> F[Validate Token]
Mã thông báo công ty
Mã thông báo của công ty cung cấp quyền truy cập cấp tổ chức vào API ChatHub.
Nhận mã thông báo của công ty
Điểm cuối: POST /api/company/get-token
Yêu cầu:
curl -X POST https://chatapi.smsbat.com/api/company/get-token \
-H "Content-Type: application/json" \
-d '{
"login": "your-company-login",
"password": "your-company-password"
}'
Nội dung yêu cầu:
Trả lời:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
Phản hồi là chuỗi mã thông báo JWT.
Sử dụng mã thông báo của công ty
Bao gồm mã thông báo của công ty trong các yêu cầu API bằng một trong hai phương pháp:
Cách 1: Tiêu đề ủy quyền (Khuyến nghị)
curl -X GET https://chatapi.smsbat.com/api/company/organization \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Cách 2: Tiêu đề X-Authorization-Key
curl -X GET https://chatapi.smsbat.com/api/company/organization \
-H "X-Authorization-Key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Mã thông báo của người vận hành
Mã thông báo của người vận hành cung cấp quyền truy cập dành riêng cho người dùng cho từng người vận hành trong một tổ chức.
Nhận mã thông báo của nhà điều hành
Điểm cuối: POST /api/operator/get-token
Yêu cầu:
curl -X POST https://chatapi.smsbat.com/api/operator/get-token \
-H "Authorization: Bearer {company-token}" \
-H "Content-Type: application/json" \
-d '{
"id": 123,
"expiresAt": "2025-12-31T23:59:59Z"
}'
Nội dung yêu cầu:
Thông số:
| Tham số | Loại | Bắt buộc | Mô tả |
|---|---|---|---|
id |
số nguyên | Có | ID nhà điều hành |
hết hạnAt |
chuỗi (ISO 8601) | Có | Ngày và giờ hết hạn mã thông báo (tối đa 24 giờ kể từ bây giờ) |
Quan trọng: Thời gian tồn tại tối đa của mã thông báo là 24 giờ. Thông số expiresAt không thể dài hơn 24 giờ trong tương lai.
Trả lời:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyYXRvcl9pZCI6MTIzLCJleHAiOjE3Mzc0MTI3OTl9.example_signature"
Sử dụng mã thông báo của người vận hành
Bao gồm mã thông báo của nhà điều hành trong các yêu cầu API:
Xác thực mã thông báo
Xác minh rằng mã thông báo vẫn hợp lệ trước khi sử dụng.
Điểm cuối: POST /api/operator/validate-token
Yêu cầu:
curl -X POST https://chatapi.smsbat.com/api/operator/validate-token \
-H "Authorization: Bearer {company-token}" \
-H "Content-Type: application/json" \
-d '{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}'
Nội dung yêu cầu:
Phản hồi (Mã thông báo hợp lệ):
{
"isValid": true,
"operatorId": 123,
"clientId": 0,
"expiresAt": "2025-12-31T23:59:59Z",
"error": null
}
Phản hồi (Mã thông báo không hợp lệ):
Mã thông báo hết hạn
Mã thông báo của công ty
- Không có sự hết hạn rõ ràng trong API
- Liên hệ với người quản lý tài khoản của bạn để biết chính sách vòng đời mã thông báo
- Xoay token định kỳ để bảo mật
Mã thông báo của người vận hành
- Đặt hết hạn khi yêu cầu mã thông báo (tham số
expiresAt) - Xác thực mã thông báo trước khi sử dụng
- Yêu cầu mã thông báo mới trước khi hết hạn
Ví dụ triển khai
Python
import requests
from datetime import datetime, timedelta
class ChatHubAuth:
def __init__(self, base_url):
self.base_url = base_url
self.company_token = None
self.operator_tokens = {}
def get_company_token(self, login, password):
"""Get company authentication token"""
response = requests.post(
f"{self.base_url}/api/company/get-token",
json={"login": login, "password": password}
)
response.raise_for_status()
self.company_token = response.json()
return self.company_token
def get_operator_token(self, operator_id, expires_days=30):
"""Get operator token with expiration"""
expires_at = (
datetime.utcnow() + timedelta(days=expires_days)
).isoformat() + "Z"
response = requests.post(
f"{self.base_url}/api/operator/get-token",
headers={"Authorization": f"Bearer {self.company_token}"},
json={"id": operator_id, "expiresAt": expires_at}
)
response.raise_for_status()
token = response.json()
self.operator_tokens[operator_id] = token
return token
def validate_token(self, token):
"""Validate if token is still valid"""
response = requests.post(
f"{self.base_url}/api/operator/validate-token",
headers={"Authorization": f"Bearer {self.company_token}"},
json={"token": token}
)
response.raise_for_status()
return response.json()
# Usage
auth = ChatHubAuth("https://chatapi.smsbat.com")
# Get company token
company_token = auth.get_company_token("login", "password")
# Get operator token
operator_token = auth.get_operator_token(operator_id=123, expires_days=30)
# Validate token
is_valid = auth.validate_token(operator_token)
print(f"Token valid: {is_valid['valid']}")
JavaScript (Node.js)
const axios = require('axios');
class ChatHubAuth {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.companyToken = null;
this.operatorTokens = {};
}
async getCompanyToken(login, password) {
const response = await axios.post(
`${this.baseUrl}/api/company/get-token`,
{ login, password }
);
this.companyToken = response.data;
return this.companyToken;
}
async getOperatorToken(operatorId, expiresDays = 30) {
const expiresAt = new Date(
Date.now() + expiresDays * 24 * 60 * 60 * 1000
).toISOString();
const response = await axios.post(
`${this.baseUrl}/api/operator/get-token`,
{ id: operatorId, expiresAt },
{
headers: {
Authorization: `Bearer ${this.companyToken}`
}
}
);
const token = response.data;
this.operatorTokens[operatorId] = token;
return token;
}
async validateToken(token) {
const response = await axios.post(
`${this.baseUrl}/api/operator/validate-token`,
{ token },
{
headers: {
Authorization: `Bearer ${this.companyToken}`
}
}
);
return response.data;
}
}
// Usage
const auth = new ChatHubAuth('https://chatapi.smsbat.com');
async function authenticate() {
// Get company token
const companyToken = await auth.getCompanyToken('login', 'password');
// Get operator token
const operatorToken = await auth.getOperatorToken(123, 30);
// Validate token
const validation = await auth.validateToken(operatorToken);
console.log('Token valid:', validation.isValid);
}
authenticate();
PHP
<?php
class ChatHubAuth {
private $baseUrl;
private $companyToken;
private $operatorTokens = [];
public function __construct($baseUrl) {
$this->baseUrl = $baseUrl;
}
public function getCompanyToken($login, $password) {
$ch = curl_init($this->baseUrl . '/api/company/get-token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'login' => $login,
'password' => $password
]));
$response = curl_exec($ch);
curl_close($ch);
$this->companyToken = json_decode($response);
return $this->companyToken;
}
public function getOperatorToken($operatorId, $expiresDays = 30) {
$expiresAt = date(
'Y-m-d\TH:i:s\Z',
time() + ($expiresDays * 24 * 60 * 60)
);
$ch = curl_init($this->baseUrl . '/api/operator/get-token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $this->companyToken
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'id' => $operatorId,
'expiresAt' => $expiresAt
]));
$response = curl_exec($ch);
curl_close($ch);
$token = json_decode($response);
$this->operatorTokens[$operatorId] = $token;
return $token;
}
public function validateToken($token) {
$ch = curl_init($this->baseUrl . '/api/operator/validate-token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $this->companyToken
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'token' => $token
]));
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
}
// Usage
$auth = new ChatHubAuth('https://chatapi.smsbat.com');
// Get company token
$companyToken = $auth->getCompanyToken('login', 'password');
// Get operator token
$operatorToken = $auth->getOperatorToken(123, 30);
// Validate token
$validation = $auth->validateToken($operatorToken);
echo "Token valid: " . ($validation['isValid'] ? 'Yes' : 'No');
Các phương pháp hay nhất
Lưu trữ mã thông báo
- ✅ Lưu trữ token một cách an toàn (cơ sở dữ liệu được mã hóa, trình quản lý bí mật)
- ✅ Không bao giờ cam kết mã thông báo để kiểm soát phiên bản
- ✅ Sử dụng biến môi trường cho thông tin xác thực
- ❌ Không lưu trữ mã thông báo ở dạng văn bản thuần túy
- ❌ Không để lộ token trong mã phía máy khách
Xoay vòng mã thông báo
- Luân chuyển token công ty định kỳ (3-6 tháng một lần)
- Đặt thời hạn hợp lý cho token của nhà điều hành (7-30 ngày)
- Thực hiện làm mới mã thông báo tự động trước khi hết hạn
- Thu hồi token khi người vận hành rời đi
Xử lý lỗi
async function authenticateWithRetry(login, password, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await getCompanyToken(login, password);
} catch (error) {
if (error.response?.status === 401) {
throw new Error('Invalid credentials');
}
if (i === retries - 1) throw error;
// Wait before retry
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, i) * 1000)
);
}
}
}
Xác thực mã thông báo
Luôn xác thực mã thông báo trước các hoạt động quan trọng:
async function performSecureOperation(token, operation) {
// Validate token first
const validation = await validateToken(token);
if (!validation.isValid) {
throw new Error('Token expired or invalid');
}
// Proceed with operation
return await operation();
}
Cân nhắc về bảo mật
Chỉ HTTPS
Luôn sử dụng HTTPS khi gửi yêu cầu xác thực:
// ✅ Correct
const baseUrl = 'https://chatapi.smsbat.com';
// ❌ Wrong - never use HTTP for authentication
const baseUrl = 'http://api.chathub.smsbat.com';
Phạm vi mã thông báo
Sử dụng mã thông báo thích hợp cho từng thao tác:
- Company Token: Quản lý tổ chức, tạo nhà điều hành
- Mã thông báo của nhà điều hành: Thao tác trò chuyện, xử lý tin nhắn
Giới hạn tỷ lệ
Triển khai giới hạn tốc độ cho các yêu cầu xác thực:
class RateLimitedAuth {
constructor() {
this.lastRequest = 0;
this.minInterval = 1000; // 1 second between requests
}
async getToken(login, password) {
const now = Date.now();
const timeSinceLastRequest = now - this.lastRequest;
if (timeSinceLastRequest < this.minInterval) {
await new Promise(resolve =>
setTimeout(resolve, this.minInterval - timeSinceLastRequest)
);
}
this.lastRequest = Date.now();
return await makeAuthRequest(login, password);
}
}
Khắc phục sự cố
401 trái phép
- Xác minh thông tin đăng nhập là chính xác
- Mã kiểm tra chưa hết hạn
- Đảm bảo mã thông báo được bao gồm trong tiêu đề yêu cầu
- Xác thực định dạng mã thông báo
403 Bị cấm
- Xác minh mã thông báo có quyền cần thiết
- Kiểm tra xem sử dụng đúng loại mã thông báo (công ty so với nhà điều hành)
- Đảm bảo token chưa bị thu hồi
Mã thông báo đã hết hạn
- Yêu cầu mã thông báo mới
- Thực hiện làm mới mã thông báo tự động
- Đặt thời gian hết hạn phù hợp
Các bước tiếp theo
- Tổ chức - Quản lý tổ chức
- Toán tử - Làm việc với các toán tử
- Tích hợp tiện ích - Tích hợp tiện ích trò chuyện