Skip to Content

Actions Engine API

Actions are custom JavaScript code snippets that execute at specific points during authentication flows. They allow you to extend Auris with custom logic without modifying the core platform: enrich tokens with external data, block suspicious signups, enforce custom password policies, or sync user data to external systems.

Actions run in a sandboxed environment with restricted scope. Each action is associated with a trigger point (e.g., post-login) and executes in order of priority. Multiple actions can be attached to the same trigger.

All actions endpoints require the x-tenant header. Creating and managing actions requires admin-level access (implied by the admin:all permission or the admin:all permission).

Action Lifecycle

  1. Create an action with a trigger type and JavaScript code.
  2. Test the action by reviewing execution logs.
  3. Set the action’s status to active to enable it in production.
  4. Monitor execution via the logs endpoint.

Action CRUD

List Actions

GET/api/actionsRequires: admin:all

List all actions for the tenant. Returns action metadata including trigger type, status, execution statistics, and ordering. Actions execute in ascending order of the order field for each trigger type.

Query parameters

ParameterTypeDescription
pageintegerPage number (default: 1)
limitintegerItems per page (default: 20)
triggerstringFilter by trigger type (e.g., post-login)
statusactive | inactiveFilter by status

Success response

{ "ok": true, "data": { "data": [ { "id": "act_abc123", "name": "Enrich Token with CRM Data", "trigger": "post-login", "status": "active", "order": 1, "timeout": 5000, "executionCount": 14523, "errorCount": 12, "lastExecutedAt": "2025-02-18T09:50:00Z", "createdAt": "2025-01-10T10:00:00Z", "updatedAt": "2025-02-15T14:00:00Z" }, { "id": "act_def456", "name": "Block Disposable Emails", "trigger": "pre-register", "status": "active", "order": 1, "timeout": 3000, "executionCount": 892, "errorCount": 0, "lastExecutedAt": "2025-02-18T08:30:00Z", "createdAt": "2025-01-20T10:00:00Z", "updatedAt": "2025-01-20T10:00:00Z" } ], "pagination": { "page": 1, "limit": 20, "total": 5, "totalPages": 1 } } }

Create Action

POST/api/actionsRequires: admin:all

Create a new action. The action is created with inactive status by default. Set it to active via the toggle endpoint after testing. The JavaScript code is validated for blocked patterns before storage.

Request body

{ "name": "Enrich Token with CRM Data", "trigger": "post-login", "code": "async function handler(context, api) {\n const response = await api.fetch('https://crm.example.com/api/user', {\n headers: { 'X-User-Email': context.user.email }\n });\n const crmData = await response.json();\n api.setCustomClaim('crm_id', crmData.id);\n api.setCustomClaim('account_tier', crmData.tier);\n}", "order": 1, "timeout": 5000 }
FieldTypeRequiredDescription
namestringYesHuman-readable name
triggerstringYesTrigger point (see Trigger Types)
codestringYesJavaScript function body
orderintegerNoExecution order within the trigger (default: 0, lower = first)
timeoutintegerNoMaximum execution time in milliseconds (default: 5000, max: 10000)

Success response

{ "ok": true, "data": { "id": "act_ghi789", "name": "Enrich Token with CRM Data", "trigger": "post-login", "status": "inactive", "code": "async function handler(context, api) { ... }", "order": 1, "timeout": 5000, "executionCount": 0, "errorCount": 0, "createdAt": "2025-02-18T10:00:00Z" } }

Error codes

CodeHTTPDescription
VALIDATION_ERROR400Missing required fields or invalid trigger type
BLOCKED_PATTERN400Code contains a blocked pattern (see Sandbox Restrictions)
CODE_TOO_LARGE400Code exceeds the maximum allowed size

Get Action

GET/api/actions/[id]Requires: admin:all

Retrieve a single action by ID, including its full code and execution statistics.

Success response

{ "ok": true, "data": { "id": "act_abc123", "name": "Enrich Token with CRM Data", "trigger": "post-login", "status": "active", "code": "async function handler(context, api) {\n const response = await api.fetch('https://crm.example.com/api/user', {\n headers: { 'X-User-Email': context.user.email }\n });\n const crmData = await response.json();\n api.setCustomClaim('crm_id', crmData.id);\n api.setCustomClaim('account_tier', crmData.tier);\n}", "order": 1, "timeout": 5000, "executionCount": 14523, "errorCount": 12, "lastExecutedAt": "2025-02-18T09:50:00Z", "createdAt": "2025-01-10T10:00:00Z", "updatedAt": "2025-02-15T14:00:00Z" } }

