skip to content

How do you configure django-allauth so community members sign in with their email address and cannot log in until it is verified?

level: middleimportance: should knowfreq 40%

answer

  1. which identifier logs in
  2. which fields signup asks for
  3. optional is the default
  4. link or code

basics

~10 s

Set ACCOUNT_LOGIN_METHODS = {"email"}, ACCOUNT_SIGNUP_FIELDS = ["email*", "password1*", "password2*"] and ACCOUNT_EMAIL_VERIFICATION = "mandatory"; the default "optional" lets unverified users in. Verification uses an HMAC link, or a code when enabled.

solid answer

~40 s

In current django-allauth, `ACCOUNT_LOGIN_METHODS = {"email"}` makes email the login identifier (the default is `{"username"}`), and `ACCOUNT_SIGNUP_FIELDS = ["email*", "password1*", "password2*"]` asks only for a required email and password; the asterisk marks a field required. `ACCOUNT_EMAIL_VERIFICATION = "mandatory"` then blocks login until the address is verified; the default `"optional"` sends the mail but lets the user in, and `"none"` sends nothing. Mandatory needs `"email*"` in the signup fields. Verification is a stateless HMAC link by default, or a short code with `ACCOUNT_EMAIL_VERIFICATION_BY_CODE_ENABLED = True`. `ACCOUNT_UNIQUE_EMAIL` (default `True`) and `ACCOUNT_PREVENT_ENUMERATION` (default `True`) keep addresses unique without revealing which are taken. Older projects used `ACCOUNT_AUTHENTICATION_METHOD` and `ACCOUNT_EMAIL_REQUIRED`, replaced in 65.4 and 65.5.

code

python · 5 lines
python
from allauth.account.models import EmailAddress


def can_post(user):
    return EmailAddress.objects.filter(user=user, verified=True).exists()

go deeper

for a junior

Remember the three settings: login methods, signup fields, and the verification mode, and that optional is the default.

for a middle

Explain the three verification modes, link versus code, the EmailAddress model, and why mandatory needs a required email field.

for a senior

Discuss enumeration prevention against uniqueness, the verification stage living in the flow rather than the backend, and migrating old setting names.

for a principal

Decide where verification belongs in the funnel: block at login, or gate risky actions, balancing sign-up conversion against abuse.

