Django auth cheatsheet.

Built-in auth

INSTALLED_APPS = [..., "django.contrib.auth", "django.contrib.contenttypes"]
MIDDLEWARE = [..., "django.contrib.auth.middleware.AuthenticationMiddleware"]
AUTHENTICATION_BACKENDS = ["django.contrib.auth.backends.ModelBackend"]

Login / logout views

# urls.py
from django.contrib.auth import views as auth_views

urlpatterns = [
    path("login/", auth_views.LoginView.as_view(), name="login"),
    path("logout/", auth_views.LogoutView.as_view(), name="logout"),
    path("password_change/", auth_views.PasswordChangeView.as_view()),
    path("password_reset/", auth_views.PasswordResetView.as_view()),
]

settings

LOGIN_URL = "/login/"
LOGIN_REDIRECT_URL = "/"
LOGOUT_REDIRECT_URL = "/"

Login form template

<form method="post">
  {% csrf_token %}
  {{ form }}
  <input type="hidden" name="next" value="{{ next }}">
  <button>Sign in</button>
</form>

Template path: registration/login.html.

Custom user (always do this from start)

# accounts/models.py
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    email = models.EmailField(unique=True)
    bio = models.TextField(blank=True)
    
    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["username"]
# settings.py
AUTH_USER_MODEL = "accounts.User"

⚠️ Set this BEFORE the first migration. Switching later is painful.

Custom user manager

class UserManager(BaseUserManager):
    def create_user(self, email, password, **extra):
        user = self.model(email=self.normalize_email(email), **extra)
        user.set_password(password)
        user.save()
        return user
    
    def create_superuser(self, email, password, **extra):
        extra.setdefault("is_staff", True)
        extra.setdefault("is_superuser", True)
        return self.create_user(email, password, **extra)

class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    
    objects = UserManager()
    
    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

Check authentication

from django.contrib.auth.decorators import login_required, permission_required

@login_required
def view(request):
    return ...

@permission_required("blog.add_post", raise_exception=True)
def create_post(request):
    return ...

Class-based:

from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin

class MyView(LoginRequiredMixin, ListView):
    login_url = "/login/"

class AdminView(PermissionRequiredMixin, ListView):
    permission_required = "blog.delete_post"

Permissions

Built-in: <app>.<action>_<model> (e.g., blog.add_post).

Add custom:

class Post(models.Model):
    ...
    class Meta:
        permissions = [
            ("can_publish", "Can publish posts"),
        ]

Check:

request.user.has_perm("blog.can_publish")
request.user.has_perms(["blog.can_publish", "blog.delete_post"])

Groups

from django.contrib.auth.models import Group

editors = Group.objects.create(name="Editors")
editors.permissions.add(Permission.objects.get(codename="add_post"))

user.groups.add(editors)

authenticate + login

from django.contrib.auth import authenticate, login, logout

def login_view(request):
    user = authenticate(request, email=email, password=password)
    if user:
        login(request, user)
        return redirect("/")
    return ...

def logout_view(request):
    logout(request)

Password hashers

# settings.py
PASSWORD_HASHERS = [
    "django.contrib.auth.hashers.Argon2PasswordHasher",
    "django.contrib.auth.hashers.PBKDF2PasswordHasher",
]
uv add argon2-cffi

Email-based password reset

Built-in PasswordResetView sends email. Configure:

EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.example.com"
EMAIL_HOST_USER = "..."
EMAIL_HOST_PASSWORD = "..."
EMAIL_USE_TLS = True
DEFAULT_FROM_EMAIL = "[email protected]"

django-allauth (OAuth + everything)

uv add django-allauth

Adds GitHub, Google, etc. Battle-tested.

Sessions

SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"   # fastest with Redis
SESSION_COOKIE_AGE = 60 * 60 * 24 * 30
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = True       # prod
SESSION_COOKIE_SAMESITE = "Lax"

CSRF

Default enabled. For AJAX, send X-CSRFToken:

fetch("/api/x/", {
  method: "POST",
  headers: { "X-CSRFToken": getCookie("csrftoken") },
  body: ...,
});

Use @csrf_exempt sparingly.

API token auth

For DRF, see DRF cheatsheet. For Django views, use simple HTTP basic or session.

Common mistakes

  • Adding custom user after first migration → painful migration.
  • Forgetting AUTH_USER_MODEL setting.
  • Using User import instead of get_user_model().
  • Plaintext password in User.objects.create() — use create_user.
  • Sharing session across HTTPS and HTTP — security risk.

Read this next

If you want my custom User + email-only auth setup, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .