Skip to main content

stapel-auth

CI coverage pypi downloads python license llms.txt

User authentication and account access: sign-up and sign-in with email or phone one-time codes, passwords, social (OAuth) accounts, corporate SSO (SAML/OIDC), magic links, QR hand-off and passkeys; guest (anonymous) access; two-factor authentication and step-up verification; session and device management with a security audit trail.

Part of the Stapel framework — composable Django apps that deploy as a monolith or as microservices without changing module code.

Install

pip install stapel-auth

At a glance

Fact Value
Version 0.34.2
Python >=3.11 (3.11, 3.12, 3.13, 3.14)
HTTP operations 122
Config axes 30
Usage surface 18
Extension points 6
Error codes 140
Documented flows 4
Fleet dependencies stapel-core · stapel-gdpr (optional) · stapel-notifications (optional)

Documentation

Flows: English · Русский · Errors: English · Español · Русский · OpenAPI · capabilities.json · llms.txt (for agents)

What this is

Authentication is the part of a product that everyone builds and nobody wants to own: a dozen sign-in methods, each with its own rate limits, lockouts, notification rules and recovery paths, plus the security surface underneath them — sessions, devices, audit, step-up. stapel-auth is that whole area as one installable Django app, with every method behind a settings flag so a product ships only the ones it wants.

The shape to keep in mind: sign-in methods are axes, not forks. Email OTP, phone OTP, password (+ TOTP), OAuth, enterprise SSO, magic link, QR hand-off, passkeys and guest access are each one AUTH_* flag. Turning a flag off unmounts its endpoints and removes it from the capabilities response the frontend reads — so the login screen changes with the setting, not with a frontend release. GET /capabilities/ is the contract for that: availability, placement, interaction and icon per method, plus OTP code length, TTL and resend cooldown, so no client hardcodes a number this module owns.

Quick start

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

STAPEL_AUTH = {
    "AUTH_EMAIL": True,          # email OTP sign-in
    "AUTH_PASSWORD_LOGIN": True,  # password (+ TOTP step-up when enrolled)
    "AUTH_ANONYMOUS": False,      # no guest accounts
}
# urls.py
path("auth/", include("stapel_auth.urls")),
python manage.py migrate

Behind a reverse proxy, tell the framework which header carries the client IP — rate limits, lockouts and the IP in every audit row are keyed on it, and by default only REMOTE_ADDR (i.e. the proxy) is trusted:

# ONLY if the edge overwrites this header on every request
# (nginx: proxy_set_header X-Real-IP $remote_addr)
STAPEL_NETINTEL = {"TRUSTED_PROXY_HEADER": "HTTP_X_REAL_IP"}

manage.py check says so too: stapel_auth.W005 when a proxy is declared but no header is, stapel_auth.W006 when the named header is one proxies usually append to (X-Forwarded-For), where the first element is whatever the caller wrote.

If your frontend posts provider access tokens to POST /oauth/login/ rather than using the redirect flow, pin which OAuth clients may vouch for an identity — a token is a bearer credential for the app it was minted for, so an unpinned endpoint accepts one minted for somebody else's app:

STAPEL_AUTH = {
    "OAUTH_ACCEPTED_AUDIENCES": {
        # a LIST — Google issues one client ID per platform
        "google": ["<web>.apps.googleusercontent.com",
                   "<ios>.apps.googleusercontent.com"],
    },
}

Google, Facebook and GitHub can prove a token's audience; Zoom and the not-yet-implemented providers cannot, so they refuse that endpoint and keep working through the redirect flow. W007/E008/W009/W010 report which case each configured provider is in.

Every configuration axis, its default and the operations it gates are listed in docs/capabilities.json — the same document the table above is generated from, and the one an agent reads before writing code against this module.

Step-up verification

Any endpoint in any module can demand a fresh proof of identity by decorating itself with @requires_verification (from stapel_core.verification). This module registers the factors that satisfy it — otp_email, otp_phone, totp, passkey — and hosts the challenge endpoints.

The factors are interchangeable by design: a challenge names a scope and the factors currently available to that user, and any of them closes it. A client implements the cycle once (403 with a challenge envelope → pick a factor → initiate → complete → repeat the original request) and reuses it for every protected endpoint in the product, forever. The reference walkthrough is the auth.step_up_verification flow.

Sessions, devices and recovery

Sessions are JWT (cookie plus a token pair) with a tracked UserSession per device, so "sign out everywhere" and "revoke this device" are real operations rather than a token TTL. Suspicious sessions (new device, unexpected IP) are detected, notified and revocable from the notification itself.

Authenticator changes — email, phone or TOTP — run through one model and one set of tasks, in two speeds: instant, when the user can prove control of the current authenticator, and delayed, when they cannot. There is no third speed: a code sent to a new address can set a first email or phone, never replace a verified one, so a stolen session cannot quietly move the recovery address out of the owner's reach. The delayed path is the one that matters after a lost phone: it notifies the verified contact on day 1, 7 and 13 and completes on day 14, which gives an attacker who has the inbox but not the device two weeks of loud warnings and the real owner two weeks to cancel.

