Your backend
Django
Read the signed-in user on every request, protect views with a decorator, serve server-rendered pages and receive webhooks in Django.
pip install "zevauth[django]"
This page covers what Django adds. The client, the backend API and the rules behind every refusal are on Python.
Setup
# settings.py
ZEVAUTH = {
"ENVIRONMENT_ID": os.environ["ZEVAUTH_ENVIRONMENT_ID"], # env_live_…
"SECRET_KEY": os.environ["ZEVAUTH_SECRET_KEY"], # for the backend API
}
MIDDLEWARE = [
# …
"zevauth.django.ZevAuthMiddleware",
]
The middleware sets request.zevauth on every request: the signed-in
person, or nobody. It never raises.
Protecting views
from django.http import JsonResponse
from zevauth.django import require_auth
@require_auth
def me(request):
return JsonResponse({"userId": request.zevauth.user_id})
@require_auth(require_mfa=True)
def payouts(request):
...
A refused request answers with the same JSON every ZevAuth library uses:
unauthorized (401), session_task_pending (403, with pendingTasks)
and mfa_required (403). For a page that should send people to your
sign-in screen instead, check request.zevauth.is_signed_in yourself and
redirect.
Who is signed in
def home(request):
auth = request.zevauth
if auth.is_signed_in and auth.has(role="org:admin"):
...
In templates: {% if request.zevauth.is_signed_in %}.
Server-rendered pages
Turn the session bridge on and include its routes:
# settings.py
ZEVAUTH = {
"ENVIRONMENT_ID": "…",
"PUBLISHABLE_KEY": os.environ["ZEVAUTH_PUBLISHABLE_KEY"],
"SESSION_BRIDGE": True,
}
# urls.py
from django.urls import include, path
urlpatterns = [
# …
path("zevauth/", include("zevauth.django.urls")), # /zevauth/session and /zevauth/handshake
]
The middleware then reads the session cookie on every request. When it has expired, a page request is redirected through the handshake, which refreshes it in the browser and returns.
The endpoint is exempt from Django’s CSRF check on purpose: the bridge
stops forged posts itself, with its own required header and an Origin
check. To use other paths, share cookies across subdomains or allow more
origins:
"SESSION_BRIDGE": {
"BRIDGE_PATH": "/auth/session",
"HANDSHAKE_PATH": "/auth/handshake",
"COOKIE_DOMAIN": "example.com",
"ALLOWED_ORIGINS": ["https://app.example.com", "https://www.example.com"],
},
Mounting the routes elsewhere? Set the two paths to match.
Webhooks
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from zevauth import ZevAuthWebhookError, construct_webhook_event
@csrf_exempt
@require_POST
def zevauth_webhook(request):
try:
event = construct_webhook_event(
request.body, # the raw bytes
request.headers.get("zevauth-signature"),
os.environ["ZEVAUTH_WEBHOOK_SECRET"],
)
except ZevAuthWebhookError:
return HttpResponse(status=400)
...
return HttpResponse(status=204)
Backend API
from zevauth.django import get_zevauth
user = get_zevauth().api.users.get(request.zevauth.user_id)
get_zevauth() returns the one client built from settings.ZEVAUTH.
Updated at, Sunday, November 1, 2026