Skip to main content

Add magic authorization links to your django project.

Project description

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).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_magic_authorization-0.1.6.tar.gz (9.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_magic_authorization-0.1.6-py3-none-any.whl (15.4 kB view details)

Uploaded Python 3

File details

Details for the file django_magic_authorization-0.1.6.tar.gz.

File metadata

File hashes

Hashes for django_magic_authorization-0.1.6.tar.gz
Algorithm Hash digest
SHA256 4ef7f16ea99effc4d805301e55e270737ea391493af87395a09ece0885dbd193
MD5 bc19f879016b256465ae793a15016555
BLAKE2b-256 dee7b2be29c4319903f9a591df28dfb88678a2f5a1afe9b4dc009016967310cf

See more details on using hashes here.

File details

Details for the file django_magic_authorization-0.1.6-py3-none-any.whl.

File metadata

File hashes

Hashes for django_magic_authorization-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 650b3202d0e14ee897fb2b7a7d55bce9887152a6e18e115a4550ba0561d0c4db
MD5 c0e582d716046a95776369d8e59cc4a3
BLAKE2b-256 a279a5f1245209e9e6ca535eac8bf9b0d3f96918a699c6808c1d6a1082f9ef56

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page