Enterprise SSO

SAML SP and OIDC RP, configured per organization in the database rather than in settings — a tenant onboards without a deploy. Users provisioned by an org admin land in the auth.first_login flow: the first password login returns a short-lived challenge instead of a session, routing to a forced password change and/or MFA enrolment before anything else is reachable.

Where a signup came from

A registration request may carry an optional attribution object — the advertising click identifier the visitor arrived on (gclid/gbraid/wbraid), when it was captured, and the campaign tags — and it is stored once against the new account. Reporting a conversion from the browser only counts while the ad platform can still tie the session the event fired in to the session the click landed in, and in a sign-up that goes through a webmail tab, an OAuth provider, or half an hour of thinking time, that tie is broken on the normal path. Holding the identifier server-side is what makes offline conversion import possible — including for the conversion the browser never sees, the account that starts paying weeks later.

Nothing is collected here on its own: no object sent means no row. Read it back with the auth.signup_attribution comm Function; switch the whole thing off with AUTH_SIGNUP_ATTRIBUTION.

Bus events

Emitted through stapel_core.comm (transactional outbox — the event leaves if and only if your transaction commits):

Event Payload When
user.session_created schema A user authenticated and a session was created
user.session_revoked schema A session was revoked (logout or admin action)

Extension points

Providers, models and policies are replaced by dotted path, never by fork — additional OAuth providers, a custom re-registration model, serializer and permission seams. MODULE.md is the full agent-facing map; docs/capabilities.json carries the machine-readable list.

Development

pip install -e . && pip install pytest pytest-django pytest-cov ruff
./setup-hooks.sh
pytest tests/

License

MIT — see LICENSE.


This page is assembled by stapel-readme from docs/readme.md plus the contract artifacts in docs/. Edit the prose in docs/readme.md; the badges, facts and links above and below it are generated — do not hand-edit README.md.

Download files

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

Source Distribution

stapel_auth-0.34.2.tar.gz (548.7 kB view details)

Uploaded Source

Built Distribution

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

stapel_auth-0.34.2-py3-none-any.whl (368.2 kB view details)

Uploaded Python 3

File details

Details for the file stapel_auth-0.34.2.tar.gz.

File metadata

  • Download URL: stapel_auth-0.34.2.tar.gz
  • Upload date:
  • Size: 548.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for stapel_auth-0.34.2.tar.gz
Algorithm Hash digest
SHA256 c9caedea877fd8a47cec81b6475dd3b68d44b66ee00134bf7585c19d363bd4a3
MD5 f70801903f5312997e2d6f8bb44d2d47
BLAKE2b-256 4d45c561575b48d3ea29dcb33e8490c1311a1c2a86ab02104cc4220df7f82d41

See more details on using hashes here.

Provenance

The following attestation bundles were made for stapel_auth-0.34.2.tar.gz:

Publisher: publish.yml on usestapel/stapel-auth

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stapel_auth-0.34.2-py3-none-any.whl.

File metadata

  • Download URL: stapel_auth-0.34.2-py3-none-any.whl
  • Upload date:
  • Size: 368.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for stapel_auth-0.34.2-py3-none-any.whl
Algorithm Hash digest
SHA256 50c72e7c8a52c632704079fdd76d69d3ab2d9ab2f470ba090b24bfe58d500404
MD5 b8b16a87edd9e2ee05c3cd4061939c83
BLAKE2b-256 651f59fa03c7ad2e297b0e066d87be63289973c880278a4a98a3b82219db4bc3

See more details on using hashes here.

Provenance

The following attestation bundles were made for stapel_auth-0.34.2-py3-none-any.whl:

Publisher: publish.yml on usestapel/stapel-auth

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.35.1

2 files

0.34.3

2 files

This release

0.34.2 This release

2 files

0.34.1

2 files

0.34.0

2 files

0.33.1

2 files

0.33.0

2 files

0.32.2

2 files

0.32.1

2 files

0.31.1

2 files

0.31.0

2 files

0.30.0

2 files

0.29.0

2 files

0.28.1

2 files

0.27.1

2 files

0.27.0

2 files

0.26.0

2 files

0.25.2

2 files

0.25.1

2 files

0.25.0

2 files

0.24.1

2 files

0.24.0

2 files

0.23.0

2 files

0.22.1

2 files

0.21.1

2 files

0.21.0

2 files

0.20.2

2 files

0.20.1

2 files

0.19.2

2 files

0.19.1

2 files

0.19.0

2 files

0.18.0

2 files

0.17.0

2 files

0.16.0

2 files

0.15.0

2 files

0.14.6

2 files

0.14.5

2 files

0.14.4

2 files

0.14.3

2 files

0.14.2

2 files

0.14.1

2 files

0.14.0

2 files

0.13.0

2 files

0.12.1

2 files

0.12.0

2 files

0.11.0

2 files

0.10.1

2 files

0.10.0

2 files

0.7.5

2 files

0.7.4

2 files

0.7.3

2 files

0.7.0

2 files

0.6.0

2 files

0.5.9

2 files

0.5.7

2 files

0.5.6

2 files

0.5.4

2 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