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.
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.methodsarray (first entry is the required method, default “bearer”) andauthorization.type(default “rbac”). Theevaluateoperation 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 nativespec.authconfiguration (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:
| Status | Error | Description |
|---|---|---|
| 401 | missing_token | No auth token provided |
| 401 | invalid_token | Token malformed or expired |
| 401 | invalid_signature | Token signature invalid |
| 401 | invalid_api_key | API key not found or revoked |
| 403 | insufficient_scope | Missing required scopes |
| 403 | insufficient_role | Missing required roles |
Monitoring
Metrics
| Metric | Description |
|---|---|
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
| Property | Value |
|---|---|
| Cascade | restrictive — strictest auth requirement wins across scopes |
| Eval Order | 50 |
| Phase | request |
| Fail Action | HTTP 401 (missing/invalid token) or HTTP 403 (insufficient scope/role) |
| Applies To | 47 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
| Error | Recovery guidance | Next actions |
|---|---|---|
validation | Auth-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
| Property | Type | Default | Description |
|---|---|---|---|
authentication | object | — | Authentication configuration |
authorization | object | — | Authorization 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. |
scope | object | — | Policy scope |
session | object | — | Session configuration |
rules | array | — | Top-level authorization rules (allow/deny per action+resource). Same as authorization.rules. |
groups | array | — | Required 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, andintentionare all independently optional.specMUST 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
| Code | Class | Retryable | Description |
|---|---|---|---|
AUTH_REQUIRED | auth | no | Authentication required |
AUTH_INVALID_TOKEN | auth | no | Invalid or expired token |
AUTH_INSUFFICIENT_PERMISSIONS | auth | no | Missing required permissions |
AUTH_INSUFFICIENT_ROLES | auth | no | Missing 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