You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Request]: Article - how should permission be checked when developing plugin #3632
Admin routes authorize with policies instead. Most admin routes list admin::isAuthenticatedAdmin. Routes that need an RBAC action also list admin::hasPermissions with that action. Plugins use their own policies, for example plugin::content-manager.hasPermissions. These policies read ctx.state.userAbility.
For me it is unclear why/if the 2 policies are needed. I have tested by only checking hasPermissions without isAuthenticated and it worked. But maybe I am missing something. Logically it looks like - if is not authenticated , he will not have permissions anyway so looks like checking isAuthenticated is redundant.
Suggested solution(s)
But should we use this way? If so - in the doc at least a link to this should be I guess. And still need to clarify.
Like
Most admin routes list admin::isAuthenticatedAdmin.
this is probalby about internal strapi routes? So thats one of reasons - to have less confusion when not developing internal, but instead developing custom plugins.
I am not sure what exactly to suggest, but first suggestion is just make somethign official non internal I guess.
🤖 I've analyzed your question and here's what I found:
Thanks for raising this — it's a great question, and you've clearly done your homework by digging into the internal architecture docs. Let me clarify how permissions should be handled when developing a custom Strapi plugin.
How permissions work in plugin routes
admin::isAuthenticatedAdmin vs admin::hasPermissions
Your intuition is correct: if a user is not authenticated, they won't have any permissions anyway, so in practice, admin::hasPermissions alone is sufficient to protect a route. Here's why both policies exist in Strapi's internal routes:
admin::isAuthenticatedAdmin simply checks ctx.state.isAuthenticated — it's a lightweight, fast gate that short-circuits the request before any heavier RBAC logic runs. [isAuthenticatedAdmin source]
admin::hasPermissions checks ctx.state.userAbility against specific RBAC actions — it's the full permission check. [hasPermissions source]
For internal Strapi admin routes, using both is a defensive pattern. For custom plugin routes, you have flexibility depending on your needs.
Recommended approach for custom plugins
1. Admin routes — authentication only
If you just need to ensure the user is logged into the admin panel:
2. Admin routes — with RBAC (recommended for most plugins)
If you want fine-grained role-based access control, register custom permissions in your plugin's bootstrap function and then protect routes using admin::hasPermissions:
Register actions + admin::hasPermissions or <Page.Protect> / useRBAC
Custom role/logic checks
Write a plugin policy (plugin::my-plugin.myPolicy)
You're right that admin::isAuthenticatedAdmin is somewhat redundant when admin::hasPermissions is present — it's mainly a performance optimization in Strapi's internal routes. For custom plugins, using admin::isAuthenticatedAdmin alone is fine for simple auth gating, and adding RBAC on top is the recommended path for more granular control.
Regarding the documentation gap you've identified — this is a valid point and worth surfacing. You can submit a documentation improvement request at feedback.strapi.io. This response may also benefit from review by @pwizla to consider adding a clearer explanation of the two-policy pattern to the plugin development docs.
Hope this clears things up! Feel free to ask follow-up questions. 🙌
ℹ️ This response was generated automatically. If this doesn't fully answer your question or if you need further assistance, please mention @pwizla in a comment and a human maintainer will help you.
Activity
github-actions commentedon Oct 31, 2026
🤖 I've analyzed your question and here's what I found:
Thanks for raising this — it's a great question, and you've clearly done your homework by digging into the internal architecture docs. Let me clarify how permissions should be handled when developing a custom Strapi plugin.
How permissions work in plugin routes
admin::isAuthenticatedAdminvsadmin::hasPermissionsYour intuition is correct: if a user is not authenticated, they won't have any permissions anyway, so in practice,
admin::hasPermissionsalone is sufficient to protect a route. Here's why both policies exist in Strapi's internal routes:admin::isAuthenticatedAdminsimply checksctx.state.isAuthenticated— it's a lightweight, fast gate that short-circuits the request before any heavier RBAC logic runs. [isAuthenticatedAdmin source]admin::hasPermissionschecksctx.state.userAbilityagainst specific RBAC actions — it's the full permission check. [hasPermissions source]For internal Strapi admin routes, using both is a defensive pattern. For custom plugin routes, you have flexibility depending on your needs.
Recommended approach for custom plugins
1. Admin routes — authentication only
If you just need to ensure the user is logged into the admin panel:
[Route declaration formats]
2. Admin routes — with RBAC (recommended for most plugins)
If you want fine-grained role-based access control, register custom permissions in your plugin's
bootstrapfunction and then protect routes usingadmin::hasPermissions:Register permissions server-side:
[Admin permissions guide]
Protect pages on the admin side using
<Page.Protect>oruseRBAC:[Admin permissions guide]
3. Custom plugin policies
You can also write your own policies (e.g., role-based checks) and reference them by their namespaced name:
[Policies & middlewares]
Summary
admin::isAuthenticatedAdminadmin::hasPermissionsor<Page.Protect>/useRBACplugin::my-plugin.myPolicy)You're right that
admin::isAuthenticatedAdminis somewhat redundant whenadmin::hasPermissionsis present — it's mainly a performance optimization in Strapi's internal routes. For custom plugins, usingadmin::isAuthenticatedAdminalone is fine for simple auth gating, and adding RBAC on top is the recommended path for more granular control.Regarding the documentation gap you've identified — this is a valid point and worth surfacing. You can submit a documentation improvement request at feedback.strapi.io. This response may also benefit from review by
@pwizlato consider adding a clearer explanation of the two-policy pattern to the plugin development docs.Hope this clears things up! Feel free to ask follow-up questions. 🙌
📚 Relevant documentation:
ℹ️ This response was generated automatically. If this doesn't fully answer your question or if you need further assistance, please mention
@pwizlain a comment and a human maintainer will help you.You can also try our interactive AI chat for more detailed assistance.
💡 For feature requests or product feedback, visit feedback.strapi.io.