Skip to main content

Overview

Genie Helper uses Directus as its authentication backend. The API supports three authentication strategies:
  1. Directus JWT - User authentication for client-facing endpoints
  2. Shared Secret - Server-to-server communication (credential encryption)
  3. Admin Token - Internal privileged operations (never exposed to clients)

Directus JWT Authentication

Login Flow

Clients authenticate via the Directus /auth/login endpoint: Request:
curl -X POST http://127.0.0.1:8055/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "securepassword123"
  }'
Response:
{
  "data": {
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEyMzQiLCJyb2xlIjoiODA0ZDQ4N2UtMjIwMC00Y2M0LWIxODgtNzA0NjU0NzFlMGE0IiwiaWF0IjoxNzA5MDAwMDAwfQ...",
    "refresh_token": "rVzG8...",
    "expires": 900000
  }
}

Using the Token

Include the access_token in the Authorization header for all authenticated requests:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Example - Generate Caption:
curl -X POST http://localhost:3001/api/captions/generate \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "onlyfans",
    "tone": "playful",
    "topic": "new photo set",
    "length": "medium"
  }'

Token Validation

Endpoints validate tokens by calling Directus /users/me:
// From captions.js:42
const me = await fetch(`${DIRECTUS_URL}/users/me?fields=id,email,first_name`, {
  headers: { Authorization: `Bearer ${token}` },
});
if (!me.ok) {
  res.status(401).json({ ok: false, error: "Invalid or expired token" });
}
If validation fails, the endpoint returns 401 Unauthorized.

Token Refresh

When the access token expires (default TTL: 15 minutes), use the refresh token:
curl -X POST http://127.0.0.1:8055/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{"refresh_token": "rVzG8..."}'
Response:
{
  "data": {
    "access_token": "<new_access_token>",
    "refresh_token": "<new_refresh_token>",
    "expires": 900000
  }
}

Shared Secret (X-RBAC-SYNC-SECRET)

Purpose

Server-to-server endpoints that handle sensitive operations (credential encryption/decryption) require a shared secret header instead of user JWT. Endpoints using this method:
  • POST /api/credentials/store
  • POST /api/credentials/reveal
  • POST /api/credentials/reveal-download-cred
  • POST /api/credentials/get-platform-session

Configuration

Set in .env:
RBAC_SYNC_WEBHOOK_SECRET=your-random-secret-key-min-32-chars

Usage

curl -X POST http://localhost:3001/api/credentials/store \
  -H "X-RBAC-SYNC-SECRET: your-random-secret-key-min-32-chars" \
  -H "Content-Type: application/json" \
  -d '{
    "creatorProfileId": "uuid-1234",
    "credentials": {"api_key": "secret123"}
  }'

Validation Logic

// From credentials.js:39
function assertSecret(req) {
  const secret = process.env.RBAC_SYNC_WEBHOOK_SECRET;
  if (!secret) throw Object.assign(new Error("RBAC_SYNC_WEBHOOK_SECRET not set"), { status: 500 });
  const hdr = req.header("X-RBAC-SYNC-SECRET");
  if (!hdr || hdr !== secret)
    throw Object.assign(new Error("Unauthorized"), { status: 401 });
}
Never expose RBAC_SYNC_WEBHOOK_SECRET to client-side code. This is strictly for server-to-server communication.

Admin Token (DIRECTUS_ADMIN_TOKEN)

Purpose

Some operations require Directus admin privileges but should not require end-users to be admins:
  1. User registration (/api/register) - Creates users via admin token so public registration doesn’t require open Directus permissions
  2. Credential storage - Writes to platform_connections table via admin token
  3. Queue stats - Reads queue metadata

Configuration

# .env
DIRECTUS_ADMIN_TOKEN=agentx-directus-admin-static-2026

Example - Registration Flow

From register.js:43:
const dRes = await fetch(`${DIRECTUS_URL}/users`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${DIRECTUS_ADMIN_TOKEN}`,
  },
  body: JSON.stringify({
    email,
    password,
    first_name: firstName || email.split("@")[0],
    status: "active",
    role: "804d487e-2200-4cc4-b188-70465471e0a4", // Pro role
    subscription_tier: "pro",
  }),
});
This allows public users to register without requiring directus_users to have public create permissions.
Never expose DIRECTUS_ADMIN_TOKEN in client code or API responses. It grants full database access.

User Role Resolution

Some endpoints verify admin status by checking the authenticated user’s role:
// From genieChat.js:189
const meRes = await fetch(
  `${DIRECTUS_URL}/users/me?fields=admin_access,role.name`,
  { headers: { Authorization: `Bearer ${req.header("Authorization")?.slice(7)}` } }
);
const me = await meRes.json();
const isAdmin =
  me?.data?.admin_access === true ||
  me?.data?.role?.name === "Administrator";
if (!isAdmin) {
  return res.status(403).json({ error: "Admin only" });
}

Security Best Practices

  • Store tokens in secure, httpOnly cookies or native secure storage
  • Never store tokens in localStorage (XSS risk)
  • Implement automatic token refresh before expiry
  • Rotate RBAC_SYNC_WEBHOOK_SECRET and DIRECTUS_ADMIN_TOKEN regularly
  • Use different secrets for development/staging/production
  • Never commit secrets to version control
  • Always use HTTPS in production (enable via ENABLE_HTTPS=true)
  • Configure SSL certificates (HTTPS_CERT_PATH, HTTPS_KEY_PATH)
  • Tokens transmitted over HTTP can be intercepted
  • All endpoints validate tokens by calling /users/me (no local JWT verification)
  • This ensures revoked tokens are immediately invalid
  • Adds latency but improves security

Next Steps

Credentials API

Learn how to encrypt and manage platform credentials

Build docs developers (and LLMs) love