Capability authorization
Authentication establishes a trusted Principal. Authorization decides whether
that principal may execute one capability for its validated domain input.
Authorization belongs in the capability access rule, so direct, CLI, MCP stdio,
and MCP HTTP calls enforce the same decision before run.
Integrate a policy decision point
Section titled “Integrate a policy decision point”-
Define a provider-neutral engine port
interface AuthorizationDecisionPoint {isAllowed(request: {readonly subject: {readonly id: string;readonly attributes?:Readonly<Record<string, unknown>>;};readonly action: string;readonly resource: {readonly type: "support-ticket";readonly id: string;};},options: { readonly signal: AbortSignal },): Promise<boolean>;}This interface belongs to the custom engine. Its implementation may call any policy service or apply local domain rules.
-
Build a fail-closed access rule
import type { AccessRule } from "@invokta/core";function canClassifyTicket(pdp: AuthorizationDecisionPoint,): AccessRule<{ ticketId: string }> {return async ({principal,input,context,capabilityId,}) => {if (principal === null) return false;try {const allowed = await pdp.isAllowed({subject: {id: principal.id,...(principal.attributes === undefined? {}: { attributes: principal.attributes }),},action: capabilityId,resource: {type: "support-ticket",id: input.ticketId,},},{ signal: context.signal },);return allowed === true;} catch {context.logger.warn("Authorization decision was unavailable.",{requestId: context.requestId,capabilityId,},);return false;}};}Return
trueonly for an explicit allow decision. Convert an unavailable, timed-out, or malformed policy response into denial when the domain is fail-closed. -
Inject the access rule through the capability factory
export function createClassifyTicket(pdp: AuthorizationDecisionPoint,) {return defineCapability({description: "Classify a support ticket.",input: z.object({ ticketId: z.string().min(1) }),output: z.object({ category: z.string() }),access: canClassifyTicket(pdp),async run({ input }) {return {category: classifyByDomainRules(input.ticketId),};},});}Wire the PDP implementation at the composition root. No framework container, policy registry, or service locator is needed.
Denial and failure behavior
Section titled “Denial and failure behavior”| Rule outcome | Principal | Engine result | Calls run |
|---|---|---|---|
true |
Present or absent, according to domain policy | Continue | Yes |
false |
null |
UNAUTHENTICATED |
No |
false |
Present | FORBIDDEN |
No |
| Throws | Any | EXECUTION_FAILED |
No |
An explicit denial communicates policy. A thrown access rule is still safe in that execution does not continue, but it becomes an infrastructure failure rather than a deliberate authorization result.
The capability timeout starts after access enforcement. Configure a finite PDP
client timeout and propagate context.signal; do not assume the capability’s
timeoutMs bounds authorization I/O.
Shape identity for the domain
Section titled “Shape identity for the domain”Principal standardizes only a non-empty id and optional record attributes.
Map verified roles, scopes, tenant IDs, groups, or relationships into the
minimal values the domain rule needs.
Capability input cannot choose or replace identity. A field named actor,
role, or principal remains ordinary caller-controlled data.
Input and identity passed to access are request-owned snapshots. run
receives independent snapshots, so caller or policy mutations cannot change
what execution observes.
Test the boundary
Section titled “Test the boundary”Create the capability with a fake PDP, build the engine, and invoke it directly. Cover these cases:
- validated input and the trusted principal reach the PDP;
- explicit allow calls
runonce; - deny and absent identity never call
run; - PDP timeout, failure, and malformed output fail closed; and
- errors are
UNAUTHENTICATED,FORBIDDEN, or a sanitized failure as intended.
Adapter tests then prove only that the boundary supplies the expected principal
and calls engine.invoke; they do not duplicate domain policy.
For a local attribute implementation, use the domain authorization recipe. For HTTP identity, start with HTTP authentication.