DEV Community

Cover image for Building Authorization in FastAPI with Open Policy Agent (OPA)
Tural Muradov
Tural Muradov

Posted on • Edited on

Building Authorization in FastAPI with Open Policy Agent (OPA)

Authorization logic shouldn't be scattered across your Python
services. In this article, we'll build a complete authorization
service using Open Policy Agent (OPA) and opa-python-client,
from writing your first Rego policy to integrating it with FastAPI.


Why Another Authorization Article?

If you've built more than one backend service, you've probably written
code like this:

if current_user.role == "admin":
    return True

if current_user.id == document.owner_id:
    return True

raise HTTPException(status_code=403, detail="Forbidden")
Enter fullscreen mode Exit fullscreen mode

Initially, this seems perfectly reasonable.

Then your application grows.

Now you need to support:

  • Multiple user roles
  • Organization-level permissions
  • Temporary permissions
  • Country-specific regulations
  • Customer-specific rules
  • Feature flags
  • Auditable authorization decisions

Soon, every service has its own authorization implementation.

Business rules become duplicated.

Changing one permission requires modifying multiple services.

Testing authorization becomes difficult.

This is exactly the problem Open Policy Agent (OPA) solves.


What is Open Policy Agent?

Open Policy Agent is an open-source policy engine.

Instead of embedding authorization logic inside your application, your
application asks OPA a question.

Can Alice read this document?
Enter fullscreen mode Exit fullscreen mode

OPA evaluates your policies written in Rego and returns either:

true
Enter fullscreen mode Exit fullscreen mode

or

false
Enter fullscreen mode Exit fullscreen mode

Your application no longer needs to know how authorization works.

It simply enforces the decision.

Architecture

                +------------------+
                |   FastAPI App    |
                +------------------+
                         |
                         |
               Authorization Request
                         |
                         ▼
                +------------------+
                | opa-python-client|
                +------------------+
                         |
                     HTTP API
                         |
                         ▼
                +------------------+
                |      OPA         |
                +------------------+
                         |
                    Rego Policies
                         |
                    Data Documents
Enter fullscreen mode Exit fullscreen mode

Running OPA

docker run \
  --name opa \
  -p 8181:8181 \
  openpolicyagent/opa:latest \
  run --server
Enter fullscreen mode Exit fullscreen mode

Installing the Python Client

pip install opa-python-client
Enter fullscreen mode Exit fullscreen mode

Writing Our First Policy

Create authz.rego

package authz

default allow := false

allow if {
    input.user.role == "admin"
}

allow if {
    input.user.id == input.document.owner
}
Enter fullscreen mode Exit fullscreen mode

Uploading Policies

from opa import OPAClient

client = OPAClient("http://localhost:8181")

with open("authz.rego") as file:
    client.create_policy(
        policy_name="authz",
        policy=file.read(),
    )
Enter fullscreen mode Exit fullscreen mode

Evaluating Authorization

decision = client.check_policy(
    "authz/allow",
    {
        "user": {
            "id": 10,
            "role": "customer"
        },
        "document": {
            "owner": 10
        }
    }
)

print(decision)
Enter fullscreen mode Exit fullscreen mode

Expected output:

True
Enter fullscreen mode Exit fullscreen mode

Dynamic Data

client.update_data(
    "users",
    {
        "admins": [
            "alice",
            "bob",
            "charlie"
        ]
    }
)
Enter fullscreen mode Exit fullscreen mode
package authz

default allow := false

allow if {
    input.user.name in data.users.admins
}
Enter fullscreen mode Exit fullscreen mode

FastAPI Integration

from fastapi import FastAPI, HTTPException
from opa import AsyncOPAClient

app = FastAPI()

client = AsyncOPAClient("http://localhost:8181")

@app.get("/documents/{document_id}")
async def get_document(document_id: int):

    payload = {
        "user": {
            "id": 15,
            "role": "customer"
        },
        "document": {
            "owner": 15
        }
    }

    allowed = await client.check_policy(
        "authz/allow",
        payload
    )

    if not allowed:
        raise HTTPException(
            status_code=403,
            detail="Forbidden"
        )

    return {
        "id": document_id,
        "title": "My Document"
    }
Enter fullscreen mode Exit fullscreen mode

Production Recommendations

  • Enable HTTPS
  • Configure request timeouts
  • Use mutual TLS where appropriate
  • Store policies in Git
  • Test Rego policies
  • Separate policies from data
  • Monitor policy evaluation latency

Why I Built opa-python-client

The goal of opa-python-client is to provide a clean, Pythonic,
production-ready interface for interacting with Open Policy Agent.

Resources

Top comments (0)