Download all docs
modifiers

Auth Policy

A near-universal access-control modifier you attach to the 47 element types the runtime's unified attach graph considers compatible (app, automation, board, circle, claude-code, codex, csharp, diagram, discord, document, email, external-agent, facebook, files, fortnox, github, go-fn, graph, hitl, http, instagram, javascript, lab, linkedin, matrix, mattermost, open-code, phone-number, planning-board, python, recruitment-board, rocketchat, ruby, rust-fn, sales-board, slack, spa, sql, ssr, teams, three-d, timeseries, triformer, twitter, vector, view, websocket) so one place declares who may call them — accepting the chosen authentication methods and the authorization rules, and rejecting a request with a 401 or 403 before it ever reaches your code. (On io elements it attaches mainly for cascade hygiene; the live 401/403 gate is on the actor, frontend, and API surfaces.)

Working with it

Selecting a Auth Policy reveals its settings in the properties panel; it has no dedicated full-screen workbench.

How it appears

The same element type rendered as a definition, a circle instance, and a live workspace card.

Ay
type

Auth Policy

Define authentication requirements and access rules

modifiersmodifierdefinition

When to use / not

When to use

  • Protecting a public-facing endpoint, app, or action so callers must present valid credentials before the request runs.
  • Centralising one set of auth rules and reusing it across many elements, instead of re-declaring authentication on each one.
  • Enforcing role- or scope-based access — denying authenticated callers who lack the required role or permission.
  • Adding a restrictive auth requirement to a scope where the strictest policy should win over anything inherited.

When not to use

  • Minting the bearer tokens or keys that callers authenticate *with* — that is the api-token element; auth-policy only checks credentials, it does not issue them.
  • Brokering a third-party login / OAuth grant flow to obtain access tokens — reach for the oauth modifier; auth-policy validates the credential a request already carries.
  • Tenant isolation itself — circles are the airtight boundary; auth-policy guards entry to elements inside a circle, it is not the boundary.

Topology

Attaches to another element as a modifier, shaping that element's behaviour rather than running on its own.

Properties

authenticationobject
Authentication configuration
scopeobject
Policy scope
sessionobject
Session configuration
rulesarray
Top-level authorization rules (allow/deny per action+resource). Same as authorization.rules.
groupsarray
Required groups for access. Same as authorization.groups.

Capabilities

Defined for this element
  • Auth
  • Evaluate
  • Observe

Operations

  • attachPOST
  • deleteDELETE
  • detachPOST
  • disablePOST
  • enablePOST
  • evaluatePOST
  • getGET
  • get_attached_modifiersGET
  • intentionGET
  • list_attachmentsGET
  • readme_updatePOST
  • schemaGET
  • updatePATCH

Ports

Inputs

  • require_authconfig
  • methodsconfig
  • rolesconfig
  • permissionsconfig
  • scopesconfig

Errors / when it fails

Auth policy should define roles and/or permissions
Fails unless: len(roles) > 0 or len(permissions) > 0

Validation rules

  • Policy allows unauthenticated access - ensure this is intentional
  • Policy grants admin role - ensure proper authorization checks

Auth Policy (auth-policy)

Category: modifiers | Form: | Symbol: Ay

Define authentication requirements and access rules

Defines authentication and authorization requirements for attached elements. Cascade strategy: restrictive — when inherited and local policies conflict, the stricter policy wins. Evaluation order 50 (early in the middleware chain, after CORS at 5). Attaches to the near-universal set of action, agent, app, automation, board, frontend, integration, and integration-connector element types whose target contracts opt in (47 element types in the runtime’s unified attach graph: app, automation, board, circle, claude-code, codex, csharp, diagram, discord, document, email, external-agent, facebook, files, fortnox, github, go-fn, graph, hitl, http, instagram, javascript, lab, linkedin, matrix, mattermost, open-code, phone-number, planning-board, python, recruitment-board, rocketchat, ruby, rust-fn, sales-board, slack, spa, sql, ssr, teams, three-d, timeseries, triformer, twitter, vector, view, websocket). Fails with HTTP 401 when auth is required but not provided, or 403 when credentials are present but insufficient. Spec supports authentication.methods array (first entry is the required method, default “bearer”) and authorization.type (default “rbac”). The evaluate operation checks credentials against the policy and returns allowed/denied. Use auth-policy for access control; use api-token for creating bearer tokens to authenticate with. Common mistake: not attaching auth-policy to public-facing elements. For io elements (inbound HTTP, etc.), the policy attaches for cascade hygiene; credential validation runs through the io element’s native spec.auth configuration (bearer/api_key/ hmac/basic). The full machine-token authorization mode is tracked in IMPROVE-381.

Guide

Overview

