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.

Scenario
Suppose your company has built a knowledge-base AI application that all employees can use to ask questions. The app backend uses a single API Key to call the Tarogo AI Gateway, but you want to track each employee's token usage to:
  • 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:

1

Client sends request with JWT

Include X-JWT-Token header with signed JWT.

2

Gateway validates API Key

Gateway validates the API Key and checks if it's App-type with per-user stats enabled.

3

JWT Token validation

Gateway verifies the JWT signature.

4

Domain allowlist check

Gateway checks email domain against allowlist.

5

Request forwarding

After all checks pass, gateway forwards the request.

Configuration Steps

1

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.

2

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.

3

Generate JWT Server-Side

Use the secret to sign JWT Tokens on your server.

JWT Payload Specification
{
 "name": "张三",          // 必填:用户真实姓名
 "email": "zhangsan@example.com",  // 必填:用户邮箱(用于域名校验)
 "dept_name": "技术部",   // 可选:用户所属部门(用于日志和统计)
 "iat": 1715328000,       // 签发时间(Unix 秒级时间戳)
 "exp": 1715331600        // 过期时间(Unix 秒级时间戳)
}
FieldTypeRequiredDescription
namestringUser's real name
emailstringUser email (domain used for allowlist check)
dept_namestringUser department (for logging and statistics)
iatnumber建议Issued at (Unix timestamp in seconds)
expnumber建议Expiration (Unix timestamp in seconds)
4

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.

AlgorithmTypeKeyUse Case
HS256SymmetricShared SecretSame key for signing and verification, simple setup
HS384SymmetricShared SecretHigher security with longer signature
HS512SymmetricShared SecretHighest security HMAC signature
RS256AsymmetricPrivate + Public KeyServer signs with private key, gateway verifies with public key — more secure
RS384 / RS512AsymmetricPrivate + Public KeyHigher 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

1

Client requests JWT from your server

2

Client includes JWT in X-JWT-Token header

3

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:

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 Keyprivate_key.pem):Store securely on the server, never expose it
  • Public Keypublic_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

1

Client requests a JWT from your server (same as HS256 mode)

2

Server signs the JWT using the private key (instead of a shared secret)

3

Client attaches the JWT in the X-JWT-Token header

4

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

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 CodeDescriptionResolution
MISS_USERINFOMissing X-JWT-Token headerEnsure the request includes the X-JWT-Token header
INVALID_USERINFOJWT validation failedCheck that the JWT is valid, the secret is correct, and the payload contains email and name
INVALID_USERINFO (domain)Email domain not in allowlistEnsure the user email domain is configured in the Key's domain allowlist
401 UnauthorizedInvalid or expired API KeyCheck 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.