Try an interactive version of this dialog: Sign up at solve.it.com, click Upload, and pass this URL.

Note: 85

Modifying HTMX to Accept a Client

When testing FastHTML apps with authentication in notebooks, the default HTMX function creates a fresh TestClient for each render. This means session cookies (like auth) don't persist, making it impossible to preview authenticated content.

Note: 12

Setup: A Simple App with Auth

Code: 367 ()

from fasthtml.common import *
from fasthtml.jupyter import HTMX, JupyUvi

# Auth beforeware - redirects to /login if not authenticated
def before(req, sess):
    auth = req.scope['auth'] = sess.get('auth', None)
    if not auth: return RedirectResponse('/login', status_code=303)

bware = Beforeware(before, skip=['/login', '/'])

app = FastHTML(before=bware)
rt = app.route
srv = JupyUvi(app)

@rt('/login')
def get(): return Titled("Login", Form(
    Input(id='username', placeholder='Username'),
    Button('Login'), action='/login', method='post'))

@rt('/login')
def post(username: str, sess):
    sess['auth'] = username
    return RedirectResponse('/', status_code=303)

@rt('/')
def home(): return Titled("Home", P("Welcome! You are logged in."))

# A component that requires auth context
def UserGreeting(name):
    return Div(H2(f"Hello, {name}!"), P("This is your dashboard."), cls="p-4 bg-blue-100 rounded")

Note: 67

The Problem

When you try to render a component using the default HTMX, it creates a new TestClient internally. This client has no session cookies, so any routes requiring auth will fail or redirect:

Code: 39 ()

# This won't work - HTMX creates a fresh client with no auth
HTMX(UserGreeting("Alice"), app=app)

Output: 1,008

Note: 36

The rendered iframe shows the login page instead of our component, because the internal TestClient has no auth session.

Note: 61

The Solution

Add an optional client parameter to HTMX. If provided, reuse that client instead of creating a new one. This lets you log in once and reuse the authenticated client:

Code: 706 ()

from html import escape
from fasthtml.jupyter import unqid
from starlette.testclient import TestClient
from IPython.display import HTML

def HTMX(path="/", host='localhost', app=None, port=8000, height="auto", 
         link=False, iframe=True, client=None):
    "An iframe which displays the HTMX application in a notebook. Pass `client` to reuse a TestClient with persistent session."
    if isinstance(height, int): height = f"{height}px"
    scr = """{
        let frame = this;
        window.addEventListener('message', function(e) {
            if (e.source !== frame.contentWindow) return;
            if (e.data.height) frame.style.height = (e.data.height+1) + 'px';
        }, false);
    }""" if height == "auto" else ""
    proto = 'http' if host=='localhost' else 'https'
    fullpath = f"{proto}://{host}:{port}{path}" if host else path
    src = f'src="{fullpath}"'
    if link: display(HTML(f'<a href="{fullpath}" target="_blank">Open in new tab</a>'))
    if isinstance(path, (FT,tuple,Safe)):
        assert app, 'Need an app to render a component'
        route = f'/{unqid()}'
        res = path
        app.get(route)(lambda: res)
        # KEY CHANGE: Use provided client or create new one
        cli = client if client is not None else TestClient(app)
        page = cli.get(route).text
        src = f'srcdoc="{escape(page)}"'
    if iframe:
        return HTML(f'<iframe {src} style="width: 100%; height: {height}; border: none;" onload="{scr}" ' + """allow="accelerometer; autoplay; camera; clipboard-read; clipboard-write; display-capture; encrypted-media; fullscreen; gamepad; geolocation; gyroscope; hid; identity-credentials-get; idle-detection; magnetometer; microphone; midi; payment; picture-in-picture; publickey-credentials-get; screen-wake-lock; serial; usb; web-share; xr-spatial-tracking"></iframe> """)

Note: 30

Usage

Create a TestClient, log in once, then reuse it for all renders:

Code: 94 ()

# Create client and log in once
cli = TestClient(app)
cli.post('/login', data={'username': 'Alice'})

# Create a shorthand for rendering with our authenticated client
def s(*c): return HTMX(*c, host=None, port=None, app=app, client=cli)

Code: 27 ()

# Now this works! The component renders with auth context
s(UserGreeting("Bob"))

Output: 949

Note: 115

Summary

The change is minimal - just one line in HTMX:

Copied!
# Before (always creates new client):
cli = TestClient(app)

# After (reuses provided client if given):
cli = client if client is not None else TestClient(app)

This enables a workflow where you log in once and preview authenticated components throughout your notebook session.