An Auth Policy defines authentication methods and rules for API endpoints. Auth policies are referenced by REST receivers to protect endpoints with JWT, API keys, OAuth, or other authentication methods.

Why Auth Policies Exist

  • Centralized Auth - Define auth rules once, use across endpoints
  • Multiple Methods - Support JWT, API keys, OAuth in one policy
  • Consistent Security - Same auth behavior across your API
  • Easy Updates - Change auth rules without modifying each endpoint

Directory Structure

jwt-auth/
├── README.md              # Documentation
├── .triform/
│   ├── triform.yaml       # kind: control/auth-policy
│   ├── contract.yaml      # Capabilities
│   └── spec.yaml          # Configuration
└── examples/
    └── multi-method.yaml

Creating an Auth Policy

$ triform create control/auth-policy jwt-auth

Configuration

Basic JWT Example

kind: control/auth-policy
slug: jwt-auth

spec:
  methods: [jwt]

  jwt:
    header: "Authorization"
    scheme: "Bearer"
    issuer: "https://auth.example.com"
    audience: "api.example.com"
    jwks_uri: "https://auth.example.com/.well-known/jwks.json"

Basic API Key Example

kind: control/auth-policy
slug: api-key-auth

spec:
  methods: [api_key]

  api_key:
    header: "X-API-Key"
    # Or in query string:
    # query_param: "api_key"

Full Example (Multiple Methods)

kind: control/auth-policy
slug: multi-auth

spec:
  # Accept any of these methods
  methods: [jwt, api_key]

  # JWT configuration
  jwt:
    header: "Authorization"
    scheme: "Bearer"
    issuer: "https://auth.example.com"
    audience: "api.example.com"
    jwks_uri: "https://auth.example.com/.well-known/jwks.json"
    algorithms: [RS256, RS384, RS512]

    # Claims to extract
    claims:
      user_id: "sub"
      email: "email"
      roles: "roles"

  # API Key configuration
  api_key:
    header: "X-API-Key"
    # Keys validated against tenant's registered API keys

  # What to expose to the target action
  context:
    include:
      - user_id
      - email
      - roles
      - auth_method  # Which method was used

Authentication Methods

JWT (JSON Web Token)

spec:
  methods: [jwt]

  jwt:
    # Where to find the token
    header: "Authorization"
    scheme: "Bearer"

    # Validation
    issuer: "https://auth.example.com"
    audience: "api.example.com"
    algorithms: [RS256]

    # JWKS for key rotation
    jwks_uri: "https://auth.example.com/.well-known/jwks.json"
    jwks_cache_ttl: 3600  # Cache keys for 1 hour

    # Or static secret (for HS256)
    # secret: "{{ secrets.JWT_SECRET }}"

    # Clock skew tolerance
    clock_skew_seconds: 60

    # Claims mapping
    claims:
      user_id: "sub"
      email: "email"
      name: "name"
      roles: "https://example.com/roles"  # Custom claim

API Key

spec:
  methods: [api_key]

  api_key:
    # From header
    header: "X-API-Key"

    # Or from query string
    # query_param: "api_key"

    # Or from cookie
    # cookie: "api_key"

    # Optional: require specific prefix
    prefix: "sk_"

    # Keys are validated against tenant's registered API keys
    # Each key has associated metadata (user_id, scopes, etc.)

OAuth 2.0

spec:
  methods: [oauth]

  oauth:
    header: "Authorization"
    scheme: "Bearer"

    # Introspection endpoint
    introspection_url: "https://auth.example.com/oauth/introspect"
    client_id: "{{ secrets.OAUTH_CLIENT_ID }}"
    client_secret: "{{ secrets.OAUTH_CLIENT_SECRET }}"

    # Or use JWKS for self-contained tokens
    jwks_uri: "https://auth.example.com/.well-known/jwks.json"

    # Required scopes
    required_scopes:
      - "read:users"
      - "write:users"

Basic Auth

spec:
  methods: [basic]

  basic:
    # Validate against internal user database
    realm: "API Access"

Custom Auth

spec:
  methods: [custom]

  custom:
    # Custom validation action
    validator: "@project/custom-auth-validator"

    # Headers to pass to validator
    headers:
      - "X-Custom-Token"
      - "X-Signature"

Multiple Methods

Accept any of multiple auth methods:

spec:
  methods: [jwt, api_key, oauth]

  # If any method succeeds, request is authenticated
  # The auth context includes which method was used

  jwt:
    # JWT config...

  api_key:
    # API key config...

  oauth:
    # OAuth config...

Auth Context

After authentication, context is available to the target:

