Authentication Testing
Test coverage and patterns for the authentication system including browser flow, JWT, and frontend tests
Authentication Testing
Test coverage and patterns for the authentication system.
Test Files
| File | Coverage |
|---|---|
tests/account/test_headless_browser_csrf.py | Browser CSRF flow, session auth |
tests/account/test_jwt_auth.py | JWT tokens, tenant claims |
tests/account/test_password_reset.py | Password reset flow |
Running Tests
# All account tests
uv run pytest tests/account/ -v
# Specific test file
uv run pytest tests/account/test_headless_browser_csrf.py -v
# With coverage
uv run pytest tests/account/ --cov=apps.accountsTest Fixtures
User with Verified Email and Tenant
@pytest.fixture
def user_with_verified_email_and_tenant():
from allauth.account.models import EmailAddress
user = UserFactory(password=TEST_PASSWORD)
EmailAddress.objects.create(
user=user,
email=user.email,
verified=True,
primary=True,
)
tenant = TenantFactory(owner=user)
tenant.users.add(user)
return user, tenantBrowser Flow Tests
CSRF Cookie Setup
def test_browser_config_sets_csrf_cookie():
"""GET /_allauth/browser/v1/config must set csrftoken cookie."""
client = Client(enforce_csrf_checks=True)
resp = client.get("/_allauth/browser/v1/config")
assert resp.status_code == 200
assert "csrftoken" in resp.cookiesLogin with CSRF
def test_browser_login_requires_csrf_cookie_then_succeeds(user_with_verified_email_and_tenant):
user, _tenant = user_with_verified_email_and_tenant
client = Client(enforce_csrf_checks=True)
# Step 1: Get CSRF token
cfg = client.get("/_allauth/browser/v1/config")
csrf_token = client.cookies["csrftoken"].value
# Step 2: Login with CSRF header
login = client.post(
"/_allauth/browser/v1/auth/login",
data=json.dumps({"email": user.email, "password": TEST_PASSWORD}),
content_type="application/json",
HTTP_X_CSRFTOKEN=csrf_token,
)
assert login.status_code == 200
payload = login.json()
assert payload["meta"]["is_authenticated"] is True
assert payload["data"]["user"]["email"] == user.emailSession Auth for API
def test_ninja_api_accepts_django_session_cookie(user_with_verified_email_and_tenant):
"""After browser login, Ninja API should authenticate via session."""
user, _tenant = user_with_verified_email_and_tenant
client = Client(enforce_csrf_checks=True)
# Login
client.get("/_allauth/browser/v1/config")
csrf_token = client.cookies["csrftoken"].value
client.post(
"/_allauth/browser/v1/auth/login",
data=json.dumps({"email": user.email, "password": TEST_PASSWORD}),
content_type="application/json",
HTTP_X_CSRFTOKEN=csrf_token,
)
# Access protected API endpoint
profile = client.get("/api/v1/users/profile/")
assert profile.status_code == 200
assert profile.json()["email"] == user.emailJWT Flow Tests
JWT tests are skipped unless JWT strategy is enabled:
if "jwt" not in getattr(settings, "HEADLESS_TOKEN_STRATEGY", "").lower():
pytest.skip("JWT strategy not enabled", allow_module_level=True)Login Returns JWT Tokens
class TestJWTLogin:
def test_login_returns_jwt_tokens(self, dj_client, user_with_tenant):
user, tenant = user_with_tenant
response = dj_client.post(
"/_allauth/app/v1/auth/login",
data=json.dumps({"email": user.email, "password": TEST_PASSWORD}),
content_type="application/json",
)
assert response.status_code == 200
data = response.json()
assert "access_token" in data["meta"]
assert "refresh_token" in data["meta"]
assert data["data"]["user"]["email"] == user.emailToken Contains Tenant Claims
def test_token_includes_tenant_id_claim(self, dj_client, user_with_tenant):
import jwt
user, tenant = user_with_tenant
response = dj_client.post(
"/_allauth/app/v1/auth/login",
data=json.dumps({"email": user.email, "password": TEST_PASSWORD}),
content_type="application/json",
)
access_token = response.json()["meta"]["access_token"]
claims = jwt.decode(access_token, options={"verify_signature": False})
assert "tenant_id" in claims or "current_tenant_id" in claims
tenant_claim = claims.get("tenant_id") or claims.get("current_tenant_id")
assert tenant_claim == str(tenant.id)API Request with JWT
def test_api_request_with_valid_jwt_succeeds(self, dj_client, user_with_tenant):
user, tenant = user_with_tenant
# Get tokens
login_response = dj_client.post(
"/_allauth/app/v1/auth/login",
data=json.dumps({"email": user.email, "password": TEST_PASSWORD}),
content_type="application/json",
)
access_token = login_response.json()["meta"]["access_token"]
# Make authenticated request
response = dj_client.get(
"/api/v1/users/profile/",
HTTP_AUTHORIZATION=f"Bearer {access_token}",
)
assert response.status_code == 200
assert response.json()["email"] == user.emailToken Refresh
def test_refresh_token_returns_new_tokens(self, dj_client, user_with_tenant):
user, _ = user_with_tenant
# Login
login_response = dj_client.post(
"/_allauth/app/v1/auth/login",
data=json.dumps({"email": user.email, "password": TEST_PASSWORD}),
content_type="application/json",
)
refresh_token = login_response.json()["meta"]["refresh_token"]
# Refresh
response = dj_client.post(
"/_allauth/app/v1/auth/token/refresh",
data=json.dumps({"refresh": refresh_token}),
content_type="application/json",
)
assert response.status_code == 200
assert "access_token" in response.json()["meta"]
assert "refresh_token" in response.json()["meta"]Frontend Testing
// hooks/use-auth.test.tsx
import { renderHook, act, waitFor } from "@testing-library/react";
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
import { useAuth, useAuthStore } from "./use-auth";
const server = setupServer(
http.get("http://localhost:8080/_allauth/browser/v1/config", () => {
return HttpResponse.json({
data: {
account: { authentication_method: "email" },
socialaccount: { providers: [] },
},
});
}),
http.post("http://localhost:8080/_allauth/browser/v1/auth/login", () => {
return HttpResponse.json({
meta: { is_authenticated: true },
data: { user: { id: 1, email: "test@example.com" } },
});
})
);
beforeAll(() => server.listen());
afterEach(() => {
server.resetHandlers();
useAuthStore.getState().logout();
});
afterAll(() => server.close());
describe("useAuth", () => {
it("should login and set user", async () => {
const { result } = renderHook(() => useAuth());
await act(async () => {
await result.current.login("test@example.com", "password");
});
await waitFor(() => {
expect(result.current.isAuthenticated).toBe(true);
expect(result.current.user?.email).toBe("test@example.com");
});
});
});Test Password
Use a consistent test password that meets validation requirements:
TEST_PASSWORD = "ComPL@S6sxPdfsdword1"This password meets:
- Minimum length (12+ chars)
- Mixed case
- Numbers
- Special characters
Last updated on