Bỏ qua

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:

{
  "login": "string",
  "password": "string"
}

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:

{
  "id": 0,
  "expiresAt": "2025-01-20T14:33:34.147Z"
}

Thông số:

Tham số Loại Bắt buộc Mô tả
id số nguyên ID nhà điều hành
hết hạnAt chuỗi (ISO 8601) 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:

curl -X GET https://chatapi.smsbat.com/api/operator \
  -H "Authorization: Bearer {operator-token}"

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:

{
  "token": "string"
}

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ệ):

{
  "isValid": false,
  "error": "Invalid token"
}

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