API reference¶
Core¶
starlette_permissions.base.BasePermission ¶
Subclass this and override :meth:has_permission.
class IsBetaTester(BasePermission):
message = "Beta programme members only"
def has_permission(self, ctx):
return "beta" in ctx.roles
Either method may be def or async def. Instances are shared across
requests, so keep them immutable — all per-request state lives on the
context.
has_permission ¶
Decide based on the request alone. Defaults to allowing everything.
has_object_permission ¶
Decide based on a specific object, once you have loaded it.
Only consulted by :func:~starlette_permissions.checks.check_object_permissions
and the @object_permission_required decorator — a rule that needs
the object cannot run before the handler has fetched it.
denial ¶
Build the exception describing this refusal.
Override for rules that need to vary the response — adding a
Retry-After on a throttle, say.
starlette_permissions.context.PermissionContext ¶
Everything a permission needs to decide, with user lookup cached.
Attributes:
| Name | Type | Description |
|---|---|---|
connection |
The |
|
endpoint |
The view function, when the check runs on one. |
|
view_kwargs |
Mapping[str, Any]
|
The arguments the endpoint was called with. Useful for object-level rules that key off a path parameter. |
request
property
¶
Alias for :attr:connection, for readability in HTTP-only code.
is_authenticated
property
¶
Whether the request carries an identity.
A user object may say so itself via is_authenticated (Starlette's
BaseUser and Django's user both do). Otherwise, merely having a
non-None user counts.
starlette_permissions.checks ¶
The evaluation entry points.
Everything else in the library — the decorator, the dependency, the mixin, the
middleware — funnels into :func:check_permissions. You can also call these
directly from a service layer, where there is no HTTP handler to decorate.
check_permissions
async
¶
check_permissions(
permissions: PermissionLike | Sequence[PermissionLike],
ctx: PermissionContext,
*,
mode: Mode = "all",
) -> None
Evaluate permissions and raise if the request is not allowed.
Raises:
| Type | Description |
|---|---|
PermissionDenied
|
with the message and status code of the rule that
refused. |
has_permissions
async
¶
has_permissions(
permissions: PermissionLike | Sequence[PermissionLike],
ctx: PermissionContext,
*,
mode: Mode = "all",
) -> bool
Non-raising variant of :func:check_permissions.
Handy for branching in a template or trimming a response, where a denial should hide a field rather than fail the request.
check_object_permissions
async
¶
check_object_permissions(
permissions: PermissionLike | Sequence[PermissionLike],
ctx: PermissionContext,
obj: Any,
*,
mode: Mode = "all",
) -> None
Run object-level checks against obj, raising on refusal.
Call this once you have loaded the record — that is the earliest point at which a rule like "you may only edit your own posts" can be decided.
has_object_permissions
async
¶
has_object_permissions(
permissions: PermissionLike | Sequence[PermissionLike],
ctx: PermissionContext,
obj: Any,
*,
mode: Mode = "all",
) -> bool
Non-raising variant of :func:check_object_permissions.
Enforcement¶
starlette_permissions.decorators ¶
Decorator-based enforcement.
The decorator must sit below the route decorator, so the router registers the guarded function rather than the bare one. Works on FastAPI and Starlette endpoints, sync and async.
permission_required ¶
permission_required(
*permissions: PermissionLike | Sequence[PermissionLike],
mode: Mode = "all",
message: str | None = None,
status_code: int | None = None,
) -> Callable[[F], F]
Guard an endpoint with one or more permissions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*permissions
|
PermissionLike | Sequence[PermissionLike]
|
Permission classes, instances, or predicate functions.
A single list is accepted too, so |
()
|
mode
|
Mode
|
|
'all'
|
message
|
str | None
|
Overrides the failing permission's message for this endpoint. |
None
|
status_code
|
int | None
|
Overrides the refusal status code for this endpoint. |
None
|
Raises:
| Type | Description |
|---|---|
MissingRequestError
|
at request time, if no |
object_permission_required ¶
object_permission_required(
*permissions: PermissionLike | Sequence[PermissionLike],
mode: Mode = "all",
getter: Callable[..., Any] | None = None,
message: str | None = None,
status_code: int | None = None,
) -> Callable[[F], F]
Guard an endpoint with object-level permissions.
Two shapes, depending on when the object becomes available:
- With
getter, the object is loaded and checked before the handler runs. The getter receives the endpoint's own keyword arguments and may be sync or async. Prefer this — nothing happens if the caller is refused. - Without
getter, the handler's return value is the object, and it is checked after the handler runs. Convenient for a plain "fetch and return" route, but the fetch has already happened by then.
get_permissions ¶
Read back the permissions attached to an endpoint.
Useful in tests, and for generating an access matrix from a router.
starlette_permissions.dependencies ¶
FastAPI dependency-based enforcement.
Preferred over the decorator on FastAPI: it needs no signature rewriting, it composes with the rest of the dependency graph, and it can be attached to a whole router in one place.
@router.get("/me", dependencies=[requires(IsAuthenticated)])
async def get_me(): ...
router = APIRouter(dependencies=[requires(IsService)])
Importing this module requires FastAPI. The rest of the library does not.
Note
This module deliberately does not use from __future__ import
annotations. FastAPI reads these annotations at runtime to decide what to
inject, resolving any string it finds against the callable's
__globals__ — and a class instance such as :class:PermissionChecker
has none. Stringified annotations therefore make older FastAPI treat
request as a query parameter instead of injecting the request. Keeping
real objects here avoids the whole class of problem.
PermissionChecker ¶
A callable dependency that enforces permissions, or raises.
Instantiate it directly when you want the object itself — to reuse one checker in several places, or to call it from your own dependency.
requires ¶
requires_object ¶
permission_responses ¶
current_context ¶
starlette_permissions.endpoints ¶
Class-based enforcement for Starlette's HTTPEndpoint.
class PostEndpoint(PermissionMixin, HTTPEndpoint):
permission_classes = [IsAuthenticated]
async def get(self, request): ...
async def delete(self, request): ...
Per-method rules are supported too, which is usually what you want once read and write differ:
class PostEndpoint(PermissionMixin, HTTPEndpoint):
permission_classes = {
"*": IsAuthenticated,
"DELETE": IsAdminUser,
}
PermissionMixin ¶
Checks permission_classes before dispatching to the handler.
Must come before HTTPEndpoint in the base list, so that its
dispatch runs first.
get_permissions
classmethod
¶
Resolve the permissions applying to method.
A mapping combines the wildcard entry with the method-specific one, so
{"*": IsAuthenticated, "DELETE": IsAdminUser} requires both on
DELETE rather than replacing one with the other.
starlette_permissions.middleware ¶
Apply permissions to a whole app, or to a subtree of routes.
app.add_middleware(
PermissionMiddleware,
permissions=IsAuthenticated,
exempt=["/health", re.compile(r"/auth/.*")],
)
Use this for a blanket default ("everything needs a login except these"). For anything route-specific, the decorator or dependency stays clearer, because the rule lives next to the handler it guards.
PermissionMiddleware ¶
ASGI middleware that enforces permissions before the app runs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
ASGIApp
|
The wrapped ASGI application. |
required |
permissions
|
PermissionLike | Sequence[PermissionLike]
|
A permission, or any nesting of lists of them. |
()
|
mode
|
Mode
|
|
'all'
|
exempt
|
Collection[str | Pattern[str]]
|
Paths that skip the check. A |
()
|
methods
|
Collection[str] | None
|
Restrict the check to these HTTP methods. |
None
|
Note
Added via app.add_middleware, this sits outside Starlette's
ExceptionMiddleware, so it renders its own JSON response rather
than raising — a raised HTTPException would surface as a 500 there.
Note
Middleware runs before routing, so ctx.view_kwargs is empty here.
Rules that need a path parameter belong on the route itself.
Built-in permissions¶
starlette_permissions.permissions.common ¶
Permissions that depend only on the request, not on who is making it.
AllowAny ¶
DenyAll ¶
ReadOnly ¶
Bases: BasePermission
Allows only GET, HEAD and OPTIONS.
Usually combined: ReadOnly | IsAdminUser gives everyone read access and
admins write access.
IsMethod ¶
Predicate ¶
Bases: BasePermission
Wraps a plain function as a permission.
The function takes the context and returns a bool; it may be async. This is what makes bare functions usable anywhere a permission is expected:
HasHeader ¶
permission ¶
starlette_permissions.permissions.auth ¶
Identity-based permissions.
IsAuthenticated ¶
Bases: BasePermission
Requires a user on the request.
Refuses with 401 rather than 403, since the caller can fix this by
authenticating. Set unauthenticated_status_code=403 in
:func:~starlette_permissions.configure if you would rather not
distinguish the two.
IsAnonymous ¶
IsAdminUser ¶
Bases: BasePermission
Requires an authenticated user flagged as an administrator.
Checks each of settings.admin_attrs in turn — by default
is_admin, is_staff, is_superuser — and allows if any is truthy.
IsAuthenticatedOrReadOnly ¶
starlette_permissions.permissions.roles ¶
Role- and scope-based permissions.
Roles come from settings.role_getter, which by default reads roles,
groups or role off the user object. Scopes come from
settings.scope_getter, which by default reads Starlette's
AuthenticationMiddleware credentials.
HasAnyRole ¶
Bases: _RoleBase
Requires at least one of the given roles.
HasAllRoles ¶
Bases: _RoleBase
Requires every one of the given roles.
HasAnyScope ¶
Bases: _RoleBase
Requires at least one of the given OAuth-style scopes.
HasAllScopes ¶
Bases: _RoleBase
Requires every one of the given OAuth-style scopes.
starlette_permissions.permissions.api_key ¶
API-key permissions, for service-to-service calls.
This is the generalisation of a hand-rolled IsService check:
Passing a callable keeps the comparison against the current configured value, which matters when settings are loaded lazily or rotated at runtime.
HasAPIKey ¶
Bases: BasePermission
Requires a matching API key in a header (or query parameter).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str | Collection[str] | Callable[[], str | Collection[str]]
|
The accepted key, a collection of accepted keys, or a callable returning either. A callable is re-invoked on every request. |
required |
header
|
str
|
Header carrying the key. Defaults to |
'X-API-Key'
|
query_param
|
str | None
|
Optional query parameter to accept the key from as well. Off by default — keys in URLs end up in access logs. |
None
|
message
|
str | None
|
Overrides the refusal message. |
None
|
Comparison uses :func:secrets.compare_digest, so a wrong key takes the
same time to reject regardless of how much of it was right.
starlette_permissions.permissions.objects ¶
Object-level permissions — rules that need the record, not just the request.
These only take effect through :func:~starlette_permissions.check_object_permissions,
@object_permission_required or requires_object. A request-level check
cannot decide "you may edit your own post" before the post has been loaded, so
has_permission on these classes deliberately allows everything.
ObjectPermission ¶
IsOwner ¶
Bases: ObjectPermission
Allows access only to the object's owner.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
owner_field
|
str
|
Attribute (or dict key) on the object holding the owner's
identifier. Defaults to |
'user_id'
|
user_id
|
Callable[[Any], Any]
|
How to get the comparable identifier from the current user.
Defaults to the first of |
_default_user_id
|
message
|
str | None
|
Overrides the refusal message. |
None
|
Composition¶
starlette_permissions.operators ¶
Boolean combinators: &, |, ~ and the All/Any helpers.
Combinators are permissions themselves, so they nest freely::
IsAuthenticated & (IsAdminUser | IsOwner("user_id")) & ~IsBanned
AND ¶
Bases: _Binary
Both sides must allow. Short-circuits on the first refusal.
OR ¶
Bases: _Binary
Either side may allow.
When both refuse, the failure is reported as the left side's — the rule written first, which is normally the broader one. This keeps the message a caller sees stable no matter which branch was evaluated last.
NOT ¶
Bases: BasePermission
Inverts a permission.
The wrapped rule's message would be misleading here (it explains why it
allows), so the refusal is reported against the NOT itself.
All ¶
Require every permission. The explicit form of chained &.
All() with no arguments allows everything, matching all([]).
Any_ ¶
Require at least one permission. The explicit form of chained |.
Exported as Any from the package root; the trailing underscore here
only keeps it from shadowing typing.Any inside this module.
Any_() with no arguments denies everything, matching any([]).
Not ¶
Invert a permission. The explicit form of ~.
Configuration¶
starlette_permissions.settings ¶
Global and per-app configuration.
The defaults work with Starlette's AuthenticationMiddleware out of the box.
Anything else — a token on request.state, a custom user model, roles stored
somewhere unusual — is a one-line :func:configure call at startup.
PermissionSettings
dataclass
¶
Immutable settings bundle. Use :func:configure to change the global one.
configure ¶
get_settings ¶
Return the settings for this connection.
An app can carry its own bundle on app.state.permission_settings, which
wins over the global one. That keeps mounted sub-applications independent.
override_settings ¶
Temporarily replace global settings. Intended for tests.
Exceptions¶
starlette_permissions.exceptions ¶
Exceptions raised when a permission check fails.
Both exceptions derive from Starlette's HTTPException, so FastAPI's default
handler renders them as {"detail": ...} JSON with no extra setup. Plain
Starlette renders HTTPException as plain text; call
:func:install_exception_handlers if you want JSON there too.
PermissionDenied ¶
Bases: HTTPException
Raised when one or more permission checks fail. Renders as 403.
NotAuthenticated ¶
ConfigurationError ¶
Bases: RuntimeError
The library was handed something it cannot use as a permission.
MissingRequestError ¶
Bases: ConfigurationError
No Request/WebSocket could be found for the endpoint being guarded.
SyncCheckError ¶
Bases: ConfigurationError
An async permission was evaluated from a context that cannot await it.
install_exception_handlers ¶
Render permission failures as JSON on a plain Starlette app.
FastAPI already does this for every HTTPException, so calling it there
is a no-op in practice. On Starlette the built-in handler returns
PlainTextResponse, which is rarely what an API wants.
Testing helpers¶
starlette_permissions.testing ¶
Helpers for unit-testing permissions without standing up an app.
ctx = make_context(user=User(id=1, roles=["admin"]), method="DELETE")
assert await has_permissions(HasRole("admin"), ctx)
make_request ¶
make_request(
*,
method: str = "GET",
path: str = "/",
headers: Mapping[str, str] | None = None,
query_string: str = "",
user: Any = _UNSET,
auth: Any = None,
path_params: Mapping[str, Any] | None = None,
app: Any = None,
) -> Request
Build a Request with just enough ASGI scope to check permissions.
user is placed on the scope where Starlette's AuthenticationMiddleware
would put it, so the default user_getter finds it.
make_context ¶
make_context(
*,
settings: PermissionSettings | None = None,
view_kwargs: Mapping[str, Any] | None = None,
endpoint: Any = None,
**request_kwargs: Any,
) -> PermissionContext
Build a :class:PermissionContext directly. Arguments as :func:make_request.
Compatibility¶
starlette_permissions.compat ¶
Drop-in replacement for a hand-rolled permission_required.
This exists so an existing codebase can adopt the library with an import swap and migrate afterwards, one module at a time. It reproduces the older behaviour exactly, including the parts the main API deliberately changed:
- OR semantics — a list passes if any permission passes. The main
permission_requiredrequires all of them, matching Django and DRF. - A returned response rather than a raised exception, so exception handlers and error-logging middleware never see the denial.
- Permissions defined as
is_permitted(request, *args, **kwargs).
Every use emits a DeprecationWarning. To migrate a call site:
# before
from starlette_permissions.compat import permission_required
@permission_required([IsAuthenticated])
# after
from starlette_permissions import permission_required
@permission_required(IsAuthenticated)
With a single permission in the list the two are equivalent, so most call sites can move without any behaviour change at all.
BasePermission ¶
The legacy base class: a static is_permitted taking the request.
New code should subclass :class:starlette_permissions.BasePermission,
whose has_permission receives a
:class:~starlette_permissions.PermissionContext instead.
LegacyPermission ¶
Bases: BasePermission
Adapts an is_permitted-style permission to the current interface.
Produced automatically by
:func:~starlette_permissions.base.resolve_permission, so legacy
permissions can be mixed into &/| expressions and passed to the
modern decorator without being rewritten first.
permission_required ¶
Legacy decorator: OR semantics, returns a 403 response instead of raising.
Deprecated since 0.1. Use :func:starlette_permissions.permission_required.