Auth¶
Django session auth works inside Reflex events. Users can log in through Django admin, built-in reflex-django auth pages, or custom handlers. Bridge-bound handlers on AppState see the live Django user through self.request.user.
Live user vs UI snapshot¶
| Use in handlers | Use in UI |
|---|---|
self.request.user |
self.is_authenticated |
current_user() |
self.username |
| Django permission checks | Show/hide buttons |
Never authorize from the reactive snapshot alone. It is client-visible and can lag behind the session.
Built-in auth pages¶
Built-in login, signup, and password-reset pages are explicit opt-in. They are not mounted just because reflex_django is installed or RX_AUTH["ENABLED"] is true.
Register them from {app}/{app}.py:
import reflex as rx
from reflex_django import add_auth_pages
from shop.views import HomeState, index
app = rx.App()
app.add_page(
index,
route="/",
title="Home",
on_load=HomeState.on_load,
)
add_auth_pages(app)
Configure routes, field names, redirects, messages, and optional custom page classes in settings.py:
RX_AUTH = {
"LOGIN_URL": "/sign-in",
"SIGNUP_URL": "/sign-up",
"LOGIN_REDIRECT_URL": "/dashboard",
"LOGOUT_REDIRECT_URL": "/",
"LOGIN_FIELDS": ["email"],
"PASSWORD_MIN_LENGTH": 10,
"SIGNUP_ENABLED": True,
"PASSWORD_RESET_ENABLED": True,
}
Branding and custom classes:
RX_AUTH = {
"BRAND_TEXT": "My App",
"BRAND_ICON_SRC": "/static/logo.png",
"MESSAGES": {
"login_heading": "Sign in to My App",
"register_heading": "Create your account",
},
"PAGE_CLASSES": {
"login": "myapp.auth.BrandedLoginPage",
"register": "myapp.auth.BrandedRegisterPage",
},
}
Prefer explicit add_auth_pages(app) over autoload(). Individual registration helpers include register_login_page(app), register_register_page(app), and password-reset page registration functions. If you do not call one of these helpers, routes such as /login, /register, and /password-reset are not created.
Custom page classes¶
Use RX_AUTH["PAGE_CLASSES"] when you want to replace built-in page state/classes while keeping the registry flow:
RX_AUTH = {
"PAGE_CLASSES": {
"login": "shop.auth_pages.CustomLoginPage",
"register": "shop.auth_pages.CustomRegisterPage",
},
}
The public auth page classes include BaseAuthPage, LoginPage, RegisterPage, PasswordResetPage, and PasswordResetConfirmPage. You can also register pages one at a time with register_login_page(app), register_register_page(app), register_password_reset_page(app), and register_password_reset_confirm_page(app).
RX_AUTH¶
Common keys:
| Key | Purpose |
|---|---|
ENABLED |
Allow explicit built-in auth page registration |
SIGNUP_ENABLED |
Enable registration page |
PASSWORD_RESET_ENABLED |
Enable password-reset pages |
LOGIN_URL, SIGNUP_URL, PASSWORD_RESET_URL |
Route paths |
LOGIN_REDIRECT_URL, LOGOUT_REDIRECT_URL, SIGNUP_REDIRECT_URL |
Redirect targets |
REDIRECT_AUTHENTICATED_USER |
Where logged-in users go from login/register |
LOGIN_FIELDS |
username, email, or both depending on configured auth flow |
MESSAGES |
UI/error copy |
PAGE_CLASSES |
Optional custom page state/classes |
RX_SITE_ORIGIN controls password-reset links when no request is bound.
Gate a handler¶
from reflex_django.auth import login_required, permission_required
from reflex_django.states import AppState
class PostState(AppState):
@rx.event
@login_required
async def create(self):
await Post.objects.acreate(owner=self.request.user, title=self.title)
@rx.event
@permission_required("blog.publish_post")
async def publish(self):
...
Group and role guards are also available:
from reflex_django.auth import group_required, staff_required, superuser_required
@rx.event
@group_required("Editors")
async def editor_action(self):
...
If a handler is denied, the decorator redirects to redirect/login URL by default, calls on_denied(state) when supplied, or uses state.on_permission_denied() when present.
Gate a page¶
@page(login_required=True) handles the common login-only case:
from reflex_django.pages.decorators import page
@page(route="/dashboard", login_required=True)
def dashboard() -> rx.Component:
...
Permission, group, staff, and superuser decorators also wrap page functions:
from reflex_django.auth import permission_required, staff_required
@permission_required("shop.view_reports", redirect="/login")
@page(route="/reports")
def reports() -> rx.Component:
...
@staff_required(redirect="/login")
@page(route="/admin-tools")
def admin_tools() -> rx.Component:
...
Page wrappers render a client-side loading/fallback state, but the actual guard is an always-mounted server event on DjangoAuthState. Unauthorized users are redirected by the server, not merely hidden in the UI.
Imperative helpers¶
from reflex_django.auth import (
ReflexDjangoAuthError,
auser_has_perm,
auser_in_group,
require_login_user,
)
user = require_login_user(self.request)
allowed = await auser_has_perm(user, "shop.change_product")
in_group = await auser_in_group(user, "Editors")
Programmatic login/logout¶
DjangoUserState / AppState expose async session helpers:
Successful login/logout mirrors cookie state for the browser and often performs a short deferred full-page navigation so the next document request sends the updated session.
RX_LOGOUT_PRESERVE_SESSION_KEYS lists session keys copied before logout and restored on the anonymous session. The default keeps "theme".
session_auth_mixin¶
For a custom login UI without the built-in pages:
from reflex_django.mixins import SessionAuthConfig, session_auth_mixin
LoginState = session_auth_mixin(
SessionAuthConfig(
state_class_name="LoginState",
post_login_redirect="/",
post_logout_redirect="/login",
login_fields=("username", "email"),
)
)
The generated state includes username/password/error vars, submit handlers, optional form-submit handler, logout handler, and cookie-sync navigation.
Cookie security¶
Built-in Reflex login/logout syncs Django sessionid through JavaScript because WebSocket events cannot apply Set-Cookie headers like normal HTTP responses. The fallback settings therefore set SESSION_COOKIE_HTTPONLY=False.
If your app requires HttpOnly session cookies, use a dedicated HTTP login/logout flow or cookie-sync endpoint. See Security.
User snapshot¶
Set RX_USER_SNAPSHOT_INCLUDE_GROUPS = True to add group names.
FAQ¶
Same cookie as admin? Yes. One sessionid, one Django session.
Works on WebSocket events? Yes. The bridge runs the configured event tier and binds the request/user to AppState. See Bridge.
Login shows "Session unavailable. Reload the page."? Built-in auth pages use DjangoAuthState, which requires a bridged Django request on login/register events. In bridge.mode: "smart", ensure you are on a reflex-django version that treats DjangoAuthState as Django-aware. As a temporary workaround, set bridge.mode: "full" in rxconfig.py or RX_EVENT_BRIDGE_MODE = "full" in Django settings.
Next: Bridge utilities, Security, and Pages and state.