A Django REST API demonstrating fine-grained authorization using OpenFGA (Open Fine-Grained Authorization) for managing file access permissions based on relationships.
This project implements a file management system where access control is managed through OpenFGA, providing relationship-based authorization (ReBAC) instead of traditional role-based access control (RBAC).
| Technology | Version | Purpose |
|---|---|---|
| Django | 5.2+ | Backend web framework |
| Django REST Framework | - | RESTful API development |
| OpenFGA | latest | Fine-grained authorization engine |
| PostgreSQL | 17 | Relational database for OpenFGA data |
| Docker Compose | - | Container orchestration |
| Python OpenFGA SDK | latest | Authorization client library |
| JWT | - | API authentication |
| Service | Container | Purpose | Ports |
|---|---|---|---|
| postgres | openfga-postgres | Stores OpenFGA authorization data, and django migrations | 5432 |
| openfga-migrate | openfga-migrate | One-shot migration container for database schema | - |
| openfga | openfga | Authorization server handling permission checks | 8080 (HTTP), 8081 (gRPC), 3000 (Playground) |
| django-web | django-docker | REST API server with file management logic | 8000 |
| openfga-cli | openfga-cli | CLI tool for managing stores and authorization models | - |
All services run on a shared openfga Docker bridge network, enabling internal DNS resolution (e.g., http://openfga:8080).
Key Connections:
- Django → OpenFGA (HTTP:8080) - Authorization checks via Python SDK
- OpenFGA → Postgres (TCP:5432) - Stores tuples and models
- Host → Django (HTTP:8000) - API access via Postman/Browser
- OpenFGA CLI → OpenFGA (HTTP:8080) - Store/model management
- Docker & Docker Compose
- Python 3.11+ (for local development)
git clone https://github.com/Sweekar07/filehub
git checkout main
cd filehub[!IMPORTANT] Branch
The local development setup is available in
mainbranch only.Branch: main
Make sure to switch to
mainbranch.
- Build and Start the container:
docker compose -f docker-compose.local.yml up --build- Stop the container:
docker compose -f docker-compose.local.yml downdocker compose -f docker-compose.local.yml: Specifies the Docker Compose file to use.run --rm: Runs the command in a temporary container that is removed after execution.openfga-cli: The OpenFGA CLI service defined in your Docker Compose file.--api-token=key1: The API token for authenticating with the OpenFGA server.
- Command:
docker compose -f docker-compose.local.yml run --rm openfga-cli --api-token=key1 store create --name="FileHub"- Output:
{
"store": {
"created_at":"2025-12-17T15:14:27.312857Z",
"id":"01KCPE0YBDZH4ATPY1M7270DVC",
"name":"FileHub",
"updated_at":"2025-12-17T15:14:27.312857Z"
}
}[!IMPORTANT] Store ID
Fetch the
store.idfrom the output above. This ID is required for configuring the Django environment variables.Store ID:
01KCPE0YBDZH4ATPY1M7270DVCMake sure to copy this ID and replace it to your Django environment variables as
FGA_STORE_ID.
- command:
docker compose -f docker-compose.local.yml run --rm openfga-cli --api-token=key1 model write --store-id=01KCPE0YBDZH4ATPY1M7270DVC --file=data/filehub/first-model.fga- output::
{
"authorization_model_id":"01KCPEZGSQQN3QPP77GST6T129"
}[!IMPORTANT] Model Authorization ID
Fetch the `` from the output above. This ID is required for configuring the Django environment variables.
Model Authorization ID:
01KCPEZGSQQN3QPP77GST6T129Make sure to copy this ID and replace it to your Django environment variables as
FGA_AUTHZ_MODEL_ID.
# .envs/.local/.env.django
FGA_STORE_ID=01KCHFZ97681C91WBDPPY303AT # Replace with the Store ID generated by OpenFGA
FGA_AUTHZ_MODEL_ID=01KCK13Z9V4F5SEBX9J67HJJGF # Replace with the Authorization Model ID generated by OpenFGA
[!IMPORTANT] Restart the docker Container
Need to restart the docker containers since environment variables are changed now.
Refer above Docker Command to stop and start the containers again.
- command:
docker exec -it django-docker python manage.py createsuperuser
# Enter your name, email and password.- Base URL: localhost:8000
POST /api/token/
Content-Type: application/json
{
"username": "john",
"password": "password123"
}
Response:
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGc..."
}POST /api/token/refresh/
Content-Type: application/json
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGc..."
}- Note: All endpoints require Authorization: Bearer <access_token> header.
GET /api/files/
Authorization: Bearer <token>response:
[
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "project-proposal.pdf",
"owner": "user:john",
"created_at": "2025-12-19T10:00:00Z"
}
]POST /api/files/
Authorization: Bearer <token>
Content-Type: application/json
{
"file": "design-mockup.png",
"content": "<base64-encoded-content>"
}response:
{
"uuid": "660e8400-e29b-41d4-a716-446655440001",
"file": "design-mockup.png",
}GET /api/files/{uuid}/
Authorization: Bearer <token>response:
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"file": "project-proposal.pdf"
}
POST /api/files/{uuid}/share/
Authorization: Bearer <token>
Content-Type: application/json
{
"permissions": [
{"user_id": "3", "relation": "editor"}
]
}- Supported relations: owner, editor, viewer response:
{
"detail": "File shared successfully."
}GET /api/files/{uuid}/permissions/
Authorization: Bearer <token>response:
{
"file": "079cd8b8-c2ef-4705-8a24-048d1de70836",
"permissions": {
"owners": [
"user:1"
],
"viewers": [
"user:2"
],
"editors": [
"user:3"
]
}
}GET /api/files/{uuid}/relations/
Authorization: Bearer <token>response:
{
"user": "user:1",
"object": "file:079cd8b8-c2ef-4705-8a24-048d1de70836",
"relations": [
"can_view",
"can_edit",
"owner"
]
}OpenFGA uses a relationship-based model inspired by Google Zanzibar. Here's the authorization model for this project:
model
schema 1.1
type user
type file
relations
define owner: [user]
define viewer: [user]
define editor: [user]
define can_edit: owner or editor
define can_view: viewer or can_edit How it works:
- owner: Full control over the file
- editor: Can read and write (inherits viewer permissions)
- viewer: Can only read (basic access)
- Computed permissions: can_read, can_write, etc. are derived from relations
Traditional RBAC with Postgres
-- Traditional approach: permission checks via database queries
SELECT * FROM user_roles WHERE user_id = 1 AND role = 'admin';
SELECT * FROM file_permissions WHERE file_id = 123 AND user_id = 1;Drawbacks:
- Authorization logic scattered across application code
- Hard to audit and update policies
- Role explosion for complex scenarios
- Tight coupling between auth and business logic
OpenFGA Approach
# Centralized authorization check
response = await fga_client.check(
user="user:john",
relation="can_write",
object="file:550e8400-..."
)
# Returns: {"allowed": true/false}Key Advantages
| Aspect | Traditional Auth | OpenFGA |
|---|---|---|
| Authorization Logic | Scattered in code | Centralized in modelsopenfga |
| Performance | Multiple DB queries | Optimized graph traversal, <1ms for simple checksdev |
| Scalability | Limited by app logic | 1M+ RPS capabilitydev |
| Auditability | Manual logging needed | Complete audit trail built-inopenfga |
| Policy Updates | Requires code deployment | No-code model updatesdev |
| Flexibility | Role explosion issue | Relationship-based (fine-grained)aserto |
| Maintenance | High (custom code) | Low (battle-tested patterns)dev |
- Django REST Framework
- Django REST Framework SimpleJWT
- OpenFGA Configuration Guide
- OpenFGA Python SDK
- OpenFGA CLI Store File Documentation
Contributions are welcome! Please open an issue or submit a pull request.
📄 License MIT License
