Authentication¶
Django session authentication in Reflex events, a unified AppState auth bridge, decorators, and optional canned login/register pages.
Prerequisites¶
- Django middleware to Reflex — how
DjangoEventBridgebindsrequest.userper event - State management — plain
rx.Statevs helper states
How authentication reaches Reflex¶
Reflex UI actions run over Socket.IO, not through Django’s HTTP middleware stack. DjangoEventBridge (enabled by default on ReflexDjangoPlugin) rebuilds a synthetic HttpRequest for every event, loads the session from the cookie, and resolves request.user with Django’s aget_user—the same auth backends and session store as normal Django views.
flowchart TB
subgraph browser [Browser]
UI[Reflex UI]
Cookie[sessionid cookie]
end
subgraph event [Each Reflex event]
E[Event + router_data]
Bridge[DjangoEventBridge.preprocess]
Req[Synthetic HttpRequest]
Sess[SESSION_ENGINE]
User[aget_user]
CTX[current_request / current_user]
Sync[AppState.refresh_django_user_fields]
H[Your handler]
end
UI --> E
Cookie --> E
E --> Bridge --> Req
Req --> Sess --> User --> CTX
CTX --> Sync --> H
| Path | Middleware | Session | User |
|---|---|---|---|
HTTP (/admin, /api, …) |
Full Django MIDDLEWARE |
SessionMiddleware |
AuthenticationMiddleware |
| Reflex events | Bridge only (session + auth + optional i18n) | SESSION_ENGINE on synthetic request |
aget_user in bridge |
There is no second auth implementation—handlers use Django’s session row and user model. OAuth, JWT, and multi-tenant auth are not built in yet.
For how cookies, sessionStorage, and server-side Reflex state fit together, see Browser storage: Django sessionid vs Reflex token.
Two layers: live objects vs reactive snapshot¶
AppState (and DjangoUserState) expose two ways to read auth, on purpose:
| Layer | Where | Use for |
|---|---|---|
| Live | self.user, self.session in @rx.event handlers |
Authorization, ORM scoping, session writes |
| Snapshot | self.is_authenticated, self.username, self.email, … |
UI bindings on AppState / DjangoUserState branches (rx.cond, rx.text) |
| Live UI (canned auth) | DjangoAuthState.is_authenticated (@rx.var) |
Sidebar logout, @login_required page gate — reads current_user() per event |
# Live — always current for this event; not sent to the browser as a Reflex var
if self.user.is_authenticated:
await MyModel.objects.filter(owner=self.user).adelete()
# Snapshot — synced to the client for AppState / dashboard branches
rx.cond(DashboardState.is_authenticated, rx.text(DashboardState.username), ...)
# Canned auth branch — live session via @rx.var (not the inherited django_user_state snapshot)
rx.cond(DjangoAuthState.is_authenticated, logout_button, ...)
In component code, DjangoAuthState.is_authenticated is a Reflex Var (for example BooleanCastedVar), not a Python bool—that is expected. The client evaluates it from server state after each event.
Rule: Never use snapshot or computed UI fields alone to allow deletes, admin actions, or private data—always check self.user or require_login_user() in the handler.
When REFLEX_DJANGO_AUTH_AUTO_SYNC is True (default), the bridge refreshes snapshot fields on every event for all DjangoUserState substates and DjangoAuthState (username, email, …), so navbars and dashboards update after login/logout without calling sync_from_django on every page. DjangoAuthState.is_authenticated is separate: it is a @rx.var that calls current_user() and does not rely on the old inherited snapshot on a parent django_user_state substate.
DjangoUserState.sync_from_django (e.g. in page on_load) performs the same full-tree refresh for snapshot fields. Optional DjangoAuthState.sync_auth_ui refreshes snapshots and marks inherited fields dirty when you mix auth and app substates in one layout.
Accessing the Django request on AppState¶
Every Reflex event runs with a synthetic HttpRequest built by DjangoEventBridge from router_data (path, query string, cookies, headers, client IP). AppState (and subclasses like ModelState) expose that request on the state instance so handlers feel like Django views.
Three equivalent styles¶
| Style | Where | Best for |
|---|---|---|
self.request |
AppState / ModelState handlers |
Instance-based code, CRUD hooks (get_queryset, …) |
self.django_request |
Same | When you need the raw HttpRequest object |
from reflex_django import request |
Any rx.State handler |
Plain rx.State without AppState |
current_request() / current_user() |
Any handler | Explicit, functional style |
All of them read the same bridged request for the current event. Outside an event (import time, background task), there is no request—request.user is anonymous and request.GET is empty.
flowchart LR
Bridge[DjangoEventBridge] --> Http[HttpRequest]
Http --> CTX[current_request contextvar]
CTX --> SR[self.request / request proxy]
CTX --> SU[self.user]
Dispatch[ModelCRUDView.dispatch] --> Bind[bind_request_context]
Bind --> SR
self.request — DjangoStateRequest wrapper¶
On AppState, self.request is a DjangoStateRequest that wraps the synthetic HttpRequest and (when context collection runs) merged context-processor output.
| Access | What you get |
|---|---|
self.request.user |
Live Django user (AnonymousUser when logged out)—use for ORM filters and authorization |
self.request.django_request |
Same as self.django_request — raw HttpRequest |
self.request.GET, .POST, .path, .method, .META, .COOKIES |
Forwarded from the underlying HttpRequest |
self.request.LANGUAGE_CODE, self.request.SITE_NAME, … |
Keys from REFLEX_DJANGO_CONTEXT_PROCESSORS (when loaded) |
self.request.context |
dict copy of all processor keys (e.g. context["user"] for JSON snapshot) |
self.user is a shortcut for self.request.user (and current_user()). Prefer self.request.user in CRUD hooks for consistency with Django view style.
Important: self.request.user is the live user model. Context processors often expose a JSON user snapshot for templates—that lives in self.request.context["user"], not in self.request.user.
Example: dashboard handler (plain AppState)¶
import reflex as rx
from reflex_django.state import AppState
class DashboardState(AppState):
last_path: str = ""
@rx.event
async def on_load(self):
# Auth — same as self.user
if not self.request.user.is_authenticated:
return rx.redirect("/login")
# Query string from the page URL (router_data)
tab = self.request.GET.get("tab", "overview")
# Session (also available as self.session["key"])
self.request.session["last_visit"] = "dashboard"
# Optional: context processor keys when configured
site = getattr(self.request, "SITE_NAME", None)
self.last_path = self.request.path
return rx.toast.info(f"Tab={tab}, site={site}")
Example: user-scoped CRUD hooks (ModelState / ModelCRUDView)¶
During dispatch (save, refresh, load, …), reflex-django calls bind_request_context(), which attaches self.request with context processors when load_context_processors is True (default).
from reflex_django.state import ModelState
from notes.models import Note
class NotesState(ModelState):
model = Note
fields = ["title", "content"]
ordering = ("-id",)
def get_queryset(self):
# Scope rows to the logged-in user
return Note.objects.filter(owner=self.request.user)
def get_object_lookup(self, pk: int) -> dict:
return {"pk": pk, "owner": self.request.user}
def get_create_kwargs(self, state_data: dict) -> dict:
return {**state_data, "owner": self.request.user}
def filter_queryset(self, qs):
# Processor key (settings.REFLEX_DJANGO_CONTEXT_PROCESSORS)
if getattr(self.request, "LANGUAGE_CODE", None) == "ar":
qs = qs.filter(locale="ar")
return qs
ModelState subclasses AppState, so you can use self.request in custom @rx.event methods as well as in generated CRUD hooks.
Example: read query params on a plain rx.State¶
When a class does not subclass AppState, use the module proxy:
import reflex as rx
from reflex_django import request
class SearchState(rx.State):
@rx.event
async def run_search(self):
q = request.GET.get("q", "").strip()
if not request.user.is_authenticated:
return rx.toast.error("Sign in to search")
# ... ORM using request.user
Invalid import: from reflex_django.state import request — use from reflex_django import request.
self.django_request — raw HttpRequest¶
Use when a Django API expects the real request object (login, messages, third-party helpers):
from reflex_django.context import current_request
from reflex_django.mixins.session_auth import _sync_session_cookie_then_nav
class AuthState(AppState):
@rx.event
async def sign_in_and_go(self):
ok = await self.login(self.username, self.password)
if not ok:
return await self.on_auth_failed()
http = self.django_request # or current_request()
if http is not None:
return _sync_session_cookie_then_nav(http, "/")
Context processors on self.request¶
Enable processors in Django settings via REFLEX_DJANGO_CONTEXT_PROCESSORS (see Django context to Reflex). During ModelCRUDView.dispatch, keys are merged onto self.request:
# Attribute style (template-like)
lang = self.request.LANGUAGE_CODE
# Dict style (explicit)
snapshot = self.request.context.get("user", {})
perms = self.request.context.get("permissions", [])
Disable collection but keep the HTTP request:
class PublicState(ModelState):
model = Article
fields = ["title", "body"]
load_context_processors = False # class body or Meta
What not to do¶
| Do not | Do instead |
|---|---|
Pass self.request.user into rx.text(...) |
Use snapshot vars: self.username, self.is_authenticated |
Rely on self.is_authenticated alone to allow deletes |
Check self.request.user or require_login_user() in the handler |
| Expect CSRF middleware on Reflex events | Protect mutations with @login_required, permissions, or Django HTTP views |
Use self.request at import time |
Only inside @rx.event handlers (after the bridge runs) |
HTTP details¶
Query params, cookies, and headers come from event.router_data. See Django middleware to Reflex for the full bridge pipeline and from reflex_django import request API (request.headers, request.COOKIES, request.path, …).
Quick start¶
1. Plugin (in rxconfig.py):
from reflex_django import ReflexDjangoPlugin
config = rx.Config(
app_name="myapp",
plugins=[
ReflexDjangoPlugin(
settings_module="backend.settings",
install_event_bridge=True, # default
)
],
)
2. State — subclass AppState:
import reflex as rx
from reflex_django.state import AppState
class AppStateRoot(AppState):
"""Rename to match your app; shown as one Reflex state tree."""
@rx.event
async def on_load(self):
# Optional: auto-sync usually makes this unnecessary for auth fields
await self.refresh_django_user_fields()
3. Protect handlers and pages:
from reflex_django.auth import login_required, permission_required
@rx.event
@login_required
async def members_only(self):
return self.user.get_username()
@rx.event
@permission_required("shop.view_product", redirect="/login")
async def list_products(self):
...
Complete example: layout, dashboard, and custom login¶
This pattern fits apps that use AppState for both navigation and feature state (no separate DjangoUserState class required).
myapp/state.py
import reflex as rx
from reflex_django.state import AppState
class SiteState(AppState):
login_username: str = ""
login_password: str = ""
login_error: str = ""
@rx.event
async def submit_login(self):
self.login_error = ""
ok = await self.login(self.login_username, self.login_password)
if not ok:
self.login_error = "Invalid username or password."
self.login_password = ""
return
# After login, sync browser cookie (see "Session cookie sync" below)
from reflex_django.context import current_request
from reflex_django.mixins.session_auth import _sync_session_cookie_then_nav
request = current_request()
if request is not None:
return _sync_session_cookie_then_nav(request, "/")
@rx.event
async def sign_out(self):
await self.logout()
from reflex_django.context import current_request
from reflex_django.mixins.session_auth import _sync_session_cookie_then_nav
request = current_request()
if request is not None:
return _sync_session_cookie_then_nav(
request, "/login", clear_cookie=True
)
def navbar() -> rx.Component:
return rx.hstack(
rx.link("Home", href="/"),
rx.spacer(),
rx.cond(
SiteState.is_authenticated,
rx.hstack(
rx.text("Hi, ", SiteState.username),
rx.button("Log out", on_click=SiteState.sign_out),
),
rx.link("Sign in", href="/login"),
),
width="100%",
padding="1rem",
)
def dashboard_page() -> rx.Component:
return rx.vstack(
navbar(),
rx.heading("Dashboard"),
rx.cond(
SiteState.is_staff,
rx.badge("Staff"),
rx.fragment(),
),
rx.text("Theme from session: ", SiteState.username), # bind real vars as needed
padding="2rem",
)
def login_page() -> rx.Component:
return rx.center(
rx.card(
rx.heading("Sign in"),
rx.input(
placeholder="Username",
value=SiteState.login_username,
on_change=SiteState.set_login_username,
),
rx.input(
placeholder="Password",
type="password",
value=SiteState.login_password,
on_change=SiteState.set_login_password,
),
rx.cond(
SiteState.login_error != "",
rx.callout(SiteState.login_error, color_scheme="red"),
),
rx.button("Sign in", on_click=SiteState.submit_login, width="100%"),
padding="1.5rem",
),
min_height="80vh",
)
myapp/myapp.py
import reflex as rx
from reflex_django.auth import login_required
from myapp.state import dashboard_page, login_page
app = rx.App()
app.add_page(dashboard_page, route="/", title="Dashboard")
app.add_page(login_page, route="/login", title="Sign in")
# Optional: wrap page function for client-side login gate
@rx.page(route="/settings")
@login_required
def settings_page():
return rx.heading("Settings")
Use add_auth_pages(app) instead of a custom login page when you want batteries-included UI (Canned auth pages below).
AppState API reference¶
Import:
AppState extends DjangoUserState and is the recommended base for dashboards and ModelCRUDView CRUD states.
Handlers (server): self.request, self.django_request, self.user, self.session.
UI (reactive): self.is_authenticated, self.username, self.email, …
See Accessing the Django request on AppState for full self.request examples.
self.request and self.django_request¶
| Property | Type | Role |
|---|---|---|
self.request |
DjangoStateRequest |
.user, .GET, .path, context-processor keys, .context dict |
self.django_request |
HttpRequest \| None |
Raw Django request from the event bridge |
class OrdersState(AppState):
@rx.event
async def export_csv(self):
if not self.request.user.is_staff:
return await self.on_permission_denied()
tenant = self.request.GET.get("tenant")
rows = await Order.objects.filter(tenant_id=tenant).aiterator()
...
Equivalent: from reflex_django import request then request.user, request.GET.get("tenant") in any rx.State.
self.user (property)¶
Returns the live Django user for the current event (AnonymousUser when logged out). Supports everything on your user model: is_authenticated, username, email, is_staff, is_superuser, and user.groups (use async ORM or prefetch in handlers).
class OrdersState(AppState):
@rx.event
async def ship_order(self, order_id: int):
if not self.user.is_authenticated:
return rx.toast.error("Sign in required")
order = await Order.objects.aget(pk=order_id, customer=self.user)
...
Equivalent without AppState: from reflex_django import current_user then user = current_user(), or self.request.user when you subclass AppState.
self.session (property)¶
A SessionProxy over Django’s session for this event. Reads and writes persist to the session backend; __setitem__ and __delitem__ call save() automatically.
class PreferencesState(AppState):
@rx.event
async def set_theme(self, theme: str):
self.session["theme"] = theme # auto-saved
@rx.event
async def clear_theme(self):
del self.session["theme"]
@rx.event
async def load_theme(self) -> str:
return self.session.get("theme", "light")
For bulk updates, you can call await self.session.asave() explicitly after several in-memory changes if you bypass the proxy’s setters.
Snapshot fields (reactive UI)¶
| Field | Meaning |
|---|---|
user_id |
Primary key or None |
username |
get_username() |
email |
Email address |
first_name, last_name |
Profile names |
is_authenticated |
Logged in |
is_staff, is_superuser |
Django flags |
group_names |
List of group names when loaded |
Refresh manually:
await self.refresh_django_user_fields()
# or (updates every DjangoUserState substate, including DjangoAuthState)
await self.sync_from_django(include_groups=True)
sync_from_django walks the client state tree and refreshes every DjangoUserState substate and DjangoAuthState (snapshot fields such as username), not only the handler’s node.
For DjangoAuthState, use DjangoAuthState.is_authenticated in UI (@rx.var from current_user()). Do not expect the legacy inherited path …django_user_state.is_authenticated on the auth branch—that parent substate no longer exists on the flat auth class.
Group names are only loaded when REFLEX_DJANGO_USER_SNAPSHOT_INCLUDE_GROUPS is True or include_groups=True is passed.
await self.has_perm(perm: str) -> bool¶
Async wrapper around Django’s user.has_perm("app_label.codename").
if await self.has_perm("billing.change_invoice"):
await self._save_invoice()
else:
return await self.on_permission_denied()
await self.has_group(name: str) -> bool¶
Checks membership by group name. Uses group_names when already loaded; otherwise one async DB query.
await self.login(username, password, *, login_fields=None) -> bool¶
Uses Django’s aauthenticate (via aauthenticate_login_fields) and alogin. On success, saves the session and refreshes snapshot fields. Returns False and calls on_auth_failed() when credentials fail or no request is bound.
login_fields defaults to REFLEX_DJANGO_AUTH["LOGIN_FIELDS"] or ("username",).
await self.logout() -> None¶
Calls alogout, saves the session, and refreshes snapshot fields. Pair with cookie sync JS when you need the browser’s sessionid updated (Session cookie sync).
Hooks¶
class BrandedState(AppState):
async def on_auth_failed(self):
self.login_error = "We could not sign you in."
return rx.toast.error("Invalid credentials")
async def on_permission_denied(self):
return rx.redirect("/forbidden")
Default on_permission_denied shows a generic error toast.
Server-side authorization (without decorators)¶
Use these inside any rx.State or AppState handler:
from reflex_django import current_user, require_login_user
from reflex_django.auth import auser_has_perm, ReflexDjangoAuthError
@rx.event
async def delete_item(self, item_id: int):
try:
user = require_login_user()
except ReflexDjangoAuthError:
return rx.toast.error("Sign in required")
if not await auser_has_perm(user, "shop.delete_product"):
return rx.toast.error("Permission denied")
# safe to delete
require_login_user() raises when the user is anonymous—useful when you want explicit error handling instead of rx.redirect.
Decorators¶
Import from reflex_django.auth or from reflex_django import login_required, permission_required.
@login_required¶
Works on page functions (no self) and event handlers (async def handler(self, ...)).
from reflex_django.auth import login_required
# Page: UI gate using DjangoAuthState.is_authenticated (@rx.var) + redirect on mount
@rx.page(route="/dashboard")
@login_required
def dashboard():
return rx.heading("Members only")
# Event: server check + rx.redirect when anonymous
class SecretState(AppState):
@rx.event
@login_required(login_url="/login")
async def load_secret(self):
return {"data": "classified"}
| Parameter | Default | Role |
|---|---|---|
login_url |
REFLEX_DJANGO_LOGIN_URL |
Redirect target for anonymous users on events |
Warning: Page decorators only affect what the client renders first. Always protect events that return or mutate private data.
@permission_required¶
Event handlers enforce the permission with auser_has_perm. Pages gate on login (and optional fallback component); enforce fine-grained permissions in on_load events or handler methods.
from reflex_django.auth import permission_required
class CatalogState(AppState):
@rx.event
@permission_required("products.view_product", redirect="/login")
async def load_catalog(self):
return await Product.objects.all().values_list("name", flat=True)
@rx.event
@permission_required(
"products.delete_product",
on_denied=lambda self: rx.toast.error("Not allowed"),
)
async def delete_product(self, product_id: int):
await Product.objects.filter(pk=product_id).adelete()
| Parameter | Role |
|---|---|
perm |
Django permission string (app_label.codename) |
redirect |
rx.redirect target when denied (handlers) |
login_url |
Used when anonymous (falls back to redirect) |
fallback |
Page-only: component factory when not authenticated |
on_denied |
Event-only: callable(state) return value (e.g. toast, redirect) |
If on_denied is omitted and the state defines on_permission_denied, that method is awaited.
Settings¶
| Setting | Default | Meaning |
|---|---|---|
REFLEX_DJANGO_AUTH_AUTO_SYNC |
True |
Refresh AppState snapshot vars on each event |
REFLEX_DJANGO_USER_SNAPSHOT_INCLUDE_GROUPS |
False |
Include group_names in sync (extra query) |
REFLEX_DJANGO_LOGIN_URL |
/login |
Default redirect for decorators |
REFLEX_DJANGO_AUTH |
(see README) | Canned pages, routes, LOGIN_FIELDS, messages |
Disable auto-sync if you need to minimize per-event work and will call sync_from_django yourself:
Browser storage: Django sessionid vs Reflex token (for Django developers)¶
If you are used to Django-only apps, login usually means: the browser gets a sessionid cookie, Django loads the session row from the database (or cache), and request.user is set in middleware. Reflex adds a second client identifier that is easy to confuse with “the session” because it is also called token and lives in the browser—but it is not your Django session and not a JWT or API key.
Three different things in the browser¶
| What | Where | Who owns it | What it means |
|---|---|---|---|
sessionid (name from SESSION_COOKIE_NAME, default sessionid) |
HTTP cookie | Django | Primary key into Django’s session store. This is who is logged in. The bridge reads it from router_data.headers.cookie (or the real cookie on full page loads) and builds request.user via aget_user. |
csrftoken (from CSRF_COOKIE_NAME, default csrftoken) |
HTTP cookie | Django | CSRF protection for unsafe HTTP requests. Often sent with Reflex events when present; cleared on logout together with sessionid. |
token |
sessionStorage (key literally named token) |
Reflex | Per-tab client id (UUID). Same value as self.router.session.client_token in Python. Used to associate this browser tab with Reflex server state over the WebSocket—not with Django’s django_session table. |
Other keys you may see in sessionStorage (for example react-router-scroll-positions) are Reflex/router UI cache. They are unrelated to Django login.
Do not confuse Reflex’s token with:
- Django’s
sessionidcookie (session auth), - Password-reset URL segments named
[token]or[key](Django’s one-time reset token), - A bearer/API token you might add in a custom API.
Where each piece is used in the pipeline¶
flowchart TB
subgraph browser [Browser tab]
SS["sessionStorage.token\n(Reflex client UUID)"]
CK["Cookie: sessionid, csrftoken\n(Django)"]
end
subgraph reflex_server [Reflex + reflex-django]
WS[WebSocket / Socket.IO event]
RD[router_data on state tree]
Bridge[DjangoEventBridge]
Req[Synthetic HttpRequest]
User[request.user via aget_user]
end
subgraph django_store [Django]
SessRow[(Session row in DB/cache)]
end
SS --> WS
CK --> RD
CK --> WS
WS --> RD
RD --> Bridge --> Req
Req --> SessRow
SessRow --> User
- Full page load (HTTP) — The browser sends
sessionidin theCookieheader. Django middleware (on admin, API routes, etc.) loads the session as usual. - Reflex event (WebSocket) — The client sends
router_data(path, headers, and often a copy of cookies).DjangoEventBridgebuilds a syntheticHttpRequest, copies cookies intorequest.COOKIES, loads the session withSESSION_ENGINE, and resolvesrequest.user—the same as a view, without running the fullMIDDLEWAREstack. - Reflex
token— Identifies this tab’s connection to Reflex state on the server (hydration, reconnect, background tasks). It does not replacesessionid; Django auth for handlers still comes from the session cookie (and mirrored cookie string inrouter_data).
Login: what changes¶
| Step | Django | Browser |
|---|---|---|
await self.login(...) / alogin |
Creates/updates the session row and binds the user | — |
session_async_save |
Persists the session backend | — |
mirror_auth_cookies_to_state_tree |
— | Copies sessionid into router_data.headers.cookie on the Reflex state tree so later events without cookie headers still see the new session |
_sync_session_cookie_then_nav |
— | sessionStorage.clear() (drops stale Reflex token), expires old sessionid in JS, writes new sessionid, then navigates (e.g. to /) |
So after login you want both: a valid Django session row and the browser holding the matching sessionid cookie (plus a fresh Reflex client id after storage clear).
Logout: what must be cleared¶
| Step | Django | Browser / Reflex |
|---|---|---|
await self.logout() / alogout |
flush session row; user is anonymous on the server |
— |
strip_auth_cookies_from_request |
Removes sessionid / csrftoken from the synthetic request for this event |
— |
clear_auth_cookies_from_state_tree |
— | Strips sessionid / csrftoken from persisted router_data on all substates |
_sync_session_cookie_then_nav(..., clear_cookie=True) |
— | Expires sessionid and csrftoken cookies, sessionStorage.clear() and localStorage.clear(), then navigates to /login |
If you only clear Django’s session row and cookies but leave the old Reflex token in sessionStorage, the tab can reconnect with a stale Reflex state (still showing authenticated UI) while cookies are empty—classic / ↔ /login redirect loop. That is why logout clears client storage as well as cookies via browser_auth_logout_clear_js in reflex_django.session_js.
Mental model (one paragraph)¶
Django answers “who is this user?” using the sessionid cookie and the session table. Reflex answers “which tab / which copy of UI state?” using sessionStorage.token and server-side state keyed by that client id. reflex-django wires Django into Reflex events by rebuilding request.user from cookies on every event; you must keep cookies, router_data cookie mirrors, and Reflex client storage in sync on login and logout because Reflex does not run SessionMiddleware or send Set-Cookie for you on Socket.IO events.
Session cookie sync after login¶
Reflex events do not run SessionMiddleware, so alogin may not send Set-Cookie to the browser. Without syncing, the next full page load can still send an old sessionid. See Browser storage: Django sessionid vs Reflex token for the full picture.
After await self.login(...) or registration, mirror the cookie and navigate:
from reflex_django.context import current_request
from reflex_django.mixins.session_auth import _sync_session_cookie_then_nav
request = current_request()
if request is not None:
return _sync_session_cookie_then_nav(request, "/") # post-login path
Logout with cookie clear (session + CSRF in the browser, plus server session flush):
DjangoAuthState.logout and session_auth_mixin handlers do this automatically. They also clear Reflex client sessionStorage (websocket token, scroll cache) and localStorage on logout via browser_auth_logout_clear_js. Login clears sessionStorage before writing the new sessionid. Lower-level helpers: browser_auth_logout_clear_js, browser_auth_cookies_clear_js, session_cookie_set_js, session_cookie_clear_js from reflex_django.session_js.
HttpOnly cookies: JS cookie mirroring cannot set HttpOnly; see Django SESSION_COOKIE_HTTPONLY tradeoffs in production.
AppState + ModelCRUDView¶
CRUD states should inherit AppState so list/save/delete handlers can use self.request.user (or self.user) and default login_required wrapping. ModelCRUDView.dispatch calls bind_request_context() before hooks run—use self.request inside get_queryset, get_create_kwargs, etc.
from reflex_django.state import AppState, ModelCRUDView
from reflex_django.state.mixins.scoping import UserScopedMixin
class NotesState(AppState, ModelCRUDView, UserScopedMixin):
scope_field = "user_id"
class Meta:
serializer = NoteSerializer
list_var = "notes"
def get_queryset(self, ctx):
# self.user is available in hooks
return super().get_queryset(ctx).filter(user=ctx.user)
Generated handlers call require_login_user() when the action is listed in Meta.login_required_actions.
DjangoUserState without AppState¶
DjangoUserState is the same auth mixin without AppStateMeta / CRUD assembly. Use it for a navbar-only state or with session_auth_mixin:
from reflex_django import DjangoUserState
from reflex_django.mixins import SessionAuthConfig, session_auth_mixin
LoginState = session_auth_mixin(
SessionAuthConfig(
post_login_redirect="/",
post_logout_redirect="/login",
),
base=DjangoUserState,
)
DjangoAuthState (canned auth pages) is a flat Reflex substate: AuthBridgeMixin + rx.State, with login/register/reset mixins merged in one class (not a nested DjangoUserState → DjangoAuthState substate chain).
| Field / API | Role on DjangoAuthState |
|---|---|
user_id, username, email, … |
Owned snapshot fields (synced like DjangoUserState) |
is_authenticated |
@rx.var — live current_user().is_authenticated for UI and @login_required |
sync_from_django |
Same tree sync as DjangoUserState (delegated implementation) |
sync_auth_ui |
Optional: refresh snapshots + dirty-mark on auth substate after layout mount |
logout, submit_login, … |
Session auth events from session_auth_mixin |
Sidebar / layout example:
from reflex_django.auth.state import DjangoAuthState
def sidebar_logout():
return rx.cond(
DjangoAuthState.is_authenticated,
rx.button("Log out", on_click=DjangoAuthState.logout),
rx.fragment(),
)
App dashboards that use AppState / OverviewState should keep using that branch’s snapshot is_authenticated (or self.request.user in handlers). Mixing branches is fine: Welcome text can come from load_metrics + self.request.user while logout uses DjangoAuthState.is_authenticated.
Canned auth pages¶
Settings (backend/settings.py):
REFLEX_DJANGO_AUTH = {
"SIGNUP_ENABLED": True,
"PASSWORD_RESET_ENABLED": True,
"LOGIN_URL": "/login",
"SIGNUP_URL": "/register",
"LOGIN_REDIRECT_URL": "/",
"LOGIN_FIELDS": ["username"], # or ["email"] or ["username", "email"]
}
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
DEFAULT_FROM_EMAIL = "noreply@localhost"
App module:
from reflex_django.auth import add_auth_pages, login_required
app = rx.App()
add_auth_pages(app)
app.add_page(index, route="/", on_load=...) # your pages
@login_required
def dashboard():
return rx.heading("Members only")
Pages: LoginPage, RegisterPage, PasswordResetPage, PasswordResetConfirmPage. Customize via BaseAuthPage hooks or REFLEX_DJANGO_AUTH["MESSAGES"].
See README authentication section for the full REFLEX_DJANGO_AUTH key table.
Choosing an API¶
| Goal | Recommended API |
|---|---|
Navbar / rx.cond on login |
AppState snapshot fields + auto-sync |
| Check permission in handler | await self.has_perm(...) or @permission_required |
| Scoped queryset | self.user in get_queryset / filter(user=self.user) |
| Custom login form | await self.login(...) + _sync_session_cookie_then_nav |
| Ready-made auth UI | add_auth_pages(app) + DjangoAuthState |
| No AppState in this class | current_user() in plain rx.State |
Troubleshooting¶
| Symptom | Likely cause | Fix |
|---|---|---|
current_user() always anonymous |
Event bridge off | install_event_bridge=True on plugin |
self.request.user always anonymous |
Same as above | Enable bridge; ensure sessionid cookie is sent |
AttributeError on self.request outside event |
No bridged request | Only access inside @rx.event handlers |
self.request.SITE_NAME missing |
Processors not loaded | Set REFLEX_DJANGO_CONTEXT_PROCESSORS; use CRUD dispatch or bind_request_context |
Confused request.user in UI |
User model in component tree | Use self.username / self.is_authenticated in rx.* |
| Login works once, next event anonymous | Browser cookie stale | _sync_session_cookie_then_nav after login |
| UI shows logged out while handler sees user | Snapshot not synced | Enable REFLEX_DJANGO_AUTH_AUTO_SYNC or on_load=State.sync_from_django |
/ and /login redirect loop after re-login |
Logout strips sessionid from persisted router_data; login only updated the browser cookie; events without cookies saw anonymous on / but authenticated on /login |
Upgrade reflex-django (mirrors session into router_data on login + fixes cookie merge in the event bridge); pip install -e and restart Reflex |
Loop fixed only after clearing session storage in devtools (token, react-router-scroll-positions) |
Stale Reflex websocket token in sessionStorage reconnects to old server state after logout |
Upgrade reflex-django (browser_auth_logout_clear_js on logout); restart Reflex |
| Logout button flashes then hides | UI bound to inherited django_user_state.is_authenticated snapshot, or stale snapshot on auth branch |
Use DjangoAuthState.is_authenticated (@rx.var); reinstall editable package and restart Reflex |
print(DjangoAuthState.is_authenticated) shows a Var, not True/False |
Expected in Python component code | Vars are reactive; test in the browser or in an event handler with current_user() |
| Logout hidden on small screens | Sidebar display breakpoints hide the column |
Show sidebar from md/lg up (see your sidebar.py display=[...]) |
RuntimeError: No Django session |
Handler outside event / bridge failed | Ensure bridge runs; check logs for preprocess errors |
| Permission always denied | Wrong codename or user lacks perm | Verify in Django admin; use user.has_perm in shell |
ImportError: _session_async_save |
Old import path | Use from reflex_django.state.auth_bridge import session_async_save |
Security checklist¶
- Authorize mutations with
self.request.user,self.user,require_login_user(), orhas_perm—notis_authenticatedalone on the client. - Use
@login_required/@permission_requiredon events that return private data. - Use a stable
SECRET_KEYin production (password reset tokens). - Set
SIGNUP_ENABLED=Falseif only admins may create users. - Scope querysets to
self.request.user(orUserScopedMixin) for multi-user data.
See also¶
- State management — plain
rx.Statevs helpers - Django middleware to Reflex — bridge internals
- CRUD with mixins —
ModelCRUDView - Best practices
Navigation: ← Django Middleware in Reflex | Next: Database Integration →