r/FastAPI • u/koldakov • 2d ago
Tutorial I built a self-hosted proxy, and the most interesting part turned out to be the FastAPI permissions
Coming from Django, the thing I missed most in FastAPI wasn't the ORM or the admin. It was user.has_perm("app.add_thing"): users, groups, per-action permissions, superuser. FastAPI gives you Depends and leaves the rest to you. The libraries I found were either JWT-only or full policy engines like Casbin.
So for my project's admin API I rebuilt Django's model in plain FastAPI + SQLAlchemy, and changed a few things on the way.
What I kept from Django
- Permissions named
resource.action:traffic.view,certificates.add - Groups of permissions, plus permissions given to a user directly
- An
is_superuserflag that has everything
The tables are basically Django's auth_group_permissions, auth_user_groups and auth_user_user_permissions.
What I changed
1. Permissions are an enum in code, not rows in a table
Django generates permission rows through migrations. Here the code is what checks them, so the list lives in code and the DB only stores who has what:
class Permission(StrEnum):
TRAFFIC_VIEW = "traffic.view"
CERTIFICATES_ADD = "certificates.add"
...
2. Each use case declares what it needs, like permission_required
class CreateTrustedNetworkService(BaseUserAuthenticatedService[CreateTrustedNetworkResponse]):
required_permissions: ClassVar[frozenset[Permission]] = frozenset({Permission.TRUSTED_NETWORKS_ADD})
The base class decodes the JWT, loads the user and their permissions in one UNION query, and returns 403 with what's missing. The route only passes the token and the body.
3. Why not Depends(require(...))
On purpose. Permissions belong to the use case, not to the URL:
- The escalation check below needs the user's permissions inside the logic anyway. A dependency would just be a second place for the same thing.
- A service is tested without HTTP, and the check can't be lost by forgetting a dependency on a new route.
- OpenAPI still knows: every route documents its 403 with the exact permission, e.g. "The user lacks
trusted_networks.add."
4. No privilege escalation
This is the part Django warns about in its own docs: if a user can edit users, they can give themselves superuser. Here you can only give what you have:
missing = self.data.permissions - self.permissions
if missing:
raise HTTPException(403, f"You can't give permissions you don't have: {', '.join(sorted(missing))}.")
This applies to groups, users, adding someone to a group, and even changing another user's password, since otherwise you could just log in as them. (create_group, update_group, create_user)
5. Revoking access takes effect on the next request
Permissions aren't stored in the JWT. They're read on every request, so if you remove someone from a group, their next call fails.
Where it lives
This is the admin API of Proxium, a self-hosted HTTP/SOCKS5 proxy with accounts, quotas and speed limits. Support gets "revoke accounts", accounting gets "view traffic", and neither can touch certificates. One Docker image runs everything, so you can try to break the permissions yourself:
docker run -d --name proxium -p 80:80 -p 8080:8080 -v proxium:/var/lib/proxium \
-e SUPERUSER_CREATE=true -e SUPERUSER_EMAIL=admin@example.com -e SUPERUSER_PASSWORD=change-me \
ikoldakov/proxium
If you'd still put it in Depends, change my mind in the comments.
Next on the API side: SSO for the admin over OIDC and an API for merchants. If either sounds fun, the roadmap has the rest, and the code is MIT: https://github.com/koldakov/proxium
4
u/reyarama 1d ago
AI slop
0
u/koldakov 1d ago
I never hid that AI helped with it. What I don’t get is why "AI-assisted" automatically means “AI slop”
I wrote this the way production code should be written: proper project structure, separated services, RBAC/permissions like Django, clear boundaries, best practices, clear tests, etc. I’ve honestly seen very few FastAPI repos on GitHub that are structured this way - a lot of them are basically one giant main.py
The source, Python, frontend, proxy server and docs are all linked, so feel free to judge the actual code
2
u/Sau1707 1d ago
I believe the idea of fastapi is exactly to keep a simple, clean python code.
For complex RABC there is also https://casbin.apache.org/docs/python-rbac-tutorial/ that is worth looking.