User Access Control
Implement user-level access control via JWT
Overview
For App-type API Keys, enable user-level access control with JWT.
Use case: JWT distinguishes user identity for per-user tracking.
- Identify your most active users
- Detect abnormal usage (e.g., a user consuming far more tokens than average)
- Analyze cost by department or individual
Simply set the Key type to "App", enable "Per-user Stats", and configure a domain allowlist. When the client includes a JWT with user identity, the gateway automatically distinguishes each user's usage and displays per-user token consumption on the statistics page.
How It Works
The access control flow:
Client sends request with JWT
Include X-JWT-Token header with signed JWT.
Gateway validates API Key
Gateway validates the API Key and checks if it's App-type with per-user stats enabled.
JWT Token validation
Gateway verifies the JWT signature.
Domain allowlist check
Gateway checks email domain against allowlist.
Request forwarding
After all checks pass, gateway forwards the request.
Configuration Steps
Create App-type Key
Go to "Key Management" and click "Create Key".
- Type: Select "App"
- Per-user stats: Enable: Enable this option
- Domain allowlist: Configure allowed domains: Configure allowed email domains (e.g.,
example.com)
Important:Important: Save the secret securely.
Configure Domain Allowlist
Configure allowed email domains in system settings.
Example: If the domain allowlist is configured as example.com, only users with *@example.com emails will pass validation.
Generate JWT Server-Side
Use the secret to sign JWT Tokens on your server.
{
"name": "张三", // 必填:用户真实姓名
"email": "zhangsan@example.com", // 必填:用户邮箱(用于域名校验)
"dept_name": "技术部", // 可选:用户所属部门(用于日志和统计)
"iat": 1715328000, // 签发时间(Unix 秒级时间戳)
"exp": 1715331600 // 过期时间(Unix 秒级时间戳)
}| Field | Type | Required | Description |
|---|---|---|---|
| name | string | 是 | User's real name |
| string | 是 | User email (domain used for allowlist check) | |
| dept_name | string | 否 | User department (for logging and statistics) |
| iat | number | 建议 | Issued at (Unix timestamp in seconds) |
| exp | number | 建议 | Expiration (Unix timestamp in seconds) |
Client Calls API with JWT
Include both API Key and JWT Token in headers.
Authorization头:Bearer <API_KEY>X-JWT-Token头:服务端签发的 JWT Token
Signature Algorithms
Supports HMAC and RSA signature algorithms.
| Algorithm | Type | Key | Use Case |
|---|---|---|---|
| HS256 | Symmetric | Shared Secret | Same key for signing and verification, simple setup |
| HS384 | Symmetric | Shared Secret | Higher security with longer signature |
| HS512 | Symmetric | Shared Secret | Highest security HMAC signature |
| RS256 | Asymmetric | Private + Public Key | Server signs with private key, gateway verifies with public key — more secure |
| RS384 / RS512 | Asymmetric | Private + Public Key | Higher security RSA signature |
HMAC Symmetric (HS256)
Server and gateway share the same secret key.
Get Secret
System generates a secret when creating App Key.
Security Tip:Security: Store secret in environment variables.
Server JWT Signing
Sign JWT using HMAC-SHA256 with the secret.
Client Flow
Client requests JWT from your server
Client includes JWT in X-JWT-Token header
Gateway validates JWT and forwards request
const crypto = require('crypto');
// JWT 生成函数
function base64UrlEncode(obj) {
return Buffer.from(JSON.stringify(obj))
.toString('base64')
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
}
function generateJWT(payload, secret) {
const header = { alg: 'HS256', typ: 'JWT' };
const encodedHeader = base64UrlEncode(header);
const encodedPayload = base64UrlEncode(payload);
const signature = crypto
.createHmac('sha256', secret)
.update(encodedHeader + '.' + encodedPayload)
.digest('base64')
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
return encodedHeader + '.' + encodedPayload + '.' + signature;
}
// 生成 JWT Token
const payload = {
name: '张三',
email: 'zhangsan@example.com',
dept_name: '技术部',
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600, // 1 小时过期
};
const secret = 'YOUR_KEY_SECRET'; // Key 创建成功后返回的 secret
const token = generateJWT(payload, secret);
console.log('JWT Token:', token);RSA Asymmetric (RS256)
Server holds private key, gateway holds public key.
Step 1: Generate RSA Key Pair
Generate an RSA key pair on your server using OpenSSL:
# ===== 服务端:生成 RSA 密钥对 =====
# 生成 RSA 私钥(2048 位)
openssl genrsa -out private_key.pem 2048
# 从私钥提取公钥
openssl rsa -in private_key.pem -pubout -out public_key.pem
# 查看公钥内容(复制此内容配置到管理后台)
cat public_key.pem- Private Key(
private_key.pem):Store securely on the server, never expose it - Public Key(
public_key.pem):Configure in Tarogo AI Admin Panel
Step 2: Configure Public Key in Admin Panel
When creating an App Key, select RS256 as the JWT signature algorithm and paste the public key content into the field.
The gateway uses the configured public key to verify JWT RS256 signatures. The private key remains on your server; the gateway cannot sign JWTs, and public key exposure does not compromise signature security.
Step 3: Sign JWT with Private Key
Sign the JWT using RSA-SHA256 with your private key. The alg field in the JWT header should be set to RS256.
Client Flow
Client requests a JWT from your server (same as HS256 mode)
Server signs the JWT using the private key (instead of a shared secret)
Client attaches the JWT in the X-JWT-Token header
Gateway verifies the signature using the configured public key
import crypto from 'crypto';
import fs from 'fs';
// 服务端:使用私钥签发 JWT(RS256)
function base64UrlEncode(obj: object | string): string {
const input = typeof obj === 'string' ? obj : JSON.stringify(obj);
return Buffer.from(input)
.toString('base64')
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
}
function signRS256(data: string, privateKey: string): string {
const sign = crypto.createSign('RSA-SHA256');
sign.update(data);
sign.end();
return sign.sign(privateKey, 'base64')
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
}
const privateKey = fs.readFileSync('private_key.pem', 'utf8');
const header = base64UrlEncode({ alg: 'RS256', typ: 'JWT' });
const payload = base64UrlEncode({
name: '张三',
email: 'zhangsan@example.com',
dept_name: '技术部',
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600,
});
const token = header + '.' + payload + '.' + signRS256(header + '.' + payload, privateKey);
console.log('JWT Token:', token);
// 客户端:使用 OpenAI SDK 调用
import OpenAI from 'openai';
const client = new OpenAI({
apiKey: 'YOUR_API_KEY',
baseURL: 'https://your-gateway-domain.com/v1',
defaultHeaders: { 'X-JWT-Token': token },
});API Call Example
Whether using HS256 or RS256, the client API call is identical — simply add the X-JWT-Token header.
Using cURL
curl -X POST https://your-gateway-domain.com/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "X-JWT-Token: YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat@deepseek",
"messages": [
{
"role": "user",
"content": "你好,请介绍一下自己"
}
]
}'Using OpenAI SDK (Node.js)
import crypto from 'crypto';
function generateJWT(payload: object, secret: string): string {
const base64UrlEncode = (obj: object) =>
Buffer.from(JSON.stringify(obj))
.toString('base64')
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
const encodedHeader = base64UrlEncode({ alg: 'HS256', typ: 'JWT' });
const encodedPayload = base64UrlEncode(payload);
const signature = crypto
.createHmac('sha256', secret)
.update(encodedHeader + '.' + encodedPayload)
.digest('base64')
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
return encodedHeader + '.' + encodedPayload + '.' + signature;
}
const token = generateJWT(
{
name: '张三',
email: 'zhangsan@example.com',
dept_name: '技术部',
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600,
},
'YOUR_KEY_SECRET'
);
// 使用 OpenAI SDK 调用
import OpenAI from 'openai';
const client = new OpenAI({
apiKey: 'YOUR_API_KEY',
baseURL: 'https://your-gateway-domain.com/v1',
defaultHeaders: {
'X-JWT-Token': token,
},
});
const response = await client.chat.completions.create({
model: 'deepseek-chat@deepseek',
messages: [{ role: 'user', content: '你好,请介绍一下自己' }],
});Common Errors
| Error Code | Description | Resolution |
|---|---|---|
MISS_USERINFO | Missing X-JWT-Token header | Ensure the request includes the X-JWT-Token header |
INVALID_USERINFO | JWT validation failed | Check that the JWT is valid, the secret is correct, and the payload contains email and name |
INVALID_USERINFO (domain) | Email domain not in allowlist | Ensure the user email domain is configured in the Key's domain allowlist |
401 Unauthorized | Invalid or expired API Key | Check that the API Key is valid, enabled, and within its validity period |
Important Notes
- Access control applies only to App-type Keys with per-user stats enabled. Personal Keys do not use JWT validation.
- JWT signing must happen server-side — never use the secret in client code.
- Set a reasonable JWT expiration (e.g., 1 hour) to limit risk if a token is leaked.
- The secret is sensitive. If compromised, reset the Key in the admin panel.
- Domain allowlist changes take effect immediately — no need to recreate the Key.
- Gateway logs record JWT user info (email, name, dept_name) for auditing and troubleshooting.