2026, Jan 12 03:00

Serve Anonymous and Logged-In Users from One FastAPI Route with a Permissive Current User Dependency

Build consistent FastAPI routing for anonymous and authenticated users with a tolerant current-user dependency that returns None, enabling a single endpoint.

Routing for both anonymous and authenticated users is a common need in FastAPI apps. The tricky part starts when identity-aware dependencies are involved. If a route lacks the expected auth dependency, you may see surprising behavior, like a user being treated as logged out when navigating between pages. Below is a clear pattern to keep routes accessible to everyone and, at the same time, surface the current user to templates when they are logged in.

The setup that led to inconsistent behavior

The initial approach was to split each public page into two endpoints: one for anonymous visitors and another for logged-in users. That worked as a quick patch, but it quickly became unmaintainable and produced odd session behavior when clicking across links that did or did not require the auth dependency.

# router to index page for unauthenticated visitors
@app.get('/', include_in_schema=False)
def landing(req: Request):
    return ui.templates.TemplateResponse('index.html', context={'request': req})


# router to the same page, but for authenticated users (logged-in)
@app.get('/home', include_in_schema=False)
def landing(req: Request, principal: User = Depends(security.provide_principal)):
    return ui.templates.TemplateResponse('index.html', context={'request': req, 'actor': principal})

Templates then mirrored the split with conditional links that pointed to different URLs depending on whether a user object was present or not.

{% if not actor %}
  <a class="navbar-brand" href="http://127.0.0.1:8000/">Brand</a>
{% else %}
  <a class="navbar-brand" href="http://127.0.0.1:8000/home">Brand</a>
{% endif %}

Why this caused problems

The core of the issue was the identity dependency itself. The function that provides the current user only returned a user when someone was actually logged in. When a route didn’t declare that dependency, navigation could make the app look like it had “logged the user out,” only for the session to appear active again on routes that did include the dependency. The behavior felt random because the dependency was mandatory on some routes and completely absent on others.

This made the application toggle identities depending on the route definition rather than the real session state, which is confusing for users and painful for maintenance.

The fix: make the identity dependency tolerant

The solution was minimal and effective: update the function that supplies the current user to return None when the user isn’t logged in. With that change, any route can safely depend on it without forcing a logout or requiring a separate “anonymous” endpoint.

async def provide_principal(token: User = Depends(token_scheme)):
    if not token:
        return None
    account = mock_decode_token(token)
    return account

Once the dependency returns None for anonymous visitors, there’s no need to maintain two routes per page. A single route can serve both cases, and templates can receive the same context key whether a user is present or not.

@app.get('/', include_in_schema=False)
def landing(req: Request, principal: User = Depends(security.provide_principal)):
    return ui.templates.TemplateResponse(
        'index.html',
        context={'request': req, 'actor': principal}
    )

Links in templates also become straightforward and stable.

<a class="navbar-brand" href="http://127.0.0.1:8000/">Brand</a>

Why this matters

By having the identity provider return None when no user is logged in, routes behave consistently regardless of authentication state. You get a single URL per page, fewer conditionals in your templates, and no accidental logouts when crossing routes that don’t declare the dependency. It also simplifies your mental model: every page can ask for the current user, and if there isn’t one, the page still works—just with anonymous context.

Takeaways

Keep the “who is the current user” dependency permissive for public pages, and let it return None when the user isn’t authenticated. Serve both anonymous and logged-in visitors through the same endpoint, pass the user object (or None) into the template, and avoid proliferating duplicate routes and conditional links. This small adjustment eliminates inconsistent navigation, trims code duplication, and keeps your FastAPI app’s session handling predictable.