libs/skills/catalog/frontmcp-authorities/SKILL.md
Use when implementing authorization, access control, RBAC, ABAC, or ReBAC for tools, resources, prompts, or skills. Covers JWT claims mapping, authority profiles, and policy enforcement.
npx skillsauth add agentfront/frontmcp frontmcp-authoritiesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Built-in RBAC/ABAC/ReBAC authorization system for FrontMCP entry types. Each flow has native checkEntryAuthorities and filterByAuthorities stages that enforce access control policies declared via the authorities field on entry decorators. Configured via @FrontMcp({ authorities: { claimsMapping, profiles, scopeMapping } }) — no plugin needed. Flow stages handle enforcement, and developers can hook into them with Will, Did, and Around decorators. Supports named profiles for reuse, JWT claims mapping for any identity provider, inline policies with roles/permissions/attributes/relationships, and composable combinators (allOf, anyOf, not).
@Skill({ authorities }))frontmcp-config / configure-auth)mode: 'public')configure-auth-modes)Decision: Use this skill whenever you need to control who can access which entries based on roles, permissions, attributes, or relationships.
Before writing any authorities configuration, the coding agent MUST ask the developer:
"What identity provider (IdP) are you using, and what does your JWT payload look like? I need to know where roles, permissions, and tenant ID are located in the claims."
Why this matters: Every IdP places roles and permissions in different JWT claim paths. Auth0 uses namespaced URIs (https://myapp.com/roles), Keycloak nests them under realm_access.roles, Okta uses groups, Cognito uses cognito:groups, and Frontegg uses flat roles/permissions. Writing claimsMapping without knowing the actual token shape will produce silent authorization failures where every user is denied.
What to collect before proceeding:
realm_access.roles)permissions or scope)org_id, tenantId)See references/claims-mapping.md for IdP-specific claim paths.
@frontmcp/sdk)@frontmcp/auth available (peer dependency of SDK, provides all authorities types)frontmcp-config / configure-auth-modes) so that authInfo is populated on incoming requestsAdd the authorities field to your @FrontMcp decorator. No plugin import needed — authorities is a built-in framework feature.
import { FrontMcp } from '@frontmcp/sdk';
@FrontMcp({
info: { name: 'my-server', version: '1.0.0' },
authorities: {
// configured in next steps
},
})
export class MyServer {}
Set claimsMapping to tell the engine where roles, permissions, and user/tenant identifiers live in your IdP's JWT. Each value is a dot-path into the decoded JWT claims object.
// Keycloak example — in @FrontMcp({ authorities: { ... } })
authorities: {
claimsMapping: {
roles: 'realm_access.roles',
permissions: 'resource_access.my-client.roles',
tenantId: 'org_id',
userId: 'sub',
},
}
// Auth0 example
authorities: {
claimsMapping: {
roles: 'https://myapp.com/roles',
permissions: 'permissions',
tenantId: 'org_id',
},
}
If no claimsMapping is provided, the engine falls back to (in order):
authInfo.user.roles → authInfo.extra.authorization.scopes → []authInfo.user.permissions → []The OAuth-scopes-as-roles fallback means a token with no roles claim but with scope: "admin read" will be treated as having roles: ['admin', 'read']. Configure explicit claimsMapping.roles to opt out. For non-standard token shapes, use claimsResolver instead (see Common Patterns below).
Profiles let you define reusable authorization policies and reference them by name in decorators. Register them in the profiles field.
// In @FrontMcp({ authorities: { ... } })
authorities: {
claimsMapping: { roles: 'realm_access.roles', permissions: 'scope' },
profiles: {
admin: {
roles: { any: ['admin', 'superadmin'] },
},
authenticated: {
attributes: {
conditions: [{ path: 'user.sub', op: 'exists', value: true }],
},
},
matchTenant: {
attributes: {
conditions: [
{ path: 'claims.org_id', op: 'eq', value: { fromInput: 'tenantId' } },
],
},
},
editor: {
permissions: { any: ['content:write', 'content:publish'] },
},
},
}
For type-safe profile names, augment the global interface:
declare global {
interface FrontMcpAuthorityProfiles {
admin: true;
authenticated: true;
matchTenant: true;
editor: true;
}
}
Use the authorities field on any entry decorator (@Tool, @Resource, @Prompt, @Skill). Three forms are supported:
String (profile reference):
@Tool({ name: 'delete_user', authorities: 'admin' })
export default class DeleteUserTool extends ToolContext { ... }
String array (multiple profiles, AND semantics):
@Tool({ name: 'update_tenant_settings', authorities: ['authenticated', 'matchTenant'] })
export default class UpdateTenantSettingsTool extends ToolContext { ... }
Inline policy object:
@Tool({
name: 'publish_content',
authorities: {
roles: { any: ['editor', 'admin'] },
permissions: { all: ['content:publish'] },
},
})
export default class PublishContentTool extends ToolContext { ... }
Combinators for complex policies:
@Tool({
name: 'sensitive_action',
authorities: {
allOf: [
{ roles: { any: ['admin'] } },
{ attributes: { conditions: [{ path: 'env.NODE_ENV', op: 'eq', value: 'production' }] } },
],
},
})
export default class SensitiveActionTool extends ToolContext { ... }
Async guards (one-shot DB/Redis/API checks):
For dynamic, async authorization that does not warrant a reusable custom evaluator, use the
guards field. Each guard receives the same AuthoritiesEvaluationContext and returns
true on grant, or false/a denial string on deny. Guards run in sequence and combine with
other policy fields via operator (default AND).
import type { AuthorityGuardFn } from '@frontmcp/auth';
const requireActiveSubscription: AuthorityGuardFn = async (ctx) => {
const active = await db.isSubscriptionActive(ctx.user.sub);
return active ? true : 'subscription is not active';
};
@Tool({
name: 'premium_feature',
authorities: {
roles: { any: ['user'] },
guards: [requireActiveSubscription],
},
})
export default class PremiumFeatureTool extends ToolContext { ... }
Use guards for one-off async checks; promote to a registered custom evaluator when the
same logic is reused across many entries (see references/custom-evaluators.md).
scopeMapping)If your transport layer issues OAuth scope challenges (RFC 6750 insufficient_scope),
declare a scopeMapping so authority denials are converted into the right WWW-Authenticate
challenge with the required scopes. Mapping is explicit only — no automatic
permission-to-scope inference.
authorities: {
scopeMapping: {
roles: { admin: ['admin:all'] },
permissions: { 'repo:write': ['repo'] },
profiles: { admin: ['admin:all'] },
},
}
When a request is denied because the user lacks role admin, the response surfaces
the admin:all scope as the required challenge.
pipes)pipes are functions that run during auth context construction and merge their output into
FrontMcpAuthContext. Use them to extract custom typed fields from JWT claims so they are
available to your tools as strongly typed accessors. Declare the resulting fields by
augmenting ExtendFrontMcpAuthContext:
A pipe receives the raw JWT claims (a Readonly<Record<string, unknown>>) and
returns a Partial<ExtendFrontMcpAuthContext> (sync or async). It does not
receive the AuthInfo envelope — there is no .user accessor on the input.
// Pipe signature (defined inline; do not import — AuthContextPipe is not
// re-exported from `@frontmcp/auth`):
// (claims: Readonly<Record<string, unknown>>) =>
// Partial<ExtendFrontMcpAuthContext> | Promise<Partial<ExtendFrontMcpAuthContext>>
const tenantPipe = (claims: Readonly<Record<string, unknown>>) => ({
tenantId: claims['tenantId'] as string | undefined,
});
declare global {
interface ExtendFrontMcpAuthContext {
tenantId?: string;
}
}
// In @FrontMcp({ authorities: { ... } })
authorities: {
pipes: [tenantPipe],
}
@Skill({ authorities }))authorities on @Skill is enforced exactly like the other entry types, across
every surface a skill is served from:
AuthorityDeniedError (MCP code -32003), the same as a denied tools/call.
Covers skills/load (MCP), skill://<path>/SKILL.md and skill://<path>/<file>
reads (SEP-2640), and GET /skills/{id} (HTTP).skills/search / skills/list (MCP), the skill://index.json discovery index and
skill-path autocomplete (SEP-2640), and GET /skills (HTTP).@Skill({ name: 'review-pr', description: '…', instructions: '…' }) // open to all
@Skill({ name: 'internal-runbook', description: '…', instructions: '…',
authorities: 'admin' }) // admins only
Two limitations to design around:
{ fromInput: '…' } ABAC/ReBAC policies can't be evaluated when
filtering and will hide the skill from discovery. Use role/permission/claims
authorities for discoverable skills; input-dependent policies still enforce at
load time. (Same limitation applies to tools/resources/prompts.)GET /skills
and denied on GET /skills/{id} regardless of the bearer. Serve gated skills over
an MCP transport for claims-based access. Ungated skills are unaffected.Boot-time fail-fast covers skills too: a @Skill with authorities but no configured
authorities engine fails server startup with AuthConfigurationError, exactly like a
tool/resource/prompt/agent.
| Scenario | Approach | Reference |
| ------------------------------------- | ----------------------------------------------------------------- | ---------------------------------- |
| Simple role gate (admin-only tool) | authorities: 'admin' profile | references/authority-profiles.md |
| Gate skill discovery + load | @Skill({ authorities: 'admin' }) (role/permission/claims-based) | references/authority-profiles.md |
| Permission-based access | authorities: { permissions: { all: ['x'] } } | references/rbac-abac-rebac.md |
| Tenant isolation | ABAC with { fromInput: 'tenantId' } | references/rbac-abac-rebac.md |
| Resource ownership check | ReBAC with relationship resolver | references/rbac-abac-rebac.md |
| IP allowlist or custom logic | Custom evaluator via custom.* | references/custom-evaluators.md |
| Different IdP (Auth0/Keycloak/Okta) | Configure claimsMapping | references/claims-mapping.md |
| Admin OR (editor AND same-tenant) | anyOf / allOf combinators | references/authority-profiles.md |
| Custom pre/post authority logic | Hook with Will/Did/Around on checkEntryAuthorities stage | references/custom-evaluators.md |
| Replace built-in check with OPA/Cedar | Around('checkEntryAuthorities') hook | references/custom-evaluators.md |
| Audit authority decisions | Did('checkEntryAuthorities') hook for logging/metrics | references/custom-evaluators.md |
| Tenant allowlist in Redis/DB | Async custom evaluator with custom.* field | references/custom-evaluators.md |
| Subscription check before tool runs | Async custom evaluator or Will('checkEntryAuthorities') hook | references/custom-evaluators.md |
| Feature flag gate on a tool | Async custom evaluator checking flag service | references/custom-evaluators.md |
| Pattern | Correct | Incorrect | Why |
| ----------------- | ------------------------------------------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| Claims mapping | claimsMapping: { roles: 'realm_access.roles' } | claimsMapping: { roles: 'roles' } (for Keycloak) | Keycloak nests roles under realm_access.roles; using the wrong path silently resolves to [] |
| Profile reference | authorities: 'admin' | authorities: { profile: 'admin' } | Profiles are referenced as plain strings, not nested objects |
| Multiple profiles | authorities: ['authenticated', 'matchTenant'] | authorities: 'authenticated, matchTenant' | Use an array, not a comma-separated string |
| Inline RBAC | { roles: { any: ['admin'] } } | { roles: ['admin'] } | roles expects an object with all and/or any arrays |
| Dynamic value ref | { fromInput: 'tenantId' } | '{{ tenantId }}' | Use DynamicValueRef objects, not template strings |
| OR combinator | { anyOf: [policy1, policy2] } | { operator: 'OR', ...policy1, ...policy2 } | Use anyOf for clarity; operator applies to top-level fields within a single policy |
| ReBAC resolver | Register relationshipResolver in plugin options | Inline the DB query in the policy | The resolver is a separate interface; policies only declare the relationship to check |
authorities: { ... } is set on the @FrontMcp decoratorclaimsMapping paths match the actual JWT token structure from your IdPauthorities: 'name' are registered in profilespublic) so authInfo is populatedrelationshipResolver is provided in plugin optionstools/list response omits entries the caller is not authorized to see{ fromInput: ... } resolve correctly from tool argumentsAuthoritiesResult objectsFrontMcpAuthorityProfiles is augmented for type-safe profile references@frontmcp/auth is imported so the authorities field is available on decorators| Problem | Cause | Solution |
| ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| profile 'admin' is not registered | Profile used in decorator but not in authorities.profiles config | Add the profile to the profiles field in @FrontMcp({ authorities }) |
| All users denied despite correct roles | claimsMapping.roles path does not match the actual JWT claim path | Decode a real JWT and verify the dot-path resolves to the roles array |
| authorities field not recognized on decorator | @frontmcp/auth not imported (metadata augmentation not active) | Add import '@frontmcp/auth' (or import type ... from '@frontmcp/auth') anywhere in your project to activate the metadata augmentation. There is no @frontmcp/auth/authorities subpath. |
| ABAC condition always fails | { fromInput: 'tenantId' } but tool input field is named tenant_id | The fromInput key must exactly match the tool's input schema field name |
| ReBAC always denies | No relationshipResolver provided | Implement RelationshipResolver and pass it to plugin options |
| Custom evaluator not found | Key in custom.* policy does not match registered evaluator name | Ensure the evaluator is registered with the same key used in the policy |
| List endpoints show all entries | No authorities config in @FrontMcp() or hook priority conflict | Verify authorities: { ... } is set on @FrontMcp() decorator |
| AuthorityDeniedError has no detail | deniedBy field shows generic message | Check the evaluatedPolicies array on the error for which policy type failed |
| TS error: evaluators/relationshipResolver/claimsResolver not on AuthoritiesConfig | The runtime Zod schema accepts these keys but the exported AuthoritiesConfig interface in authorities.profiles.ts only declares claimsMapping, profiles, scopeMapping, pipes. | Pass the config inline (TS infers from the decorator's broader type) or cast the typed config as the interface catches up. |
This skill currently exposes only references; see references/ for guidance.
Skills are distributed as plain SKILL.md files plus a sibling references/
and examples/ tree, so consumers can pick whichever access mode fits:
| Mode | How it works |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Filesystem | Read libs/skills/catalog/frontmcp-authorities/ directly from a clone of the catalog repo, or from a published @frontmcp/skills install. SKILL.md is the entry point. |
| frontmcp CLI | frontmcp skills list, frontmcp skills read frontmcp-authorities, frontmcp skills read frontmcp-authorities:references/<file>.md, frontmcp skills install frontmcp-authorities — no server required. |
| MCP skill:// | When a developer mounts this skill into their own FrontMCP server (@FrontMcp({ skills: [...] })), the SDK exposes it via SEP-2640 resources: skill://frontmcp-authorities/SKILL.md, skill://frontmcp-authorities/references/{file}.md, etc. The server’s skill://index.json returns the SEP-2640 discovery document for everything mounted on it. |
The catalog itself is not an MCP server. The skill:// URIs only resolve
when a server has been configured to host this skill.
libs/auth/src/authorities/ (engine, types, evaluators), flow stages in libs/sdk/src/tool/flows/, libs/sdk/src/resource/flows/, libs/sdk/src/prompt/flows/, libs/sdk/src/agent/flows/frontmcp-config, frontmcp-development, frontmcp-extensibilitytools
ALWAYS use this skill when the user asks to build, modify, or audit a FrontMCP tool. Covers everything inside `@Tool({...})`: class and function-style tools, Zod input/output schemas with derived `execute()` types, dependency injection (`this.get` / `this.tryGet`), error handling (`this.fail`, MCP error classes), throttling (rate-limit / concurrency / timeout), auth providers (single / multi / vault), availability constraints (`availableWhen`), elicitation (`this.elicit`), interactive UI widgets via `@Tool({ ui })` (MCP Apps / SEP-1865 — including `.tsx` FileSource, CSP, `window.FrontMcpBridge`, host-detect `resourceMode`), annotations (`readOnlyHint` / `destructiveHint` / …), `examples` metadata, registration in `@App({ tools })`, and per-tool unit testing. Does NOT cover: - Read-only data exposed via a URI — use `create-resource` - Conversation templates / system prompts — use `create-prompt` - Multi-tool orchestration loops — use `create-agent` - Background work / pipelines — use `create-job` / `create-workflow` - Server-level config (transport, sessions, auth modes) — use `config` / `auth` Triggers: `@Tool`, ToolContext, tool decorator, MCP tool, snake_case tool name, inputSchema, outputSchema, ToolInputOf, ToolOutputOf, `@Tool({ ui })`, tool UI widget, MCP Apps widget, FileSource widget, `.tsx` widget, ui.csp, ui.resourceMode, window.FrontMcpBridge, tool annotations, readOnlyHint, destructiveHint, rate-limit tool, throttle tool, concurrency tool, tool timeout, this.fail, this.respond, this.fetch, this.notify, this.progress, this.elicit, ElicitationDisabledError, ToolContext.execute, this.get(TOKEN), this.tryGet, register tool in @App, tool examples metadata, availableWhen, missingAxes, `tool()` function builder, Tool.esm, Tool.remote, PublicMcpError, ResourceNotFoundError, MCP_ERROR_CODES, ui://widget.
tools
Use when you want to add tracing, structured logging, or monitoring to your FrontMCP server. Covers OpenTelemetry instrumentation, vendor integrations (Coralogix, Datadog, Logz.io, Grafana), this.telemetry API for custom spans, structured JSON logging with sinks, and testing observability. Triggers: observability, telemetry, tracing, logging, monitoring, opentelemetry, otel, spans, datadog, coralogix, logz, grafana, winston, pino.
testing
Use when you want to write tests, run tests, add e2e tests, improve test coverage, test a tool, test a resource, or learn how to test any FrontMCP component. The skill for ALL testing needs.
tools
Domain router for project setup, scaffolding, and organization. Use this skill whenever someone asks to create a new FrontMCP project, set up an Nx monorepo, configure Redis or SQLite storage, organize project structure, compose multiple apps into one server, or manage the skills system. Also triggers for questions like 'how do I start', 'project layout', 'folder structure', 'add redis', 'set up database', or 'create a new app'.