## The goal A community site wants members to sign up with an email address and a password, sign in with that email, and prove they own the address before they can post. In django-allauth this is three settings plus sensible defaults. ## The settings ```python # settings.py (django-allauth 65.x) ACCOUNT_LOGIN_METHODS = {"email"} ACCOUNT_SIGNUP_FIELDS = ["email*", "password1*", "password2*"] ACCOUNT_EMAIL_VERIFICATION = "mandatory" ACCOUNT_EMAIL_VERIFICATION_BY_CODE_ENABLED = True # optional: code instead of link ``` | Setting | Default | What the value above does | |---|---|---| | `ACCOUNT_LOGIN_METHODS` | `{"username"}` | Sign in with email only; `{"email", "username"}` accepts either | | `ACCOUNT_SIGNUP_FIELDS` | `["username*", "email", "password1*", "password2*"]` | No username field; email required (`*`) | | `ACCOUNT_EMAIL_VERIFICATION` | `"optional"` | Login blocked until verified | | `ACCOUNT_EMAIL_VERIFICATION_BY_CODE_ENABLED` | `False` | Send a code to type in rather than a link | | `ACCOUNT_UNIQUE_EMAIL` | `True` | One account per address | | `ACCOUNT_PREVENT_ENUMERATION` | `True` | Do not reveal whether an address is registered | If the user model has no `username` field at all, also set `ACCOUNT_USER_MODEL_USERNAME_FIELD = None` so allauth stops looking for one. ## What the three verification modes mean 1. **`"mandatory"`**: after signup, and on any later login attempt with an unverified address, the user is sent to a "verify your email" step and is not logged in. It requires `"email*"` in `ACCOUNT_SIGNUP_FIELDS`, since there must be an address to verify. 2. **`"optional"`** (the default): the verification mail is sent, but the user is logged in immediately. Unverified addresses are recorded as such. 3. **`"none"`**: no verification mail at all. allauth tracks addresses in its own **`EmailAddress`** model, one row per address with `verified` and `primary` flags, so a user can hold several addresses and change their primary one without losing verification history. ## Link or code - **Link** (default): the email carries a key. With `ACCOUNT_EMAIL_CONFIRMATION_HMAC = True`, also the default, that key is an HMAC-signed token, so no confirmation row is stored server side; it expires after `ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS` (3). - **Code**: with `ACCOUNT_EMAIL_VERIFICATION_BY_CODE_ENABLED = True` the user types a short code into the open tab. Codes expire (900 seconds by default) and allow a limited number of attempts (3), which suits mobile users who read mail on another device. ## How login by email works underneath `allauth.account.auth_backends.AuthenticationBackend`, a `ModelBackend` subclass listed in `AUTHENTICATION_BACKENDS`, reads `ACCOUNT_LOGIN_METHODS`. With `"email"` enabled it finds users by email address, preferring verified addresses, checks the password with `check_password()`, applies `user_can_authenticate()` and hashes a dummy password when nobody matches. The **verification requirement** is not in the backend: it is a stage in allauth's login flow, which is why a password check can succeed and the user still lands on the verification page. ## Enumeration and uniqueness With `ACCOUNT_PREVENT_ENUMERATION = True` and mandatory verification, signing up with an address that is already registered looks identical to a fresh signup: the page says a verification mail was sent, and the real owner receives a notice instead. The attacker learns nothing. With optional or no verification that trick is not possible, so allauth lets uniqueness win and the signup form reports the conflict; `"strict"` lets the signup through instead, at the cost of duplicate unverified addresses. ## Names you may meet in older code - `ACCOUNT_AUTHENTICATION_METHOD = "email"` was replaced by `ACCOUNT_LOGIN_METHODS` in allauth 65.4.0. - `ACCOUNT_EMAIL_REQUIRED`, `ACCOUNT_USERNAME_REQUIRED` and the "enter twice" flags were folded into `ACCOUNT_SIGNUP_FIELDS` in 65.5.0. Both changes were made backwards compatible, so old settings may still work, but new code and interview answers should use the current names. ## Checking the setup A short checklist before going live: 1. Sign up with a fresh address and confirm that login is refused until the link or code is used. 2. Sign up again with the same address and confirm the page gives nothing away while the owner receives a notice. 3. Log in with a mixed-case version of the address; lookups should still find the account. 4. Confirm the outgoing mail setup actually delivers in production, since mandatory verification turns a mail outage into a signup outage. The last point is the operational cost of `"mandatory"`: the email channel becomes part of the login path, so it deserves monitoring like any other dependency.

  • Why might a site choose optional verification and gate actions instead of mandatory verification?
    Mandatory verification loses users whose mail is delayed or filtered at the first step. With `"optional"` they can browse and set up a profile immediately, while posting or messaging checks `EmailAddress.verified`. The trade-off is that unverified accounts exist and must be cleaned up or rate limited.
  • Where does the mandatory check actually happen, given that the backend authenticated the password?
    In allauth's login flow, not in `AuthenticationBackend`. After the credentials are accepted the flow runs its stages; the email verification stage sees an unverified address under `"mandatory"` and sends the user to the verification step instead of completing `login()`.

saying these in an interview costs you the question

  • ACCOUNT_EMAIL_VERIFICATION defaults to mandatory.
  • ACCOUNT_AUTHENTICATION_METHOD = 'email' is the current way to enable email login.
  • Mandatory verification works even without email in the signup fields.
  • Email confirmation links always need a stored confirmation row per signup.
  • The authentication backend is what blocks unverified users.