Apply when controlling access to VTEX IO app resources using role-based or resource-based policies. Covers policies.json for role-based access control, service.json policies for resource-based access, VRN syntax for principals, the difference between app-to-app and user/integration access, and GraphQL @auth directives. Use when deciding how to secure routes and restrict which apps, users, or integrations can access your endpoints.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Apply when controlling access to VTEX IO app resources using role-based or resource-based policies. Covers policies.json for role-based access control, service.json policies for resource-based access, VRN syntax for principals, the difference between app-to-app and user/integration access, and GraphQL @auth directives. Use when deciding how to secure routes and restrict which apps, users, or integrations can access your endpoints.
metadata
{"track":"vtex-io","tags":["vtex-io","rbac","policies","access-control","security","vrn","auth","principals"],"globs":["**/policies.json","**/service.json","**/*.graphql"],"version":"1.0","purpose":"Secure IO app routes with the correct access control policy type","applies_to":["securing REST endpoints for app-to-app communication","controlling which users or integrations can call private routes","setting up GraphQL authorization with @auth directive","understanding VRN syntax for principals"],"excludes":["general backend service patterns (see vtex-io-service-apps)","PCI payment security (see payment-pci-security)"],"decision_scope":["role-based-vs-resource-based-policies","graphql-auth-vs-rest-policies","public-vs-private-routes"],"vtex_docs_verified":"2026-03-30"}
VTEX IO access control (RBAC)
When this skill applies
Use this skill when you need to control who can access your VTEX IO app's routes and resources:
Deciding between role-based (policies.json) and resource-based (service.json policies) access control
Securing REST endpoints so only specific apps, users, or API keys can call them
Setting up GraphQL authorization with the @auth directive
Understanding (VTEX Resource Name) syntax for declaring principals
VRN
Debugging 403 Forbidden errors caused by missing or misconfigured policies
Do not use this skill for:
General service architecture (use vtex-io-service-apps)
PCI compliance and payment security (use payment-pci-security)
Route prefix and CDN behavior (use vtex-io-service-paths-and-cdn)
Decision rules
Role-based vs resource-based policies
Role-based (policies.json)
Resource-based (service.json policies)
Who can call?
Only other IO apps (by themselves or on behalf of other apps)
Apps, users, and integrations (API keys)
API types
GraphQL and REST
REST only
How callers get access
Must declare required policies in their manifest.json
No policy declaration needed; just call with auth token
Where configured
policies.json in app root
policies array inside route definition in service.json
Use when
Exposing GraphQL endpoints; exposing REST endpoints for app-to-app only
Controlling access for users, API keys, or specific apps to REST endpoints
Choosing the right approach
GraphQL endpoints → Use role-based policies (policies.json) and/or the @auth directive in the schema for user-level authorization.
REST endpoint called only by other IO apps → Use role-based policies (policies.json). Consuming apps must declare the policy in their manifest.json.
REST endpoint called by users or API keys → Use resource-based policies in service.json. Set the route as "public": false and define principals.
Public REST endpoint (no auth) → Set "public": true in service.json. No policies needed, but be aware this means anyone can call it.
VRN syntax
VRNs (VTEX Resource Names) identify resources and principals:
API keys: vrn:vtex.vtex-id:*:*:*:user/vtexappkey-{account}-{hash}
Wildcards: * matches any value in a segment. app/* matches all apps. user/*@gmail.com matches all Gmail users.
Hard constraints
Constraint: Use resource-based policies when users or API keys need access
Role-based policies only work for app-to-app communication. If users (admin or storefront) or integrations (API keys) need to call your endpoint, you must use resource-based policies in service.json with the route set to "public": false.
Why this matters — Setting up a role-based policy for a route that users or API keys call results in 403 Forbidden for those callers, because role-based policies don't evaluate user/integration tokens.
Detection — A private route that should be callable by admin users or external integrations, but only has policies.json configuration and no policies array in service.json.
Correct — Resource-based policy in service.json for user/integration access.
Wrong — Only policies.json for a route that users need.
// policies.json — this only covers app-to-app, not users[{"name":"access-orders","statements":[{"effect":"allow","actions":["GET"],"resources":["vrn:my-app:*:*:*:/_v/private/my-app/orders"]}]}]// Users calling this route still get 403
Constraint: Deny policies take precedence over allow policies
When resource-based policies have overlapping principals between an allow and a deny rule, the deny always wins. Be careful with wildcards in allow rules that intersect with specific deny rules.
Why this matters — A broad allow for app/* combined with a specific deny for app/vendor.bad-app@* correctly blocks bad-app. But the reverse—a broad deny with a specific allow—blocks everything including what you wanted to allow.
Detection — Multiple policy entries for the same route with conflicting effects and overlapping principals.
The @auth directive checks the caller's License Manager role for the specified productCode and resourceCode.
Common failure modes
403 for users on role-based routes — Route only has policies.json; users and API keys get 403 because role-based policies don't apply to them.
Overly broad public: true — Route set to public when it should be private. Anyone can call it without auth.
Missing policy in consumer manifest — App tries to call a role-based protected route but didn't declare the policy in its manifest.json. Results in 403.
VRN typo — Misspelled vendor, app name, or principal format in VRN. Silently fails to match, resulting in 403.
Wildcard in deny — Broad deny with app/* blocks all apps including trusted ones. Deny takes precedence.
No @auth on GraphQL mutations — Mutations that modify data accessible without role checks.
Review checklist
Is the access control type (role-based vs resource-based) correct for the callers (apps vs users/integrations)?
Are private routes set to "public": false with appropriate policies?
Are VRNs correctly formatted for the principal type (apps, users, API keys)?
Do consuming apps declare required role-based policies in their manifest.json?
Are deny rules used carefully (they override allow rules for intersecting principals)?
Do GraphQL mutations have @auth directives with correct productCode and resourceCode?
Are wildcard principals scoped as narrowly as possible?