- ⚠️ Shared email addresses between Django superusers and regular users are allowed by default and can lead to severe security risks.
- 🛠️ Only usernames are unique by default in Django; emails must be manually set as unique to prevent login conflicts.
- 🔐 Custom user models using email as the
USERNAME_FIELDsignificantly reduce authentication errors and improve audit trails. - 🧪 Testing user creation and authentication logic protects against unusual situations introduced by shared fields or account duplication.
- 🚨 Misconfigured superuser permissions may prevent access to the Django admin site or create hidden vulnerabilities.
When building authentication systems in Django, one of the earliest and most important design decisions will involve how you identify users: by email, username, or both. Mistakes in handling email and username uniqueness—especially when telling apart regular users from superusers—can lead to broken login flows and security risks. Here is how Django handles user uniqueness by default, what that means for your superusers, and how to prepare your authentication system for the future.
Understanding Django’s Default User Model
Django includes a built-in user model located in django.contrib.auth.models.User. This default model works for most basic authentication needs for small to mid-sized projects. It includes standard fields such as:
username(unique by default)email(not unique unless explicitly set)passwordis_staffis_superuserdate_joined
By Django's default behavior, the username is used as the unique identifier. It's defined in the model as:
USERNAME_FIELD = 'username'
This means during login, authentication, and user queries, Django treats the username as the required unique field. The email field, although collected during registration or superuser creation, is not required to be unique and may be shared across multiple accounts.
So, unless you explicitly define the email field with unique=True, Django won’t raise errors if multiple users (including superusers) share the same email.
Creating a Superuser with Default User Model
When you run:
python manage.py createsuperuser
Django prompts for username, email, and password. It still uses the username as the primary unique identifier, so a duplicate username will cause an error. However, entering an email that’s already associated with another user will not raise a conflict unless uniqueness is enforced in your model definition.
This default behavior is important to understand if your login system plans to use emails for authentication.
Can Regular Users and Superusers Share the Same Email or Username?
Let’s explain what Django’s defaults allow and what they block:
Username Field
- ✅ Must be unique (Django enforces this).
- ❌ Cannot be shared between any two users—regular or superuser.
Email Field
- ⚠️ Not unique by default.
- ✅ Can be shared between regular users.
- ✅ Can be shared between a regular user and a superuser.
This means it's technically possible—and can cause problems—for two different users to share the same email address.
For example, a customer and their account manager (a staff user) could both have email="user@example.com" in the system. Unless handled carefully, this overlap can cause many problems, from small bugs to serious data leaks.
Why Unique Email/Username Matters
Using email as the unique identifier is a common practice in the industry, especially in modern authentication workflows that rely on email-only sign-ups or Single Sign-On (SSO) systems.
Here’s why uniqueness in email (or any identifier) is very important:
🔁 Login Conflicts
If you change USERNAME_FIELD to 'email', Django's authentication backend assumes every email is unique. If multiple accounts share the same email, the authentication process becomes unclear. Django won’t know which account to authenticate.
🔐 Security Risks
Unique identifiers form the main part of access control. If superusers and regular users share emails:
- Security logs may wrongly assign actions to the wrong person.
- Admin panels might reveal information intended for only one user.
- Email notifications (e.g., password resets, 2FA codes) could be sent to the wrong place.
It becomes impossible to check logs and fix problems if identities aren’t kept separate.
🔄 Broken Workflows
User-centric features like:
- Email verifications
- Password reset links
- Notification preferences
…all assume email uniqueness. Since Django sends links and tokens based on lookups to the email field, duplicate emails may result in unexpected results or hidden failures.
😕 End User Confusion
If two users share an email and get sent a password reset or a transactional email, who is it for? This could result in:
- Users seeing another’s data.
- Unintended access being granted via email-based actions.
- Decreasing user trust in your platform’s safety.
Customizing Django’s User Model for Email Uniqueness
One of the best ways to solve this problem is using a custom user model. This allows you to control:
- Which fields are required
- What the unique identifier is
- How users authenticate
Django provides two base classes that you can extend:
AbstractUser: includes the full Django User model (username, date joined, etc.)AbstractBaseUser: minimal user implementation, which gives you full control
Example: Custom User Model with Email as Identifier
Here's a complete example using AbstractBaseUser and making email the unique, required identifier:
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
from django.db import models
class CustomUserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError('The Email field is required')
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)
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True')
return self.create_user(email, password, **extra_fields)
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
date_joined = models.DateTimeField(auto_now_add=True)
objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
Important Configuration Changes
After defining your custom model, declare it as your default user model in settings.py:
AUTH_USER_MODEL = 'yourapp.CustomUser'
⚠️ Do this before your first migration. Switching user models after the first migration is very hard and can result in data loss and broken features.
Managing Superuser Creation with Custom User Models
When USERNAME_FIELD='email', the createsuperuser command will automatically ask for the email as the login credential.
Don’t Forget:
- Set both
is_staffandis_superusertoTrue. - Include proper validation logic in your
create_superuser()method. - Double-check that the
emailfield hasunique=True.
Example superuser creation command using the new model:
python manage.py createsuperuser --email admin@example.com
This approach ensures your django superuser is created with clear and checkable login details.
Common Pitfalls with Custom User Models
Going custom gives you more flexibility—but also more complexity.
🔁 Changing User Model Mid-Project
You cannot safely switch to a custom user model after making your first migration. Doing so makes schema changes difficult, especially since many core models reference the user through foreign keys.
🔒 Incorrect Superuser Permissions
New developers sometimes forget to assign is_staff=True while creating a superuser. This makes it impossible to log into the Django admin interface—even with the correct credentials.
🛠️ Admin Panel Misconfiguration
To make Django admin work with your custom user model, you’ll need to connect it clearly:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser
class CustomUserAdmin(UserAdmin):
model = CustomUser
list_display = ['email', 'is_staff', 'is_active']
ordering = ('email',)
search_fields = ('email',)
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Permissions', {'fields': ('is_staff', 'is_superuser', 'groups', 'user_permissions')}),
('Important dates', {'fields': ('last_login',)}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('email', 'password1', 'password2', 'is_staff', 'is_active')}
),
)
admin.site.register(CustomUser, CustomUserAdmin)
Best Practices for Django User Management
A good setup now helps prevent bugs later:
- ✅ Always enforce
unique=Trueon important identifiers like email. - 📝 Declare
AUTH_USER_MODELbefore your first migration. - 🔐 Never share login credentials between user roles (regular vs. superuser).
- 🔄 Assign permissions through
Groupsand built-in permission models rather than making userssuperuserunnecessarily. - 🧪 Write tests for user creation, authentication, and unusual situations.
Security Considerations
Shared emails are not just a design problem—they're a security risk.
🚪 Login Spoofing
If login is based on email, a user may unintentionally log into another user's account with shared email data.
📜 Confused Audit Trails
Time logs, access records, and system audits may assign one developer’s actions to a customer if emails aren’t unique.
⚠️ Admin Access Risks
Superusers with duplicate emails may gain unauthorized access to customer data, as Django’s admin system may not handle overlapping credential use consistently.
Enforce unique emails to prevent this confusion and keep roles clean and easy to check.
Granting Admin Access the Right Way
Instead of creating multiple accounts or duplicating user roles, control admin access properly:
- Enable admin access with
is_staff=True. - Grant Django-wide capabilities via
is_superuser=True. - Use Django’s permissions and groups system to control access precisely.
Example:
admin_user.is_staff = True
admin_user.groups.add(admin_group)
admin_user.save()
This approach keeps a single user entry, simplifies logging, and prevents too many permissions.
Testing Your User Model
Testing protects your custom logic from hidden problems. Focus on:
- ✅ Duplicate email rejections
- ✅ Custom authentication behavior
- ✅ Superuser creation and access paths
- ✅ Admin login compatibility
- ✅ Model uniqueness constraints
Using Django's TestCase class:
from django.test import TestCase
from .models import CustomUser
class UserModelTests(TestCase):
def test_email_uniqueness(self):
user1 = CustomUser.objects.create_user(email="user@example.com", password="pass")
with self.assertRaises(Exception):
CustomUser.objects.create_user(email="user@example.com", password="pass2")
Even better: add your tests to your CI/CD setup.
Future-Proofing Your Authentication System
To ensure long-term stability and growth:
- 📚 Document your custom user logic and configuration choices.
- 🧹 Migrate legacy users with duplicate emails early.
- 🔄 Use Django’s migrations framework to update instead of completely changing.
- 🚨 Set email uniqueness constraints at the database level.
- 🔍 Regularly check your database for users with shared credentials or unusual access logs.
By following Django’s systems and making your identifiers secure, your application can grow without user identity problems.
Django Done Right
Managing users in Django isn’t just about authentication—it’s about building trust, clarity, and clean workflows. You can avoid the frustrating trap of shared credentials by creating a strong authentication system today with a custom user model, unique identifiers, and roles that are given out clearly.
Taking extra care in how your django custom user system is built will ensure that features like login, user management, admin access, and notifications work smoothly at every level of your application.
Citations
Django Documentation. (2023). Using the Django authentication system. Retrieved from https://docs.djangoproject.com/en/stable/topics/auth/
Stack Overflow User ‘Ralph’. (2023). Answer to “Can users and superusers have the same email/username in a Django application?”. Retrieved from https://stackoverflow.com/questions/10861767/can-users-and-superusers-have-the-same-email-username-in-a-django-application/