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": "Zhang San",          // // Required: User's real name
 "email": "zhangsan@example.com",  // // Required: User email (domain used for allowlist check)
 "dept_name": "Engineering",   // // Optional: User department (for logging and statistics)
 "iat": 1715328000,       // // Issued at (Unix timestamp in seconds)
 "exp": 1715331600        // // Expiration (Unix timestamp in seconds)
}
FieldTypeRequiredDescription
namestringRequiredUser's real name
emailstringRequiredUser email (domain used for allowlist check)
dept_namestringOptionalUser department (for logging and statistics)
iatnumberRecommendedIssued at (Unix timestamp in seconds)
expnumberRecommendedExpiration (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: Server-signed JWT

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 generate function
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;
}

// // Generate JWT Token
const payload = {
  name: 'Zhang San',
  email: 'zhangsan@example.com',
  dept_name: 'Engineering',
  iat: Math.floor(Date.now() / 1000),
  exp: Math.floor(Date.now() / 1000) + 3600, // // 1 hour expiration
};

const secret = 'YOUR_KEY_SECRET'; // // Secret returned after creating the Key
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
# ===== # ===== Server: Generate RSA key pair ===== =====
# # Generate RSA private key (2048-bit)
openssl genrsa -out private_key.pem 2048

# # Extract public key from private key
openssl rsa -in private_key.pem -pubout -out public_key.pem

# # View public key content (copy this to admin panel)
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';

// // Server: Sign JWT using private key (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: 'Zhang San',
  email: 'zhangsan@example.com',
  dept_name: 'Engineering',
  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);

// // Client: Use OpenAI SDK to call
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": "Hello, please introduce yourself"
      }
    ]
  }'

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: 'Zhang San',
    email: 'zhangsan@example.com',
    dept_name: 'Engineering',
    iat: Math.floor(Date.now() / 1000),
    exp: Math.floor(Date.now() / 1000) + 3600,
  },
  'YOUR_KEY_SECRET'
);

// // Use OpenAI SDK to call
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: 'Hello, please introduce yourself' }],
});

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.