Update Action

PUT/api/actions/[id]Requires: admin:all

Update an action’s name, code, trigger, order, or timeout. All fields are optional — only provided fields are updated. Updated code is re-validated for blocked patterns.

Request body

{ "name": "Enrich Token with CRM Data v2", "code": "async function handler(context, api) {\n // Updated logic\n const data = await api.fetch('https://crm.example.com/v2/user/' + context.user.id);\n const user = await data.json();\n api.setCustomClaim('crm_id', user.id);\n}", "timeout": 8000 }

Success response

{ "ok": true, "data": { "id": "act_abc123", "name": "Enrich Token with CRM Data v2", "trigger": "post-login", "status": "active", "code": "async function handler(context, api) { ... }", "order": 1, "timeout": 8000, "updatedAt": "2025-02-18T11:00:00Z" } }

Delete Action

DELETE/api/actions/[id]Requires: admin:all

Delete an action permanently. The action is immediately removed from the execution pipeline. Execution logs for this action are retained.

Success response

{ "ok": true, "data": { "deleted": true } }

Toggle Action Status

PATCH/api/actions/[id]Requires: admin:all

Toggle an action between active and inactive status. Only active actions are executed during authentication flows.

Request body

{ "status": "active" }

Valid values: active, inactive.

Success response

{ "ok": true, "data": { "id": "act_abc123", "status": "active", "updatedAt": "2025-02-18T11:30:00Z" } }

Execution Logs

GET/api/actions/[id]/logsRequires: admin:all

Retrieve execution logs for a specific action. Each log entry records whether the execution succeeded or failed, the duration, and any error messages. Logs are ordered by timestamp descending.

Query parameters

ParameterTypeDescription
pageintegerPage number (default: 1)
limitintegerItems per page (default: 20)

Success response

{ "ok": true, "data": { "data": [ { "id": "log_abc123", "actionId": "act_abc123", "trigger": "post-login", "status": "success", "duration": 234, "userId": "usr_xyz789", "createdAt": "2025-02-18T09:50:00Z" }, { "id": "log_def456", "actionId": "act_abc123", "trigger": "post-login", "status": "error", "duration": 5001, "error": "Action timed out after 5000ms", "userId": "usr_abc123", "createdAt": "2025-02-18T09:48:00Z" }, { "id": "log_ghi789", "actionId": "act_abc123", "trigger": "post-login", "status": "error", "duration": 112, "error": "TypeError: Cannot read property 'email' of undefined", "userId": "usr_def456", "createdAt": "2025-02-18T09:45:00Z" } ], "pagination": { "page": 1, "limit": 20, "total": 14535, "totalPages": 727 } } }

Log status values: success, error.

Trigger Types

Actions can be attached to one of six trigger points in the authentication pipeline:

TriggerFires WhenUse Cases
pre-loginBefore Keycloak authentication is attemptedBlock login from specific IPs or email domains, custom rate limiting
post-loginAfter successful authentication, before tokens are issuedEnrich tokens with external data, log custom analytics, sync to CRM
pre-registerBefore a new user account is createdBlock disposable emails, enforce custom validation, check external blocklists
post-registerAfter a new user account is createdSend welcome notification, create records in external systems, assign default roles
post-change-passwordAfter a user changes their passwordInvalidate cached credentials, notify external systems, audit log
pre-tokenBefore an M2M token is issuedValidate client scopes, add custom claims, enforce time-of-day restrictions

Execution Order

When multiple active actions share the same trigger, they execute sequentially in ascending order value. If an action fails (throws an error or times out), subsequent actions for that trigger still execute unless the failing action explicitly denies the request.

ActionContext Object

Every action receives a context object as its first argument. The shape varies by trigger type.

pre-login / post-login

