Skip to content

Authentication ​

Two credentials let you call the Manager yourself: a Personal Access Token (a token tied to your user account) and a machine user (a service account for a backend). Each works on a different set of routes, listed below. Browser users need neither; the proxy logs them in with OAuth and forwards their identity.

Authentication Methods ​

Personal Access Tokens (PAT) ​

For user-driven API access: CLI tools, scripts, third-party integrations, development and testing.

It works on the GraphQL API (/query) and the pilet management routes (/pilets/*), reached through the proxy (see the warning below). Only a PBKDF2-SHA256 hash (600,000 iterations) is stored; the token itself is shown once.

Machine Users ​

For service-to-service authentication: backend services, automated workflows, trigger execution and system integrations.

It works on /api/machine/* and /esb/* on the Manager, and on application routes behind the proxy. Send it as a Bearer token on the Manager routes; Basic auth is accepted only on proxy-fronted application routes (see below).

Personal Access Tokens ​

Creating a PAT ​

  1. Navigate to Personal Access Tokens in sidebar
  2. Click Create Token
  3. Enter Token Name (e.g., "CLI Access")
  4. Optional: Set Expiration Date
  5. Click Create
  6. Copy the token - shown only once!

Using a PAT ​

Send it as a Bearer token. Tokens look like pat_<8 chars>_<32 chars>.

bash
curl -H "Authorization: Bearer pat_abc123..." \
  https://manager.example.com/query

Call /query through the proxy host, not the Manager port

/query and /pilets/* first check the proxy shared secret (X-Proxy-Auth) and only then read your PAT. A request sent straight to the Manager's port is rejected with 401 and a JSON body explaining that the request did not come through the trusted proxy. Use the proxy-fronted hostname; the proxy validates the PAT and adds the required headers.

With a GraphQL client (here urql):

typescript
import { createClient } from "urql";

const client = createClient({
  url: "https://manager.example.com/query",
  fetchOptions: {
    headers: {
      authorization: `Bearer ${personalAccessToken}`,
    },
  },
});

Managing PATs ​

The token list shows each token's name, prefix, last-used time and expiry. To revoke one, find it in the list, click Delete and confirm; the token is invalidated immediately.

Recommendations:

  • Name tokens descriptively
  • Set expiration dates
  • Rotate tokens regularly
  • Revoke unused tokens
  • Never commit tokens to git

Machine Users ​

Creating a Machine User ​

  1. Navigate to Machine Users
  2. Click Create Machine User
  3. Enter Name
  4. Select Authentication Type:
    • Bearer Token - Token-based (recommended)
    • Basic Auth - Username/password
  5. Click Create
  6. Copy credentials - shown only once!

Bearer Token Authentication ​

Recommended. The token is a random 32-byte value; the Manager stores only its hash.

bash
curl -H "Authorization: Bearer <machine-user-token>" \
  https://manager.example.com/api/machine/check
typescript
const response = await fetch("/api/machine/register-backend", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${machineUserToken}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    callback_url: "https://backend.example.com/triggers",
    application_id: "app-uuid",
  }),
});

Basic Authentication ​

Basic auth does not work on /api/machine/* or /esb/*

MachineUserAuthMiddleware accepts Bearer tokens only. A Basic-auth request to any of those routes is rejected before the credential is looked up:

json
{ "error": "Invalid authorization format. Expected: Bearer <token>" }

Basic-auth machine users exist, but they are validated by the proxy, which forwards the header to the Manager's POST /api/validate-proxy-auth endpoint. Use them for applications sitting behind the proxy; use a Bearer token for anything talking to the Manager's own machine endpoints.

Behind the proxy, a Basic-auth machine user is used exactly like any other credential on an application route:

bash
curl -u "username:password" \
  https://myapp.example.com/some/application/route

Machine User Permissions ​

Machine users are scoped to:

  • A single environment. A machine user belongs to one environment, not to one tenant. One machine user serves every tenant its application is enabled for; the tenant travels in the request body (for comms, the tenant list, and so on) and the Manager checks it against the credential, so a caller can never reach a tenant its credential is not entitled to.
  • Applications in that environment's project. Registration and comms calls name an application, and the Manager rejects one that belongs to another project.
  • Specific ESB endpoints. ESB access is granted per endpoint on the machine user's access tab.

API Endpoints ​

Check Authentication (Machine User) ​

Verify machine user credentials:

http
GET /api/machine/check
Authorization: Bearer <token>

Response (always 200; authenticated is false with a message when the credential did not resolve):

json
{
  "authenticated": true,
  "machine_user_id": "uuid",
  "environment_id": "uuid"
}

Register Backend ​

Register for trigger execution:

http
POST /api/machine/register-backend
Authorization: Bearer <token>
Content-Type: application/json

{
  "callback_url": "https://backend.example.com/triggers",
  "version": "1.0.0",
  "application_id": "uuid"
}

Response (201 for a new registration, 200 when an existing registration for the same application and machine user was updated):

json
{
  "success": true,
  "registered_id": "uuid",
  "token": "callback-auth-token",
  "message": "Backend registered successfully",
  "environment_id": "uuid",
  "application_id": "uuid"
}

The Manager sends the returned token as Authorization: Bearer <token> on every trigger callback to your callback URL. Registering again issues a new token and invalidates the old one, so always keep the token from the latest response.

Security Best Practices ​

Token Storage ​

Store tokens in environment variables or a secrets manager (HashiCorp Vault, AWS Secrets Manager), encrypted at rest. Do not commit them to version control, hardcode them in source, or share them over unsecured channels.

Token Rotation ​

  1. Create new token
  2. Update applications to use new token
  3. Verify new token works
  4. Revoke old token

Access Control ​

  • Use least privilege principle
  • Create separate tokens per service
  • Revoke tokens when no longer needed
  • Monitor token usage in audit logs

Network Security ​

  • Use HTTPS only
  • Restrict by IP if possible
  • Implement rate limiting
  • Monitor for unusual patterns

Troubleshooting ​

401 Unauthorized ​

Reasons: an invalid, expired or revoked token; the wrong header format (Basic auth on a /api/machine/* or /esb/* route); a PAT sent straight to the Manager port instead of through the proxy.

To fix:

  1. Read the JSON error field in the response; it names the reason
  2. Verify token is correct and has not expired
  3. Ensure header format: Authorization: Bearer <token>
  4. Create new token if needed

429 Too Many Requests ​

Repeated failed authentications against /api/machine/* or /esb/* from one client trip a throttle. Wait for the number of seconds in the Retry-After header before trying again.

403 Forbidden ​

Reasons: valid authentication but insufficient permissions; a resource in another tenant; a machine user whose environment's project does not contain the application.

To fix:

  1. Verify tenant/application access
  2. Check user permissions
  3. Ensure correct resource IDs

Token Not Working After Creation ​

Check:

  1. Did you copy the entire token?
  2. Are there extra spaces or newlines?
  3. Is the header formatted correctly?
  4. Try the /api/machine/check endpoint to test

Example: Backend Service Authentication ​

typescript
// backend-service.ts
import fetch from "node-fetch";

const MANAGER_URL = process.env.MANAGER_URL;
const MACHINE_TOKEN = process.env.MACHINE_USER_TOKEN;
const APP_ID = process.env.APPLICATION_ID;

// Register with Manager
async function registerBackend() {
  const response = await fetch(`${MANAGER_URL}/api/machine/register-backend`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${MACHINE_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      callback_url: "https://my-backend.com/triggers",
      version: "1.0.0",
      application_id: APP_ID,
    }),
  });

  if (!response.ok) {
    throw new Error(`Registration failed: ${response.statusText}`);
  }

  const data = await response.json();
  console.log("Backend registered:", data.registered_id);
}

// Verify authentication
async function checkAuth() {
  const response = await fetch(`${MANAGER_URL}/api/machine/check`, {
    headers: {
      Authorization: `Bearer ${MACHINE_TOKEN}`,
    },
  });

  const data = await response.json();
  console.log("Authenticated:", data.authenticated);
}

// Start service
async function start() {
  await checkAuth();
  await registerBackend();
  console.log("Service ready");
}

start();

See Also ​