For AI agents: the complete documentation index is at llms.txt. Markdown versions are available by appending .md or sending Accept: text/markdown.
Reflex Logo
Docs Logo

New in reflex-enterprise v0.9.1.

Testing Guarded Code

When the AuthPlugin is enabled, every non-exempt page, event handler, base field, and computed var is secure by default. The logic worth testing is usually the auth=<callable> authorization checks that decide who may see a value or run a handler.

Because a check is an ordinary function that takes a context object and returns a bool, you can test it directly with no network, IdP, or browser. Use a local mock provider when the OIDC wiring itself is under test.

A check is a function

An authorization check receives a single context object and returns a bool (or an awaitable of one). It reads the user's claims from ctx.auth_user_state.userinfo:

from reflex_enterprise.auth import VarAuthContext


def is_staff(ctx: VarAuthContext) -> bool:
    return "staff" in (ctx.auth_user_state.userinfo.get("groups") or [])

To test it, build a context around an AuthUserState carrying the claims for that case, and call the check. Set the claims on the private _userinfo attribute; the check reads them back through the public userinfo property:

from reflex_enterprise.auth import AuthUserState, VarAuthContext

from my_app.auth import is_staff


def _ctx(userinfo) -> VarAuthContext:
    """A VarAuthContext presenting ``userinfo`` as the current user."""
    user = AuthUserState()
    user._userinfo = userinfo
    return VarAuthContext(auth_user_state=user, field_or_var=None)


def test_is_staff_allows_member():
    assert is_staff(_ctx({"sub": "u1", "groups": ["staff"]})) is True


def test_is_staff_denies_non_member():
    assert is_staff(_ctx({"sub": "u2", "groups": ["guests"]})) is False

The context classes are exported from reflex_enterprise.auth. Build the one that matches the guarded surface:

ContextConstruct as
VarAuthContextVarAuthContext(auth_user_state=user, field_or_var=None)
EventAuthContextEventAuthContext(auth_user_state=user, event_handler=None, payload={})
PageAuthContextPageAuthContext(auth_user_state=user)

A check typed with the AuthContext union works on any surface. Test it through the simplest matching context, usually VarAuthContext.

Async checks

Async checks are tested the same way. Pass the claims into the test context and await the check:

import pytest


@pytest.mark.asyncio
async def test_async_check():
    assert await my_async_check(_ctx({"sub": "u1", "groups": ["admins"]})) is True

Use the mock IdP flow below when the OIDC wiring itself or live Reflex state is under test.

End-to-end against a mock IdP

To exercise the OIDC flow, including the login redirect, /callback token exchange, JWKS validation, userinfo fetch, and async checks that touch real state, run the app against a local mock identity provider.

is a small OIDC server that runs in-process. Add it as a dev dependency:

uv add --dev oidc-provider-mock

Run it on a background thread and point the OIDC_* env vars at it before the app starts. It accepts any client credentials by default and issues refresh tokens:

import os

import pytest
from oidc_provider_mock import User, run_server_in_thread


@pytest.fixture(scope="session")
def mock_idp():
    """Run a local mock OIDC IdP and point the OIDC_* env vars at it."""
    env = {
        # Lets the mock IdP (authlib, server-side) serve OAuth over plain HTTP.
        # Redundant for a localhost issuer, needed for a non-localhost HTTP host.
        "AUTHLIB_INSECURE_TRANSPORT": "1",
        "OIDC_CLIENT_ID": "test-client",
        "OIDC_CLIENT_SECRET": "test-secret",
    }
    # Save and restore to avoid leaking OIDC_* into other tests.
    saved = {key: os.environ.get(key) for key in [*env, "OIDC_ISSUER_URI"]}
    os.environ.update(env)
    users = [
        User(sub="user-1", claims={"name": "Ada Lovelace", "groups": ["admins"]}),
    ]
    try:
        with run_server_in_thread(user_claims=users) as server:
            os.environ["OIDC_ISSUER_URI"] = f"http://localhost:{server.server_port}"
            yield server
    finally:
        for key, value in saved.items():
            if value is None:
                os.environ.pop(key, None)
            else:
                os.environ[key] = value
Expand

The login flow is browser-driven: redirects, cookies, and websocket state. Exercise it with AppHarness (from reflex.testing) and a browser driver such as Playwright. With the mock_idp fixture above active, drive the auth-specific steps and assert that a protected value is delivered:

import re

from playwright.sync_api import Page, expect


def test_login_delivers_protected_value(auth_app, page: Page):
    base = auth_app.frontend_url.rstrip("/")
    # A protected page bounces an anonymous visitor to /login.
    page.goto(f"{base}/dashboard")
    page.wait_for_url(re.compile(r"/login"))
    # Click the app's login button (labelled "Login with {display_name()}").
    # This starts the OIDC redirect to the IdP's authorize page.
    page.get_by_role("button", name="Login with Generic").click()
    # Authorize on the IdP. oidc-provider-mock authorizes with one passwordless
    # click per predefined user; a real IdP shows its own login/consent form here.
    page.locator('button[name="sub"][value="user-1"]').click()
    # Control returns to the app; the callback exchanges tokens and the
    # protected value is now delivered.
    page.wait_for_url(lambda url: url.startswith(base) and "callback" not in url)
    expect(page.get_by_text("Ada Lovelace")).to_be_visible()

The auth_app fixture starts your app under AppHarness with mock_idp active; page is the standard Playwright fixture. oidc-provider-mock also ships a CLI for standalone manual testing.