Skip to main content

Django Magic Authorization

Token-based authorization middleware for protecting Django URL paths.

Installation

uv add django-magic-authorization

or

pip install django-magic-authorization

Setup

Add the app and middleware to your Django settings:

# settings.py
INSTALLED_APPS = [
    ...,
    "django_magic_authorization",
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django_magic_authorization.middleware.MagicAuthorizationMiddleware",
    ...,
]

Place MagicAuthorizationMiddleware after SecurityMiddleware.

Run migrations:

python manage.py migrate

Quick start

Mark any URL as protected using protected_path, a drop-in replacement for django.urls.path:

# urls.py
from django.http import HttpResponse
from django_magic_authorization.urls import protected_path

def secret_view(request):
    return HttpResponse("Secret content")

urlpatterns = [
    protected_path("secret/", secret_view),
]

Accessing /secret/ without a token returns 403. Create a token to grant access:

from django_magic_authorization.models import AccessToken

token = AccessToken.objects.create(path="secret/", description="Demo token")
print(token.token)
# e.g. "aB3x..."

Visit /secret/?token=aB3x... to authenticate. The token is stripped from the URL via redirect, and a cookie is set for subsequent requests.

Dynamic URL patterns

protected_path supports the same route syntax as django.urls.path, including captured parameters:

urlpatterns = [
    protected_path("blog/<int:year>/<str:slug>/", blog_detail_view),
]

A token with path="blog/<int:year>/<str:slug>/" will protect all URLs matching that pattern.

Nested paths with include()

Use protected_path with include() to protect an entire URL subtree:

from django.urls import include
from django_magic_authorization.urls import protected_path

urlpatterns = [
    protected_path("internal/", include("internal.urls")),
]

All paths under /internal/ are protected with a single token.

Conditional protection (protect_fn)

Pass a protect_fn callable to protect URLs conditionally based on captured parameters. It receives the captured kwargs dict and should return True if the path should be protected:

urlpatterns = [
    protected_path(
        "<str:visibility>/<int:pk>/",
        detail_view,
        protect_fn=lambda kwargs: kwargs["visibility"] == "private",
    ),
]

Here /private/42/ requires a token, but /public/42/ does not.

Token management

Creation

from django_magic_authorization.models import AccessToken

token = AccessToken.objects.create(
    path="secret/",
    description="For reviewers",
)

Tokens are generated using secrets.token_urlsafe(32).

Revocation

token.is_valid = False
token.save()

Expiration

from django.utils import timezone
from datetime import timedelta

AccessToken.objects.create(
    path="secret/",
    description="Expires in 7 days",
    expires_at=timezone.now() + timedelta(days=7),
)

Usage limits

AccessToken.objects.create(
    path="secret/",
    description="Single use",
    max_uses=1,
)

Access stats

Each token tracks times_accessed and last_accessed automatically.

Cleanup command

Remove expired and exhausted tokens:

python manage.py cleanup_expired_tokens

Cookie behavior

On first valid token access, a cookie is set so subsequent requests to the same path do not require the token in the URL. Cookies are scoped to the static prefix of the protected path pattern (everything before the first dynamic segment). Cookie attributes are configurable via settings.

Custom 403 responses

Override the default 403 response with a template or a handler function.

Template:

MAGIC_AUTHORIZATION = {
    "FORBIDDEN_TEMPLATE": "errors/403.html",
}

The template receives path in its context.

Handler function:

MAGIC_AUTHORIZATION = {
    "FORBIDDEN_HANDLER": "myapp.views.custom_forbidden",
}

The handler is called as handler(request, path) and must return an HttpResponse.

Signals

Two signals are available for monitoring access:

access_granted -- sent after successful token validation.

from django_magic_authorization.signals import access_granted

def on_access_granted(sender, request, token, path, **kwargs):
    ...

access_granted.connect(on_access_granted)

Keyword arguments: sender (AccessToken class), request, token (AccessToken instance), path.

access_denied -- sent when access is denied.

from django_magic_authorization.signals import access_denied

def on_access_denied(sender, request, path, reason, **kwargs):
    ...

access_denied.connect(on_access_denied)

Keyword arguments: sender (None), request, path, reason ("no_token" or "invalid_token").

Admin interface

Register the app to get a management interface for access tokens. The admin displays all token fields, provides a dropdown of registered protected paths when creating tokens, and shows a computed access link for each token. Tokens with paths that no longer match a registered route are flagged in the list view.

Settings

All settings are namespaced under MAGIC_AUTHORIZATION in your Django settings:

MAGIC_AUTHORIZATION = {
    "COOKIE_SECURE": True,
    "COOKIE_MAX_AGE": 86400,
}
Key Default Description
COOKIE_SECURE not DEBUG Set the Secure flag on auth cookies
COOKIE_MAX_AGE 31536000 (1 year) Cookie max age in seconds
COOKIE_SAMESITE "lax" Cookie SameSite attribute
COOKIE_HTTPONLY True Set the HttpOnly flag on auth cookies
COOKIE_PREFIX "django_magic_authorization_" Prefix for cookie names
TOKEN_PARAM "token" Query parameter name for the token
FORBIDDEN_TEMPLATE None Template path for custom 403 page
FORBIDDEN_HANDLER None Dotted path to a custom 403 handler function

Security

  • Use HTTPS in production. COOKIE_SECURE defaults to True when DEBUG is False.
  • Tokens are automatically stripped from URLs via redirect after first use, preventing token leakage in browser history and referrer headers.
  • Auth cookies are HttpOnly and SameSite=lax by default.
  • Tokens are generated with secrets.token_urlsafe(32) (256 bits of entropy).

Metadata

Release files for django-magic-authorization 0.1.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-magic-authorization 0.1.6
File Size Uploaded
django_magic_authorization-0.1.6.tar.gz 9.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-magic-authorization 0.1.6
File Interpreter ABI Platform
django_magic_authorization-0.1.6-py3-none-any.whl Python 3 none any Details

Total release size: 25.3 kB

Release files / django_magic_authorization-0.1.6.tar.gz

Download URL django_magic_authorization-0.1.6.tar.gz
Size 9.9 kB
Tags Source
SHA-256 checksum
How to use checksums
4ef7f16ea99effc4d805301e55e270737ea391493af87395a09ece0885dbd193
BLAKE2b-256 checksum
How to use checksums
dee7b2be29c4319903f9a591df28dfb88678a2f5a1afe9b4dc009016967310cf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.2

Release files / django_magic_authorization-0.1.6-py3-none-any.whl

Download URL django_magic_authorization-0.1.6-py3-none-any.whl
Size 15.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
650b3202d0e14ee897fb2b7a7d55bce9887152a6e18e115a4550ba0561d0c4db
BLAKE2b-256 checksum
How to use checksums
a279a5f1245209e9e6ca535eac8bf9b0d3f96918a699c6808c1d6a1082f9ef56
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.2

Release history Release notifications | RSS feed

This release

0.1.6 This release

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page