# In REST receiver
target:
  ref: "@project/my-function"
  input:
    # Auth context populated by auth policy
    user_id: "{{ auth.user_id }}"
    email: "{{ auth.email }}"
    roles: "{{ auth.roles }}"
    auth_method: "{{ auth.method }}"  # "jwt", "api_key", etc.
    api_key_id: "{{ auth.api_key_id }}"  # If API key used

Usage in REST Receivers

Reference an auth policy:

kind: rest
slug: users-api

spec:
  path: /api/v1/users
  methods: [GET, POST]

  auth: "@project/jwt-auth"

  target:
    ref: "@project/users-crud"
    input:
      user_id: "{{ auth.user_id }}"

Optional Authentication

kind: rest
slug: products-api

spec:
  path: /api/v1/products
  methods: [GET]

  auth:
    ref: "@project/jwt-auth"
    required: false  # Allow anonymous access

  target:
    ref: "@project/list-products"
    input:
      # null if not authenticated
      user_id: "{{ auth.user_id }}"

Common Patterns

Internal Service Auth

kind: control/auth-policy
slug: service-auth

spec:
  methods: [api_key]

  api_key:
    header: "X-Service-Key"
    prefix: "svc_"

  # Only allow internal service keys
  restrictions:
    key_types: [service]

User + Service Auth

kind: control/auth-policy
slug: user-or-service-auth

spec:
  methods: [jwt, api_key]

  jwt:
    # For user authentication
    issuer: "https://auth.example.com"
    audience: "api.example.com"
    jwks_uri: "https://auth.example.com/.well-known/jwks.json"

  api_key:
    # For service authentication
    header: "X-API-Key"

Role-Based Access

kind: control/auth-policy
slug: admin-auth

spec:
  methods: [jwt]

  jwt:
    issuer: "https://auth.example.com"
    jwks_uri: "https://auth.example.com/.well-known/jwks.json"

    claims:
      roles: "roles"

  # Require admin role
  require:
    roles:
      any_of: [admin, super_admin]

Scope-Based Access

kind: control/auth-policy
slug: scoped-auth

spec:
  methods: [oauth]

  oauth:
    jwks_uri: "https://auth.example.com/.well-known/jwks.json"

    # Require specific scopes
    required_scopes:
      - "users:read"
      - "users:write"

Error Responses

Auth failures return standard errors:

StatusErrorDescription
401missing_tokenNo auth token provided
401invalid_tokenToken malformed or expired
401invalid_signatureToken signature invalid
401invalid_api_keyAPI key not found or revoked
403insufficient_scopeMissing required scopes
403insufficient_roleMissing required roles

Monitoring

Metrics

MetricDescription
triform_auth_attempts_total{policy,method,result}Auth attempts
triform_auth_failures_total{policy,method,reason}Auth failures
triform_auth_latency_ms{policy,method}Auth check latency

CLI Commands

# Test auth policy
triform auth test jwt-auth --token="eyJhbG..."

# View recent auth failures
triform auth failures jwt-auth --limit=50

# Validate API key
triform auth validate-key jwt-auth --key="sk_..."

Runtime Behavior

PropertyValue
Cascaderestrictive — strictest auth requirement wins across scopes
Eval Order50
Phaserequest
Fail ActionHTTP 401 (missing/invalid token) or HTTP 403 (insufficient scope/role)
Applies To47 element types (actions, agents, app, automation, board, frontend, integration, integration-connector whose contracts opt in)

Files in This Resource

  • README.md - Documentation
  • .triform/triform.yaml - Metadata
  • .triform/contract.yaml - Capabilities
  • .triform/spec.yaml - Configuration

Error Recovery

ErrorRecovery guidanceNext actions
validationAuth-policy accepts two schema shapes: nested (authorization.rules) and a top-level rules alias. Pick one, don’t mix.

Capabilities

  • require-auth: Enforce authentication
  • roles: Role-based access
  • permissions: Permission-based access
  • scopes: OAuth scope requirements

Properties

