How to Add Auth to a Django App (Built-in, allauth, OAuth)

14 Jun 2026 · Bank K.

A practical guide to adding authentication to a Django app: django.contrib.auth, sessions, django-allauth, and OAuth — with code and honest trade-offs.

Django is one of the few frameworks where the auth system is already in the box. django.contrib.auth ships with a User model, password hashing, sessions, and login views. So why do so many Django auth tutorials still send you down a rabbit hole? Because “add auth to a Django app” splits into a few different jobs — built-in sessions, social login, email verification — and they’re not all equally easy.

This guide covers the realistic paths: Django’s built-in auth, when to add django-allauth, OAuth, and the trade-offs that matter when you’re shipping a SaaS MVP solo.

Use What Django Already Gives You

Before installing anything, know that django.contrib.auth and django.contrib.contenttypes are in INSTALLED_APPS on a default project, along with the session and auth middleware. You already have:

  • A User model with hashed passwords (PBKDF2 by default, with Argon2 and bcrypt available).
  • Session-based authentication backed by a server-side session store.
  • login() and logout() helpers.
  • View protection via @login_required and LoginRequiredMixin.

The golden rule: use login() and logout() from django.contrib.auth. Never set session data by hand for authentication — you’ll skip the security work Django already did.

Option 1: Built-in Session Auth

For email/password login, Django’s own views get you most of the way. Wire up the auth URLs:

# urls.py
from django.urls import path, include

urlpatterns = [
    path("accounts/", include("django.contrib.auth.urls")),
    # gives you /login, /logout, /password_reset, etc.
]

If you want a custom login view to control the flow:

# views.py
from django.contrib.auth import authenticate, login

def login_view(request):
    if request.method == "POST":
        user = authenticate(
            request,
            username=request.POST["username"],
            password=request.POST["password"],
        )
        if user is not None:
            login(request, user)  # creates the session correctly
            return redirect("dashboard")
    return render(request, "login.html")

Protect views with the decorator or mixin:

from django.contrib.auth.decorators import login_required

@login_required
def dashboard(request):
    return render(request, "dashboard.html", {"user": request.user})

In production, lock down the session cookie. These settings are not optional for a real app:

# settings.py
SESSION_COOKIE_SECURE = True       # HTTPS only
SESSION_COOKIE_HTTPONLY = True     # no JS access
SESSION_COOKIE_SAMESITE = "Lax"    # CSRF mitigation
CSRF_COOKIE_SECURE = True

Run manage.py clearsessions on a schedule to purge expired sessions. That’s a complete, secure email/password setup with zero third-party packages.

A Custom User Model — Decide Early

One Django gotcha worth flagging: if you ever want to log in by email instead of username, or add fields to the user, swap in a custom user model before your first migration. Changing it later is painful.

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

class User(AbstractUser):
    pass  # add fields here later

# settings.py
AUTH_USER_MODEL = "accounts.User"

Even an empty subclass buys you flexibility for free. Do it on day one.

Option 2: django-allauth (social login and email flows)

When you need “Sign in with Google,” email verification, or more polished account management, django-allauth is the standard choice. It integrates with 50+ OAuth providers and handles signup, email confirmation, and password reset.

pip install django-allauth
# settings.py
INSTALLED_APPS += [
    "django.contrib.sites",
    "allauth",
    "allauth.account",
    "allauth.socialaccount",
    "allauth.socialaccount.providers.google",
]

AUTHENTICATION_BACKENDS = [
    "django.contrib.auth.backends.ModelBackend",
    "allauth.account.auth_backends.AuthenticationBackend",
]

SITE_ID = 1
# urls.py
urlpatterns += [path("accounts/", include("allauth.urls"))]

Two newer allauth settings worth turning on: ACCOUNT_PREVENT_ENUMERATION (so attackers can’t probe which emails exist) and ACCOUNT_EMAIL_VERIFICATION_BY_CODE_ENABLED (a numeric code instead of a link, which works better on mobile).

One real requirement people miss: allauth’s rate limiting needs a proper cache backend. The default DummyCache does nothing, so brute-force protection silently won’t work. Configure Redis or memcached.

If you’re comparing social login against passwordless email, the magic link authentication guide breaks down when each makes sense.

Sessions vs JWT in Django

Django’s default is session-based and that’s the right choice for a server-rendered app or a same-origin SPA. Sessions are server-side, easy to revoke, and Django handles them for you.

JWT (via something like djangorestframework-simplejwt) makes sense when you have a Django REST API serving a separate frontend or mobile app on another domain. The trade-off is the usual one: a JWT can’t be revoked before it expires unless you maintain a blocklist. For a single web app, sessions are simpler and safer. Don’t add JWT because it sounds modern — add it when you have a cross-origin client that needs it.

The Wall Every SaaS Hits

Django gets you a logged-in user with very little code. What it doesn’t get you is revenue. The moment you want to charge, you’re building the billing layer by hand: dj-stripe or raw Stripe integration, a customer record tied to each user, webhook endpoints for checkout.session.completed and customer.subscription.deleted, plan gating in your views, and a portal for upgrades and cancellations. Then you keep auth state and subscription state in sync forever.

That’s the part Beag handles. Instead of stitching Django auth to a Stripe integration you maintain, Beag bundles server-validated authentication and Stripe subscriptions together. The user’s plan and status are available to your frontend after login, so gating a premium feature is a plan check, not a billing subsystem:

if request.user.plan in ("pro", "enterprise"):
    # grant access
    ...

No webhook handlers to write, no customer sync logic, no second system to keep aligned with your user table. You keep Django for your app and skip building payments from scratch.

What to Use

  • Email/password only? Django’s built-in auth. Add a custom user model on day one and lock down the cookies.
  • Social login or email verification? django-allauth, with a real cache backend for rate limiting.
  • REST API with a separate frontend? Sessions if same-origin, JWT only if cross-origin.
  • Need payments live this week? Don’t hand-build auth-plus-Stripe — use an all-in-one so identity and billing stay in sync.

Set up auth before your features. In Django that mostly means picking your user model correctly, because every model you write afterward will relate back to it.

Add auth and Stripe payments to your Django app fast. Get started with Beag — no webhook plumbing, no billing subsystem.

For more guides, see how to add auth to a Next.js app or browse the Beag blog.

About the Author
Bank K.

Bank K.

Serial entrepreneur & Co-founder of Beag.io

Founder of Beag.io. Indie hacker building tools to help developers ship faster. Previously built multiple micro SaaS products.

Ready to Make Money From Your SaaS?

Turn your SaaS into cash with Beag.io. Get started now!

Start 7-day free trial →