Back to Reflex

Authentication Overview

docs/enterprise/auth/overview.md

0.9.77.6 KB
Original Source

New in reflex-enterprise v0.9.1.

Authentication Overview

rxe.AuthPlugin adds OIDC (OpenID Connect) authentication to Reflex apps. Add the plugin to rxe.Config(plugins=[...]), set the provider environment variables, and use rxe.App().

By default, every page, event handler, base field, and computed var requires an authenticated user. Use auth=False for surfaces that should be public.

The plugin runs the OIDC Authorization Code + PKCE flow against your identity provider (IdP) and registers /login, /logout, /callback, and /forbidden routes.

md
# Requirements
The auth plugin ships with `reflex-enterprise` (v0.9.1+). Your app **must** use `rxe.App()` (not `rx.App()`), and you must configure an OIDC identity provider via environment variables. Using a plain `rx.App()` with the plugin raises a `ConfigError` at startup.

Quickstart

1. Add rxe.AuthPlugin() to rxconfig.py and configure your OIDC provider through the OIDC_* environment variables:

python
import os

import reflex as rx
import reflex_enterprise as rxe

os.environ.setdefault("OIDC_ISSUER_URI", "https://your-idp.example.com")
os.environ.setdefault("OIDC_CLIENT_ID", "your-client-id")
os.environ.setdefault("OIDC_CLIENT_SECRET", "your-client-secret")  # optional with PKCE

config = rxe.Config(
    app_name="my_app",
    plugins=[
        rxe.AuthPlugin(),
    ],
)

With the OIDC_* variables set, the app imports and compiles before the IdP is reachable. OIDC discovery runs only when a user logs in. Placeholder values are enough for local builds and CI.

2. Use rxe.App() (not rx.App()) in your app module:

python
import reflex_enterprise as rxe

app = rxe.App()

3. Register the redirect URI with your IdP. Add the plugin's auth_callback_endpoint (/callback by default) as an allowed redirect URI in your identity provider's client settings. Register the full URL (scheme, host, and path) for each environment. A mismatched value produces redirect_uri_mismatch; see deploying to production for the exact callback URL.

You don't need to write a provider class. The plugin uses the built-in GenericOIDCAuthState, which reads those variables. See providers for named and multi-provider setups.

The four protected surfaces

Once active, the plugin protects four kinds of surface by default:

SurfaceDefaultHow it's withheldOpt out / gate
Pages (@rxe.page / @rx.page / app.add_page)login requiredredirect to /loginauth=False, or @rxe.page(auth=True) to force login
Event handlers (@rxe.event)login requiredblock + redirect/toast@rxe.event(auth=False) or auth=<check>
Base fields (rxe.field / plain rx.field)withheld until loginreplaced with its declared defaultrxe.field(default, auth=False) or auth=<check>
Computed vars (@rxe.var)withheld until loginreplaced with its initial_value (dropped if it has none)@rxe.var(auth=False) or auth=<check>

auth=True is the default everywhere, so a plain rx.field(...) or bare @rxe.var is already protected. Set auth=False to make a surface public.

python
import reflex as rx
import reflex_enterprise as rxe


class DashboardState(rx.State):
    # Protected by default; withheld from the client until login.
    revenue: rx.Field[float] = rx.field(0.0)

    # Public; always sent to the client.
    theme: rx.Field[str] = rxe.field("light", auth=False)

    @rxe.event  # default auth=True: anonymous callers are redirected to /login
    async def refresh(self): ...

    @rxe.event(auth=False)  # public handler anyone may call
    def toggle_theme(self):
        self.theme = "dark" if self.theme == "light" else "light"

See secure by default for the full enforcement model, the four auth= wrappers, and authorization check functions.

Reading the current user

reflex_enterprise.auth.User is an alias of reflex_enterprise.auth.AuthUserState. Use its class-level Vars directly in components. They are populated by the provider that authenticated the user:

python
import reflex as rx
from reflex_enterprise.auth import User


def profile() -> rx.Component:
    return rx.hstack(
        rx.avatar(src=User.picture, fallback="U"),
        rx.vstack(
            rx.heading(User.name),
            rx.text(User.email, color_scheme="gray"),
        ),
    )

Inside an event handler, await User.current() returns the user's OIDCUserInfo claims dict (or None when anonymous):

python
import reflex as rx
import reflex_enterprise as rxe
from reflex_enterprise.auth import User


class GreetState(rx.State):
    @rxe.event  # default auth=True
    async def greet(self):
        user = await User.current() or {}
        return rx.toast(f"Hello {user.get('name') or user.get('sub')}!")

See reading the current user for the full User API, including frontend Vars, current(), and current_provider().

Signing out

Sign the user out by linking to /logout or by binding the User.logout event:

python
import reflex as rx
from reflex_enterprise.auth import User

# Either link to the logout route.
rx.link("Sign out", href="/logout")

# Or bind the logout event to any component.
rx.button("Sign out", on_click=User.logout)

User.logout signs out whichever provider the user logged in with, so it works with multiple providers. If no one is signed in there's nothing to sign out, so it just sends them back to the home page (/).

Example header:

python
import reflex as rx
from reflex_enterprise.auth import User


def header() -> rx.Component:
    return rx.cond(
        User.sub != "",
        rx.hstack(
            rx.text(f"Signed in as {User.name}"),
            rx.link("Sign out", href="/logout"),
        ),
        rx.link("Log in", href="/login"),
    )

How a login flows end to end

  1. An anonymous visitor hits a protected page (or calls a protected handler) and is redirected to /login, with the requested page preserved as a redirect_to query parameter.
  2. /login renders a button per configured provider. The visitor clicks one and is sent to the IdP's authorization endpoint (Authorization Code + PKCE).
  3. The IdP authenticates the user and redirects back to /callback, which validates the OAuth state (CSRF), exchanges the code for tokens, and stores them in secure cookies.
  4. The user is redirected back to redirect_to. Protected fields, vars, pages, and handlers now resolve against the authenticated user.
  5. /logout clears tokens, resets the protected surface of every state, and chains the provider's logout. A CSRF guard blocks cross-site logout requests (see secure by default).
  • Secure by default: enforcement, auth=, authorization checks, and the User facade.
  • Providers: provider classes, environment variables, scopes, and multi-provider setups.
  • Custom pages: custom /login, /callback, /logout, and /forbidden components.
  • Testing: authorization checks and mock-IdP integration tests.
  • Deploying to production: HTTPS, callback URLs, reverse proxies, and troubleshooting.
  • Enterprise overview: other Enterprise features.