Dj-rest-auth + allauth not sending email
Context: I'm setting DRF + dj-rest-auth + allauth + simple-jwt for user authentication.
Desired behaviour: Register with no username, only email. Authorize login only if email is verified with a link sent to email. Social login to be added.
Problem: It seems that confirmation email is not being sent. When I run the following test I see that it wanted to send some email but it's not found anywhere.
Test code:
client = APIClient()
url = reverse("rest_register") # dj-rest-auth register endpoint
# Register a user
data = {
"email": "user1@example.com",
"password1": "StrongPass123!",
"password2": "StrongPass123!",
}
response = client.post(url, data, format="json")
assert response.status_code == 201, response.data
print(response.data)
# Manually verify the user
from allauth.account.models import EmailConfirmation
user = User.objects.get(email="user1@example.com")
from django.core import mail
print(f'Amount of sent emails: {len(mail.outbox)}')
print(f'Email Confimation exists: {EmailConfirmation.objects.filter(email_address__email=user.email).exists()}')
This prints:
{'detail': 'Verification e-mail sent.'}
Amount of sent emails: 0
Email Confimation exists: False
My code:
core/urls.py
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('api/auth/', include('authentication.urls')),
path("admin/", admin.site.urls),
path("accounts/", include("allauth.urls")),
]
authentication/urls.py
from dj_rest_auth.jwt_auth import get_refresh_view
from dj_rest_auth.registration.views import RegisterView, VerifyEmailView
from dj_rest_auth.views import LoginView, LogoutView, UserDetailsView
from django.urls import path
from rest_framework_simplejwt.views import TokenVerifyView
urlpatterns = [
path("register/", RegisterView.as_view(), name="rest_register"),
path("register/verify-email/", VerifyEmailView.as_view(), name="account_email_verification_sent"), # Docs says I must add if email confirmation is mandatory
path("login/", LoginView.as_view(), name="rest_login"),
path("logout/", LogoutView.as_view(), name="rest_logout"),
path("user/", UserDetailsView.as_view(), name="rest_user_details"),
path("token/verify/", TokenVerifyView.as_view(), name="token_verify"),
path("token/refresh/", get_refresh_view().as_view(), name="token_refresh"),
]
Custom User model:
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
from django.db import models
class UserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError("The Email field must be set")
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password=None, **extra_fields):
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)
return self.create_user(email, password, **extra_fields)
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
is_active = models.BooleanField(default=True) # Some SO question fixed this setting is_active default value to True
is_staff = models.BooleanField(default=False)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
objects = UserManager()
Relevant settings:
SITE_ID = 1
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
"django.contrib.sites",
'django.contrib.staticfiles',
# Django REST Framework
"rest_framework",
"rest_framework.authtoken",
# Simple JWT
"rest_framework_simplejwt",
# Allauth
"allauth",
"allauth.account",
"allauth.socialaccount",
# dj-rest-auth
"dj_rest_auth",
"dj_rest_auth.registration",
# CORS
"corsheaders",
# Local apps
"authentication.apps.AuthenticationConfig",
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# Allauth
'allauth.account.middleware.AccountMiddleware',
# CORS
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
]
ROOT_URLCONF = 'core.urls'
EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" # for testing
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
AUTH_USER_MODEL = "authentication.User"
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
"REFRESH_TOKEN_LIFETIME": timedelta(days=7),
"ROTATE_REFRESH_TOKENS": False,
"BLACKLIST_AFTER_ROTATION": False,
"UPDATE_LAST_LOGIN": True,
"SIGNING_KEY": "complexsigningkey", # generate a key and replace me
"ALGORITHM": "HS512",
}
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework_simplejwt.authentication.JWTAuthentication",
]
}
# Dj-rest-auth settings
REST_AUTH = {
"USE_JWT": True,
"JWT_AUTH_HTTPONLY": False,
"REGISTER_SERIALIZER": "authentication.serializers.RegisterSerializer"
}
# Allauth settings
ACCOUNT_SIGNUP_FIELDS = ['email*', 'password1*', 'password2*']
ACCOUNT_LOGIN_METHODS = {'email'}
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_AUTHENTICATION_METHOD = "email"
ACCOUNT_EMAIL_CONFIRMATION_HMAC = True # This is the default. But just to be explicit.
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
]