PropertyTypeDefaultDescription
authenticationobjectAuthentication configuration
authorizationobjectAuthorization config. Two DISTINCT rule shapes live here — do NOT mix their fields. RBAC rules[] take {effect, action, resource, role} (e.g. {effect: ‘allow’, action: ‘read’, resource: ‘/api/docs/*’, role: ‘editor’}). ABAC conditions[] take {attribute, operator, value} (e.g. {attribute: ‘department’, operator: ‘eq’, value: ‘finance’}). Putting ABAC attribute/operator/value keys inside a rules[] entry is the #1 first-attempt 422 (a rule’s allowed properties are only action/effect/resource/role). Set type (rbac | abac | acl) to signal intent.
scopeobjectPolicy scope
sessionobjectSession configuration
rulesarrayTop-level authorization rules (allow/deny per action+resource). Same as authorization.rules.
groupsarrayRequired groups for access. Same as authorization.groups.

Operations

attach

Post /ops/attach | Auth: Read

Attach this modifier to a target element

Attaches this modifier to a target element. The target_id must be a UUID of an existing element that supports this modifier type (check applies_to in definition.yaml). Priority controls evaluation order when multiple modifiers of the same type are attached — lower priority runs first. The attachment is stored in element_modifiers table. Cascade resolution runs at bond-time to merge this modifier into the target’s resolved config. Common mistake: attaching to an incompatible element type — check topology rules first.

delete

Delete /ops/delete | Auth: Admin

Delete element (soft delete)

Soft delete — sets state to ‘deleted’ but retains the record. Cannot delete elements that have children (has_no_bond precondition) or active runs. Requires admin auth and confirmation.

detach

Post /ops/detach | Auth: Read

Detach this modifier from a target element

Removes this modifier from a target element. Requires the target_id. Pervasive modifiers (audit, policy) can only be detached at the level they were originally attached — inherited pervasive modifiers cannot be detached by child elements. After detach, cascade resolution re-runs to remove this modifier’s effect from the resolved config.

disable

Post /ops/disable | Auth: Admin

Disable element (hides and prevents use)

Idempotent — safe to call on already-disabled elements. Optionally pass a reason string. Disabled elements cannot be invoked or executed. Inverse of enable.

enable

Post /ops/enable | Auth: Admin

Enable element (makes usable and visible)

Idempotent — safe to call on already-enabled elements. Transitions element to ready/enabled state. Cannot enable deleted elements. Inverse of disable.

evaluate

Post /ops/evaluate | Auth: Read

Evaluate authentication policy

Tests credentials against this policy. Accepts flat fields (role, action, token), nested objects (subject.role, request.action), or context paths (context.role, context.action). Reads spec.authentication.methods[0] (default “bearer”) and spec.authorization.type (default “rbac”). Returns allowed (bool), denied_reasons, and policy details. Configure spec.authorization.roles and spec.authorization.permissions to enforce access control.

get

Get /ops/get | Auth: Read

Get element details

Element is already resolved by the routing layer — this returns the cached element, not a fresh DB query. Use the path /api/{circle}/{slug} to address elements.

get_attached_modifiers

Get /ops/attached/{target_id} | Auth: Read

Get all modifiers attached to a target element

Lists all modifiers attached to a specific target element, including modifier_id, type, subcategory, and priority. Useful for debugging cascade resolution or understanding which policies apply to an element before invoking it.

intention

Get /ops/intention | Auth: Read

Get element intention with full inheritance chain

Returns three levels: direct (this element’s intention), inherited (from category and root), and resolved (final merged intention). Useful for understanding an element’s purpose in context of its hierarchy.

list_attachments

Get /ops/targets | Auth: Read

List all elements this modifier is attached to

Returns all target elements where this modifier is currently applied. Shows target_id, target_type, priority, and cascade_policy.

readme_update

Post /ops/readme_update | Auth: Write

Update element README.md content

Creates or overwrites README.md in the element’s git repo. Commits to the draft branch. Content must be provided as a markdown string.

schema

Get /ops/schema | Auth: Read

Get element input/output schema (MCP tools/list compatible)

Returns type-level port schemas from the TypeRegistry — not instance-specific overrides. Includes direction (input/output), required flag, and JSON schema per port. Useful for understanding what data an element accepts and produces.

update

Patch /ops/update | Auth: Write

Update element

Partial update — send only the fields you want to change. spec, name, and intention are all independently optional. spec MUST be a JSON object when present; deep-merged into the existing spec by default. Empty {"spec":{}} preserves existing spec content but still records a new version (no-op for content, not for version state). To clear/replace the entire spec wholesale send {"spec":{...},"deep":false}. List-typed spec fields use replace semantics (the patch list replaces the existing list, no array merging). Coordinates Git + DB writes. Slug cannot be changed after creation.

Error Codes

CodeClassRetryableDescription
AUTH_REQUIREDauthnoAuthentication required
AUTH_INVALID_TOKENauthnoInvalid or expired token
AUTH_INSUFFICIENT_PERMISSIONSauthnoMissing required permissions
AUTH_INSUFFICIENT_ROLESauthnoMissing required roles

Lifecycle / runtime

Defined for this element

Execution model: sync

Observability

Defined for this element

Metrics

  • evaluation_count
  • rejection_count

Events

  • auth-policy.evaluated
  • auth-policy.rejected

Pricing / cost

Platform default

Operation costs

  • create: free
  • update: free
  • delete: free
  • get: free
  • list: free
  • invoke: 10000 micro-AU
  • tool_use: free

Set it up

Require Authenticationstring
Auth Methodsstring