Accounts App
The Accounts app is the authentication backbone of every Django-CFG project — passwordless login, tokens, social auth, and abuse protection, zero boilerplate.
Full Stack Picture
What’s Included
| Feature | Description |
|---|---|
| OTP Login | Passwordless email — 4-digit codes, 10-min expiry |
| JWT Tokens | Access + refresh with rotation and blacklist |
| 2FA (TOTP) | Google Auth, Authy, any TOTP app |
| OAuth | GitHub social login |
| Brute-force protection | 4-layer defense — IP rate limits, per-email throttle, lockout |
| Email validation | RFC syntax check + normalization (no blocklist, no MX/DNS lookups) |
| Soft delete | GDPR-safe account archive |
| Cleanup jobs | RQ tasks for expired OTPs and JWT blacklist |
Extension signals
The accounts app fires Django signals at key lifecycle points so your product can hook in without patching the app:
| Signal | Fired | Provides |
|---|---|---|
user_authenticated | On a successful login | user, request |
user_email_verified | Whenever an email is proven — every OTP verify, and every OAuth login (the provider email is already verified) | user, consent (a dict on the OTP path, None on OAuth) — see OTP consent |
user_soft_deleted | Synchronously, inside the account-deletion transaction, after the user is soft-deleted | user |
user_soft_deleted exists because a soft delete does not run the database’s
on_delete handlers. Use it to apply your own deletion policy for records that
would otherwise only be cleaned up on a hard delete (e.g. revoking credentials or
purging personal product data). Because it runs inside the deletion
transaction, a receiver that raises rolls the whole deletion back, leaving the
account usable rather than half-deleted:
from django.dispatch import receiver
from django_cfg.apps.system.accounts.signals import user_soft_deleted
@receiver(user_soft_deleted)
def purge_product_data(sender, user, **kwargs):
# Raise on failure — the account deletion is rolled back if this fails.
MyRecord.objects.filter(owner=user).delete()user_email_verified is the hook to enroll a newsletter/marketing subscription
on sign-up. It fires for both login paths — OTP verify and OAuth (GitHub’s
primary email arrives already verified) — so a receiver connected to it covers
every way a user reaches your service. The framework stores no subscription and
sends no consent of its own on the OAuth path (consent=None); your receiver
decides what proof it requires and where the subscription lives:
from django.dispatch import receiver
from django_cfg.apps.system.accounts.signals import user_email_verified
@receiver(user_email_verified)
def enroll_newsletter(sender, user, consent=None, **kwargs):
# Treat any verified login as the opt-in, or gate on `consent["marketing_consent"]`
# from the OTP flow — your product's policy, not the framework's.
subscribe(user.email)Enable
from django_cfg import DjangoConfig, JWTConfig
class MyConfig(DjangoConfig):
enable_accounts = True
jwt = JWTConfig() # secure defaults: 30-min access, 90-day refresh, rotation onAuthLayout, useAuth / useAuthForm hooks, middleware
OTP & Brute-ForceAuth flow, throttle layers, anti-enumeration
JWTToken lifetimes, rotation, blacklist
Two-Factor AuthTOTP setup, enforcement, backup codes
OAuth (GitHub)Social login, account linking, CSRF protection
TAGS: accounts, otp, jwt, 2fa, oauth, authentication DEPENDS_ON: [frontend, otp, jwt, two-factor, oauth]