Authorisation and Access Control

By Davy Rogers

Authentication says who. Authorisation says what. One is consistently broken.

Why it's broken

  • Authentication is centralised. Authorisation is distributed across every endpoint.
  • Frameworks give auth middleware. Authorisation is business logic - you build it.

IDOR

Insecure direct object reference. App uses user-controlled ID without checking ownership.

GET /api/orders/4821

Does the user own order 4821? If not checked, any authenticated user can access any order.

Fix - verify ownership:

order = Order.objects.get(id=order_id)
if order.user_id != request.user.id:
    raise PermissionDenied()

Better - scope queries:

order = Order.objects.get(id=order_id, user_id=request.user.id)

Access control models

RBAC - roles have permissions. Simple apps, fixed roles.

ABAC - attributes of user, resource, environment. Complex enterprise.

ReBAC - relationship-based. "This user is an editor of this document." Collaboration, sharing.

ScenarioModel
Simple appRBAC
Multi-tenant SaaSRBAC per tenant or ReBAC
Complex enterpriseABAC
CollaborationReBAC

Common mistakes

Checking roles instead of permissions:

# Fragile
if user.role == "admin":
    delete_user(target_id)

# Better
if user.has_permission("users:delete"):
    delete_user(target_id)

Client-side only: Hiding a button isn't access control. Server must check.

Inconsistent enforcement: GET checks ownership, PUT doesn't. Attacker modifies what they can't view.

Mass assignment: API accepts { "role": "admin" } without filtering fields.

Hidden admin URLs: /api/admin/users lacks auth because "only admins know the URL."

Doing it right

Centralise policy: Define rules in one place. Auditable, testable.

Deny by default: New endpoint without auth check fails closed.

Test explicitly:

def test_user_cannot_access_other_users_order():
    order = create_order(user=user_a)
    response = client.get(f"/api/orders/{order.id}", auth=user_b)
    assert response.status_code == 404

Log denials: Sequential ID attempts show probing.

The takeaway

Check ownership on every resource access. Permissions not role names. Server-side. Deny by default. Scope queries to authenticated user.

Want a professional to look at it?Get an AppSec Health Check.