Skip to content

How Auth Works

Auth touches both halves of the stack, so it’s easier to understand as one loop than as separate backend and frontend pieces.

The frontend posts to /login/access-token with email and password (the OAuth2 spec calls the email field “username” — that’s just the field name, not a separate concept). The backend checks the password with pwdlib against the stored hash, then signs a JWT: sub is the user’s ID, exp is 8 days out, signed with SECRET_KEY. That’s the whole token — no session, no server-side state to look up later.

useAuth.ts’s login() stores the token in localStorage. From then on, main.tsx’s client config reads it back out and attaches it as a Bearer header to every request automatically:

client.setConfig({
auth: () => localStorage.getItem("access_token") || "",
})

You never manually add an auth header anywhere — every generated *Service call already goes through this.

Add current_user: CurrentUser as a parameter. That type alias (app/api/deps.py) decodes the JWT, looks up the user, and checks they’re active — all before your function body runs:

def some_route(current_user: CurrentUser, session: SessionDep):
...

For admin-only routes, use get_current_active_superuser instead — same idea, with an is_superuser check on top.

_layout.tsx’s beforeLoad redirects to /login if isLoggedIn() is false. That check only looks for the token’s presence in localStorage — it doesn’t validate it. An expired token still gets you past this check and onto the page.

Password reset is a separate, shorter-lived token (verify_password_reset_token in app/utils.py) — not a login token, and not something you’d reuse this flow for.