django-altcha-widget
A Django form field and widget for ALTCHA, the privacy-friendly proof-of-work CAPTCHA.
It runs fully self-hosted: the ALTCHA JavaScript is vendored into the
package, and the challenge is generated by your own server. No request ever
reaches an external service, and there is no npm install, no bundler
configuration and no CDN. It is secure by default, with built-in
protection against replay attacks ensuring each challenge is only ever
validated once, and it works under a strict Content-Security-Policy — no
'unsafe-inline' styles, no blob: workers — with nothing to configure.
Requires Python 3.12+ and Django 6.0+.
Contents
- Installation
- Usage
- Configuration Options
- Content Security Policy (CSP)
- Replay Attack Protection
- Settings
- Logging
- Contributing
- License
Installation
-
Install the package:
pip install django-altcha-widget
-
Add to
INSTALLED_APPSin your project'ssettings.py:INSTALLED_APPS = [ # Other installed apps "django_altcha_widget", ]
-
Set your secret HMAC key, used to sign ALTCHA challenges. Treat it like a password:
ALTCHA_HMAC_KEY = "5f4dcc3b5aa765d61d8327deb882cf992b95990a9151374abd8ff8c5a7a0fe08"
-
Collect the static files, as you would for any Django app:
python manage.py collectstatic
That is the whole installation. The ALTCHA JavaScript is vendored in the package, so there is no npm dependency, no bundler entry point to edit and no CDN.
Usage
Add the field to your form:
from django import forms
from django_altcha_widget import AltchaField
class MyForm(forms.Form):
captcha = AltchaField()
There is nothing to add to your template: the widget emits the stylesheet and
script tags itself, so a plain {{ form }} is enough. The same tags are also
exposed through Django's
form media, so a
base template already rendering {{ form.media }} gets a second, harmless copy
rather than moving them into the <head>.
Configuration Options
AltchaField accepts the options documented in
Altcha's widget integration guide:
from django import forms
from django_altcha_widget import AltchaField
class MyForm(forms.Form):
captcha = AltchaField(
display="floating", # Enables floating behavior
debug=True, # Enables debug mode (for development)
# Additional options supported by Altcha
)
The options ALTCHA takes as HTML attributes — auto, challenge,
configuration, display, language, theme, type and workers — are
rendered as attributes of the <altcha-widget> element. Every other option is
collected into the JSON-encoded configuration attribute.
Two arguments are refused outright:
name, with aTypeError. It is the name the CAPTCHA value is submitted under, so it always comes from the form field itself.required=False, with aValueError, rather than being honoured or silently ignored: anything submitting the form without the field would pass unchallenged, so an optional CAPTCHA is not a weaker one but no CAPTCHA at all. Leave the field out of the forms that do not need one, or setALTCHA_VERIFICATION_ENABLEDtoFalseto stop verifying.
Register a URL to provide the challenge
By default the challenge is generated by the AltchaField and embedded in the
rendered HTML as JSON, using the challenge option. That same option also
accepts a URL for the widget's JavaScript to fetch instead.
django_altcha_widget ships a ready-to-use view for that. Register it:
from django.urls import path
from django_altcha_widget import AltchaChallengeView
urlpatterns += [
path("altcha/challenge/", AltchaChallengeView.as_view(), name="altcha_challenge"),
]
and point the field at it:
from django.urls import reverse_lazy
from django import forms
from django_altcha_widget import AltchaField
class MyForm(forms.Form):
captcha = AltchaField(
challenge=reverse_lazy("altcha_challenge"),
)
Fetching the challenge also keeps it out of the form's HTML, which is what makes
it safe to serve that page from a cache — see
Replay Attack Protection. The view is served
no-store, so the challenge itself is never cached.
Content Security Policy (CSP)
A strict CSP works out of the box. There is nothing to enable:
Content-Security-Policy: script-src 'self'; style-src 'self'; worker-src 'self'
No 'unsafe-inline', no blob:, no third-party origin to allowlist: everything
the widget loads is served from your own static files.
Replay Attack Protection
django-altcha-widget automatically protects against replay attacks: a
challenge that validates is claimed in a cache, and any later attempt to reuse
it is rejected. The claim is a single atomic cache operation, so submitting one
payload many times at once does not let any copy slip through, and it is held
for as long as the challenge keeps verifying — including challenges issued with
an expiry of their own through AltchaChallengeView.
This is enabled by default and needs no configuration for single-process deployments.
Settings
ALTCHA_HMAC_KEY
Required. The key used to HMAC-sign ALTCHA challenges; it must be kept
secret. Forging a challenge is exactly as hard as guessing it, so keys shorter
than 32 characters are rejected with an ImproperlyConfigured error.
ALTCHA_CACHE_ALIAS
Django cache alias used for replay attack protection. Defaults to "default",
which needs no further configuration if that cache is already a shared backend
(Redis, Memcached, database).
To use a dedicated cache, define one and point to it:
CACHES = {
"altcha": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
}
}
ALTCHA_CACHE_ALIAS = "altcha"
Django's database cache
is a simple alternative if you would rather not run Redis or Memcached — use
"BACKEND": "django.core.cache.backends.db.DatabaseCache" with the table name
as LOCATION, then create it with python manage.py createcachetable.
ALTCHA_CHALLENGE_EXPIRE
Challenge expiration duration in milliseconds. Defaults to 20 minutes as per Altcha security recommendations.
ALTCHA_ALGORITHM
Key derivation function used for the Proof-of-Work challenges. Defaults to
"PBKDF2/SHA-256". Supported values are "PBKDF2/SHA-256",
"PBKDF2/SHA-384", "PBKDF2/SHA-512", "SHA-256", "SHA-384", "SHA-512",
"ARGON2ID" and "SCRYPT". See
Altcha's Proof-of-Work documentation
for the trade-offs between them.
ALTCHA_COST
Algorithm-specific cost: the number of iterations for PBKDF2 and SHA, the
time cost for ARGON2ID and SCRYPT. Defaults to 5000, the value recommended
upstream for PBKDF2/SHA-256.
ALTCHA_TRANSLATIONS
The Altcha translations
to load, as a language code or "all" for the combined bundle covering every
language. Defaults to None, which loads none and leaves the widget in English.
Serving a single language is much lighter than the combined bundle — 1.4 KB gzipped instead of 18.2 KB:
ALTCHA_TRANSLATIONS = "fr-fr"
Every per-language file ALTCHA ships is vendored, along with the combined
"all" bundle, so any of those works without installing anything. A trailing
.js is tolerated: "fr-fr" and "fr-fr.js" name the same file.
The four regional bundles ALTCHA also publishes — "africa", "americas",
"asia" and "europe" — are not vendored. Naming one loads nothing under a
plain static files storage, and raises ValueError: Missing staticfiles manifest entry under ManifestStaticFilesStorage. Use "all" or a language code
instead.
ALTCHA_VERIFICATION_ENABLED
Set to False to skip Altcha validation altogether. Defaults to True.
Logging
Logs are emitted through the standard Python logging module under the logger
name django_altcha_widget. Nothing is logged under normal operation; logging
fires only on validation failures and misconfiguration:
- WARNING on invalid or missing CAPTCHA tokens submitted to a form.
- WARNING on replay attempts (a challenge reused after it has already been validated).
- ERROR when
ALTCHA_HMAC_KEYis not configured. - Exception with traceback when verification or payload decoding raises unexpectedly.
Payloads, challenge values, and the HMAC key are never included in log messages.
To see them, add the logger to your project's LOGGING setting:
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"console": {
"class": "logging.StreamHandler",
},
},
"loggers": {
"django_altcha_widget": {
"handlers": ["console"],
"level": "WARNING",
},
},
}
Set "level": "ERROR" to see only misconfiguration and unexpected failures,
or "level": "DEBUG" to see additional diagnostic messages during development.
Contributing
Issues and pull requests are welcome. See
DEVNOTES.md
for how to set up a development environment and how the test matrix, the
vendored assets and the release pipeline work. Please run just check and
just test before opening a pull request.
License
This project is licensed under the MIT License. See the LICENSE file for details.
It began as a fork of django-altcha, Copyright (c) nexB Inc. and others, also MIT licensed. It is published as a separate package and shares no release history with it; installing both in the same environment is not supported.
The ALTCHA JavaScript library is Copyright (c) 2023-2026 Daniel Regeci, BAU
Software s.r.o., MIT licensed. It is vendored in this package and redistributed
under that license, whose text travels with it at
src/django_altcha_widget/static/django_altcha_widget/altcha/LICENSE.txt. The
exact version and provenance are recorded alongside it in VENDOR.json.
Metadata
Release files for django-altcha-widget 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| django_altcha_widget-1.0.0.tar.gz | 168.5 kB | With provenance | Details for django_altcha_widget-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| django_altcha_widget-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for django_altcha_widget-1.0.0-py3-none-any.whl |
Total release size: 392.9 kB
Release files / django_altcha_widget-1.0.0.tar.gz
| Download URL | django_altcha_widget-1.0.0.tar.gz |
|---|---|
| Size | 168.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
207bb369db16273d4848ce463dc8bc9fd71349e5d077d58c3aa634328d0afce7
|
|
BLAKE2b-256 checksum How to use checksums |
9da8c0576a12b46786ad82538d8ec9f3e0190bb277a8a78eeef85436e83e6f33
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logRelease files / django_altcha_widget-1.0.0-py3-none-any.whl
| Download URL | django_altcha_widget-1.0.0-py3-none-any.whl |
|---|---|
| Size | 224.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f21668741903aa67bd5e8783f3b741cb05fd6a9b2fff50ff97e6460955ed2a26
|
|
BLAKE2b-256 checksum How to use checksums |
0a312d2f933b605f2f2317d94796102287b873cde467f897269879bc09c20e8b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logSecurity for django-altcha-widget 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for django_altcha_widget-1.0.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.