Overview
Authorization in Polar is split into two layers:- Authentication (
Authenticator): “Who are you?” — validates tokens/sessions, resolves the subject (User, Organization, Customer, Member), extracts token scopes. - Authorization (
server/polar/authz/): “Are you allowed to do this?” — checks org membership, evaluates policies, enforces access control.
Authenticator dependency. Authorization happens via PolicyGuard dependencies that resolve resources and check policies before the endpoint body runs.
PolicyGuard
A PolicyGuard is a FastAPI dependency factory that combines resource resolution and authorization into a single step. When an endpoint declares a PolicyGuard dependency, the framework:- Authenticates the subject
- Resolves the resource from the path parameter
- Checks that the subject has access to the owning organization
- Evaluates the policy function
- Returns an
AuthzContext(orAuthorizedAccount, etc.) containing both the resource and the auth subject
Error behavior
| Scenario | HTTP status | Reason |
|---|---|---|
| No valid credentials | 401 Unauthorized | Authenticator rejects the request |
| Token lacks required scopes | 403 Forbidden | Authenticator scope check fails |
| Resource doesn’t exist | 404 Not Found | Resource not found in database |
| Subject not a member of the org | 404 Not Found | Returns 404 (not 403) to avoid leaking existence |
| Subject is a member but policy denied | 403 Forbidden | Policy returned a denial reason |
Guard variants
There are three PolicyGuard variants for different resource types:| Guard | Resolves by | Use case |
|---|---|---|
OrgPolicyGuard | {id} → Organization | Org-scoped endpoints (/organizations/{id}/...) |
AccountPolicyGuard | {id} → Account → owning Organization | Account endpoints (/accounts/{id}) |
PayoutAccountPolicyGuard | {id} → PayoutAccount → owning Organization | Payout account endpoints (/payout-accounts/{id}) |
Typed dependencies
Pre-built typed dependencies are defined inserver/polar/authz/dependencies.py:
Policies
Policies are async functions inserver/polar/authz/policies/. Each policy receives the database session, auth subject, and organization, and returns either True (allowed) or a denial reason string.
NotPermitted exception and appears in the 403 response body.
Policy files
| File | Functions | Controls access to |
|---|---|---|
policies/finance.py | can_read, can_write | Accounts, transactions, payouts |
policies/organization.py | can_delete | Organization deletion |
policies/members.py | can_manage | Inviting, removing, and changing members |
Org-scoped data access
Repositories are pure data access — they don’t check authentication or authorization. Instead, they accept explicit org IDs:get_accessible_org_ids returns the set of organization IDs the subject can access:
- For a User: all orgs they’re a member of (via
UserOrganization) - For an Organization token: just that org’s ID
- For anything else: empty set
Scopes
Token scopes are checked by theAuthenticator as a first-pass filter. They represent what the token is allowed to do, not what the user is allowed to do.
- Web sessions have all scopes — the user’s permissions are determined entirely by org membership and policies
- PATs (Personal Access Tokens) have user-selected scopes — a CI token might only have
products_read - OATs (Organization Access Tokens) have org-selected scopes
required_scopes appropriate to their resource area. For example, AuthorizeFinanceRead requires transactions_read, transactions_write, payouts_read, or payouts_write.
Adding a new policy-guarded endpoint
- Choose or create a policy function in
server/polar/authz/policies/ - Create a typed dependency in
server/polar/authz/dependencies.pyusing the appropriate guard variant - Use the dependency in the endpoint — declare it as a parameter, access
.organizationand.auth_subjectfrom the result

