As authorization rules grow complex, consider dedicated frameworks: Cerbos (open-source policy engine), Auth0 (managed service), AWS IAM (if on AWS). They centralize rules, make them testable, and scale to thousands of permissions. Without them, authorization logic scattered across your code becomes unmaintainable.
5 min read
As apps grow, authorization logic spreads everywhere:
# In view 1: can delete a post?
if user.role == "admin" or (user.role == "editor" and user.id == post.owner_id):
delete_post(post_id)
# In view 2: can comment on a post?
if user.role != "banned" and post.allow_comments:
add_comment(post_id, user.id, text)
# In view 3: can see post?
if user.role in ["admin", "editor", "viewer"] or post.is_public:
return post
# In view 4: can see analytics?
if user.role == "admin" or (user.organization_id == post.organization_id and user.role in ["admin", "editor"]):
return analyticsProblems:
An authorization framework centralizes rules in one place:
# authorization.yaml
rules:
delete_post:
- user.role == "admin"
- user.role == "editor" AND user.id == resource.owner_id
comment_on_post:
- user.role != "banned" AND resource.allow_comments
view_post:
- user.role IN ["admin", "editor", "viewer"]
- resource.is_public
view_analytics:
- user.role == "admin"
- user.organization_id == resource.organization_id AND user.role == "editor"Then your code calls the framework:
@app.delete('/posts/<post_id>')
def delete_post(post_id):
post = db.get_post(post_id)
# Centralized check
if not authz.allow(request.user, 'delete_post', post):
abort(403)
db.delete_post(post_id)
return 200Benefits:
Cerbos is a dedicated authorization service. You define policies in Cerbos, and your app asks Cerbos for decisions.
Install and run:
docker run -d -p 3592:3592 ghcr.io/cerbos/cerbos:latestDefine a policy:
# posts.yaml
apiVersion: api.cerbos.dev/v1
metadata:
resource: "post"
rules:
- actions: ['delete']
effect: EFFECT_ALLOW
principals:
- admin
condition:
match:
expr: "true" # admins can delete any post
- actions: ['delete']
effect: EFFECT_ALLOW
principals:
- editor
condition:
match:
expr: "principal.id == resource.owner_id" # editors can delete own postsQuery from your app:
import grpc
from cerbos import cerbos_service_pb2
# Create a request
request = cerbos_service_pb2.CheckResourcesRequest()
# Add principal (user)
principal = request.principal
principal.id = "alice"
principal.roles.append("editor")
# Add resource (post)
resource = request.resources.add()
resource.id = "post-123"
resource.kind = "post"
resource.attributes["owner_id"].string_value = "alice"
resource.attributes["title"].string_value = "My Blog"
# Add action (delete)
request.actions.append("delete")
# Query Cerbos
channel = grpc.aio.aio.secure_channel('localhost:3592', grpc.ssl_channel_credentials())
client = cerbos_service_pb2_grpc.CerbosServiceStub(channel)
response = client.CheckResources(request)
# Check result
if response.results[0].actions["delete"] == cerbos_service_pb2.EFFECT_ALLOW:
delete_post(post_id)
else:
abort(403)If your app runs on AWS, AWS IAM (Identity and Access Management) can handle authorization.
Define an IAM policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "posts:Delete",
"Resource": "arn:aws:posts:us-east-1::posts/*",
"Condition": {
"StringEquals": {
"aws:userid": "${post:owner_id}"
}
}
}
]
}Attach to a role/user. When your Lambda or EC2 instance runs code, the AWS SDK checks the policy.
Benefits:
Downside: only for AWS services, not for application-specific rules.
Auth0 (now Okta) is a managed identity and access management (IAM) platform. It handles authentication and authorization.
Define roles and permissions in Auth0's dashboard:
Role: Editor
Permissions:
- read:posts
- create:posts
- edit:own_posts
- delete:own_posts
Role: Viewer
Permissions:
- read:posts
Your app queries Auth0:
from auth0 import Auth0
auth0 = Auth0('your-domain.auth0.com', 'your-api-token')
# Get user's roles
user_roles = auth0.get_user_roles('alice') # ['editor']
# Get role permissions
permissions = auth0.get_role_permissions('editor') # ['read:posts', 'create:posts', ...]
# Check permission
if 'delete:posts' in permissions:
delete_post(post_id)Benefits:
Downside: vendor lock-in, cost per user.
Check user and resource attributes:
allow_edit_document:
- user.role == "admin"
- user.department == resource.department AND user.role == "editor"
- user.id == resource.owner_id AND resource.allow_owner_editRules that depend on time:
allow_access_during_office_hours:
- user.role == "admin" # admins always
- user.role == "employee" AND now().hour >= 9 AND now().hour <= 17Rules that depend on relationships:
can_view_team_resources:
- user.team_id == resource.team_id AND user.role in ["admin", "member"]Rules that respect organizational hierarchy:
can_manage_employees:
- user.role == "admin"
- user.department_head == true AND resource.department_id == user.department_idAuthorization rules must be tested:
def test_editor_can_delete_own_post():
user = User(id="alice", role="editor")
post = Post(id="123", owner_id="alice")
assert authz.allow(user, 'delete_post', post)
def test_editor_cannot_delete_others_post():
user = User(id="alice", role="editor")
post = Post(id="123", owner_id="bob")
assert not authz.allow(user, 'delete_post', post)
def test_admin_can_delete_any_post():
user = User(id="admin", role="admin")
post = Post(id="123", owner_id="bob")
assert authz.allow(user, 'delete_post', post)With a centralized framework, tests are simple and cover all cases.
Trusting the frontend to enforce authorization
// WRONG — frontend hides buttons for non-admins
if (user.role === "admin") {
showDeleteButton();
}Always enforce on the backend.
Mixing authorization with business logic
# WRONG — authorization is buried in the business logic
def delete_post(post_id):
post = db.get_post(post_id)
if request.user.role != "admin" and request.user.id != post.owner_id:
abort(403)
# ... business logicCORRECT — separate concerns
def delete_post(post_id):
if not authz.allow(request.user, 'delete_post', post):
abort(403)
# ... business logicNot testing authorization rules
Authorization rules are code and must be tested.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What is the main advantage of centralizing authorization rules in a framework?
2. What does Cerbos do?
3. When should you use AWS IAM for authorization?
Auth, Authorization & Multi-Tenancy