Goal for this part
Sign neighbors in and out with Django’s session authentication. Protect every write. Show the signed-in name in the header.
Why session cookies, not JWTs in localStorage
Django stores a session key in an HttpOnly cookie. JavaScript cannot steal it with a casual XSS copy-paste. JWTs in localStorage are the default SPA pattern and a default theft target. Westloop is server-rendered; sessions are the correct tool. Same-site cookies plus CSRF is the pair that makes HTMX POSTs safe.
Walkthrough
Use Django’s built-in login view, or a thin wrapper if you want a Tailwind-styled template. Set the redirects once:
LOGIN_URL = "accounts:login"
LOGIN_REDIRECT_URL = "posts:feed"
LOGOUT_REDIRECT_URL = "posts:feed"# accounts/urls.py
from django.contrib.auth import views as auth_views
from django.urls import path
from . import views
app_name = "accounts"
urlpatterns = [
path("accounts/register/", views.register, name="register"),
path("accounts/login/", auth_views.LoginView.as_view(template_name="accounts/login.html"), name="login"),
path("accounts/logout/", auth_views.LogoutView.as_view(), name="logout"),
]Logout must be a POST (Django 5 default). The header form is a button, not a GET link. Wrap future compose, like, follow, and comment views with @login_required.
How to run it and what you should see
Sign in as ada / password123. The header should show Ada’s display name and a Sign out button. Visit /accounts/login/ again while signed in and you should bounce to the feed. Sign out and the menu returns to Sign in / Join.
Common mistakes
A GET logout URL will 405. Missing CSRF on the login form is a 403. If login “works” but the next request is anonymous, you are on two different hosts (localhost vs 127.0.0.1) or SESSION_COOKIE_SECURE is true on HTTP.
Try this
Open DevTools → Application → Cookies. You should see sessionid and csrftoken, both HttpOnly or at least not readable as a JWT blob. Then try a wrong password and confirm the form error does not reveal whether the username exists.