{ user: { id: "usr_abc123", email: "[email protected]", username: "alice", firstName: "Alice", lastName: "Smith", roles: ["editor", "viewer"], emailVerified: true, phoneNumber: "+39021234567", phoneNumberVerified: true, metadata: {} }, connection: { method: "password", // "password" | "magic_link" | "social" | "sso" provider: null, // social provider name (e.g., "google") or null ipAddress: "203.0.113.50", userAgent: "Mozilla/5.0 ...", timestamp: "2025-02-18T10:00:00Z" }, tenant: "acme-corp" }

For pre-login, the user object may be null if the user has not been resolved yet (e.g., wrong email). The connection.method and connection.ipAddress are always available.

pre-register / post-register

{ user: { email: "[email protected]", username: "newuser", firstName: "New", lastName: "User" }, connection: { method: "password", ipAddress: "203.0.113.50", userAgent: "Mozilla/5.0 ...", timestamp: "2025-02-18T10:00:00Z" }, tenant: "acme-corp" }

For post-register, the user object also includes id and roles.

post-change-password

{ user: { id: "usr_abc123", email: "[email protected]" }, tenant: "acme-corp" }

pre-token

{ application: { id: "app_xyz789", name: "Backend Service", clientId: "m2m-client-id", type: "M2M" }, requestedScopes: ["read:users", "manage:roles"], tenant: "acme-corp" }

ActionResult (API Object)

The second argument passed to actions is the api object, which provides methods to influence the authentication flow:

MethodAvailable InDescription
api.setCustomClaim(key, value)post-login, pre-tokenAdd a custom claim to the access token
api.setMetadata(key, value)post-login, post-registerSet user metadata (persisted in the database)
api.deny(reason)pre-login, pre-register, pre-tokenDeny the authentication attempt with a reason
api.log(message)All triggersWrite a message to the action’s execution log
api.fetch(url, options)All triggersMake an HTTP request (restricted fetch with timeout)

Example: Deny Signup for Disposable Emails

async function handler(context, api) { const disposableDomains = ['tempmail.com', 'throwaway.email', 'guerrillamail.com']; const domain = context.user.email.split('@')[1]; if (disposableDomains.includes(domain)) { api.deny('Disposable email addresses are not allowed'); return; } api.log('Signup allowed for domain: ' + domain); }

Example: Enrich Token After Login

async function handler(context, api) { try { const response = await api.fetch('https://crm.example.com/api/lookup', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer crm-api-key' }, body: JSON.stringify({ email: context.user.email }) }); if (response.ok) { const data = await response.json(); api.setCustomClaim('crm_id', data.customerId); api.setCustomClaim('plan', data.subscriptionPlan); api.log('Enriched token: plan=' + data.subscriptionPlan); } else { api.log('CRM lookup failed: ' + response.status); } } catch (error) { api.log('CRM lookup error: ' + error.message); // Do not deny login if enrichment fails } }

Example: Block M2M Token Outside Business Hours

async function handler(context, api) { var hour = new Date().getUTCHours(); if (hour < 6 || hour > 22) { api.deny('M2M tokens cannot be issued outside business hours (06:00-22:00 UTC)'); return; } api.log('M2M token issued for ' + context.application.name + ' at UTC hour ' + hour); }

Sandbox Restrictions

Actions execute in a sandboxed environment with a restricted scope. The following patterns are detected and blocked at code validation time (during create and update). Code containing any of these patterns is rejected with a BLOCKED_PATTERN error:

  • require( — no CommonJS module imports
  • import — no ES module imports
  • process. — no access to Node.js process object
  • child_process — no shell execution
  • fs. / fs/promises — no filesystem access
  • global. / globalThis. — no access to global scope
  • Dynamic code evaluation patterns — no runtime code generation from strings

The api.fetch() method is provided as a safe alternative to external HTTP libraries. It supports GET, POST, PUT, PATCH, and DELETE methods with JSON or text bodies. The timeout is inherited from the action’s timeout setting.

Runtime Limits

LimitValue
Maximum execution timeConfigurable per action (default 5000ms, max 10000ms)
Maximum code size64 KB
Maximum api.fetch() response size1 MB
Available globalsJSON, Date, Math, String, Number, Array, Object, Map, Set, Promise, RegExp, console.log (redirected to api.log)

Error Handling

When an action throws an unhandled error or times out:

  1. The error is logged to the action’s execution log.
  2. The errorCount on the action is incremented.
  3. The authentication flow continues (actions do not block auth by default unless api.deny() is called).
  4. If the action is critical, use api.deny() explicitly in your error handler.

Action errors do not block authentication by default. If you need a failing action to prevent login (e.g., a compliance check), you must call api.deny() explicitly in your catch block. Otherwise, the user will be authenticated even if the action fails.

Blueprint Visual Editor

The Auris Console includes a visual node editor (Blueprint Editor) for creating actions using a drag-and-drop interface instead of writing JavaScript. The Blueprint Editor generates JSON rule definitions that are compiled to equivalent JavaScript at runtime.

The Blueprint Editor is an alternative to the code editor — both produce the same result. Actions created with the Blueprint Editor can be viewed and edited as code, and vice versa.

See the Console documentation for details on the Blueprint Editor.