Route permissions
Goal
Gate plugin routes by who's calling them. The plugin SDK exposes one primary mechanism: per-plugin roles via context.require_plugin_role(*roles). The platform's broader RBAC permission set is enforced by the platform's own routes — plugin code can't import the platform's require_permission.
Plugin role guard
The simplest case — restrict to users with a specific plugin role.
from fastapi import APIRouter, Depends
router = APIRouter()
class MyPlugin(AnalysisPlugin):
async def initialize(self, context=None):
self._context = context
if context is not None:
self.admin_only = Depends(context.require_plugin_role("admin"))
self.editor_or_admin = Depends(context.require_plugin_role("editor", "admin"))
else:
async def _stub(): return None
self.admin_only = Depends(_stub)
self.editor_or_admin = Depends(_stub)
@router.delete("/items/{item_id}")
async def delete_item(item_id: int, _user = self.admin_only):
...require_plugin_role(*roles) automatically lets platform admins through — you don't add a special case.
Authenticated-user dependency
When you don't need a specific role, just an authenticated user:
class MyPlugin(AnalysisPlugin):
async def initialize(self, context=None):
self._context = context
if context is not None:
self.current_user = Depends(context.get_current_user_dependency())
else:
async def _stub(): return None
self.current_user = Depends(_stub)
@router.get("/me/preferences")
async def my_prefs(user = self.current_user):
return await load_prefs(user.id)get_optional_user_dependency() is the variant that returns None for unauthenticated requests instead of raising.
Combining with platform permissions
The platform RBAC permissions (projects.view, experiments.edit, plugins.use, etc.) are enforced only by the platform's own routes — not by plugin code. Plugins cannot import require_permission from api.dependencies.permissions; that module is platform-internal.
When a request reaches your plugin's route, it has already passed the platform's auth check (the user is authenticated). Use context.require_plugin_role(*roles) for fine-grained authorization within plugin routes. To check the user's platform role explicitly, inspect user.role (the string "Admin" / "Member" / "Viewer" / a custom role name).
from fastapi import HTTPException, status
@router.post("/admin/maintenance/run")
async def run_maintenance(user = self.admin_only):
# plugin-admin and platform-admin both pass
if not _is_safe_to_run():
raise HTTPException(status.HTTP_409_CONFLICT, "another maintenance run is active")
return await _do_maintenance()Standalone fallback
In standalone mode, self._context is None and there's no auth. Stub out the dependencies so the same route code runs:
class MyPlugin(AnalysisPlugin):
async def initialize(self, context=None):
self._context = context
if context is not None:
self.admin_only = Depends(context.require_plugin_role("admin"))
else:
async def _stub_admin(): return None # always allow in dev
self.admin_only = Depends(_stub_admin)For a stricter standalone (e.g., a CI test of the guard's behavior), wire the stub to raise a 403 instead.
Patterns to avoid
- Don't read the
Authorizationheader yourself. The platform's auth dependency owns JWT/passkey validation. Reading it directly will miss session cookies, MFA, and SSO paths. - Don't hardcode admin user IDs. Always go through
require_plugin_role(auto-bypasses platform admins) or the user'sroleattribute (user.role == "Admin"). - Don't mix HTTP error codes from FastAPI with
PluginException. ThrowPluginExceptionsubclasses (PermissionException,ValidationException, …) and let the platform's middleware turn them into structured responses. See Recipes → Error handling.
Notes
require_plugin_rolereads the user's role fromPluginRoleRepositoryon every request. Cached lookups would defeat the dynamic admin-bypass for revoked admins; the platform handles caching at its repository layer.- A user without a plugin role assignment has role
None—require_plugin_rolerejects them unless they're a platform admin. - To list a user's role for the current plugin (e.g., to surface in the frontend), expose it via a
/me/roleroute — see Tutorial 4 → Plugin roles.
Related
- Concepts → PlatformContext —
require_plugin_roleAPI - Tutorials → Plugin roles — end-to-end role setup
- Reference → Permissions — platform RBAC catalog