Authentication
Caspian provides a robust, session-based authentication system built on FastAPI's security utilities. It is secure by default (HttpOnly cookies), supports Role-Based Access Control (RBAC), and integrates seamlessly with the async RPC layer.
Configuration
Auth settings are located in
utils/auth.py. You can configure global route protection, define roles, and set
redirect paths here.
Global Protection Switch
By default, routes are public. To make your application private by default, toggle this flag. You then explicitly whitelist public routes (like landing pages).
class AuthConfig: # Set to True to require login for ALL pages by default IS_ALL_ROUTES_PRIVATE = False # Where to go after login/logout DEFAULT_SIGNIN_REDIRECT = "/dashboard" DEFAULT_SIGNOUT_REDIRECT = "/signin" # Exceptions to the rule PUBLIC_ROUTES = ["/", "/about"] AUTH_ROUTES = ["/signin", "/signup"] # Redirects to dashboard if already logged in
The Auth Object
The global auth object manages the
session lifecycle. It abstracts FastAPI's response and cookie logic.
auth.sign_in(data, redirect_to?)
Creates a session. data is a
dict stored in the secure session cookie. Returns a response object
handling the cookie set.
auth.sign_out(redirect_to?)
Destroys the session and clears HttpOnly cookies.
auth.is_authenticated()
Returns True if the current
session is valid.
auth.get_payload()
Retrieves the user data stored during sign-in.
Implementation Example
A complete async sign-in flow. The backend handles verification using Prisma (Async), while the frontend handles the form submission via RPC.
from casp.auth import auth, AuthConfig from casp.prisma.db import prisma from casp.rpc import rpc from werkzeug.security import check_password_hash @rpc() async def do_login(email: str, password: str, next: str | None = None): # 1. Fetch User (Async Await) user = await prisma.user.find_unique( where={'email': email}, include={'userRole': True} ) if not user: raise ValueError("Invalid credentials") # 2. Verify Password stored_password = user.password if not stored_password or not check_password_hash(stored_password, password): raise ValueError("Invalid credentials") # 3. Create Session Payload (exclude sensitive data) user_data = user.to_dict(omit={'password': True}) # 4. Sign In & Redirect # auth.sign_in creates the response with cookies for FastAPI redirect_url = next or AuthConfig.DEFAULT_SIGNIN_REDIRECT return auth.sign_in(data=user_data, redirect_to=redirect_url) def page(): # Renders the HTML template return load_page(__file__)
<form onsubmit="handleSubmit(event)" class="space-y-5"> <!-- Error Message --> <p class="text-red-600 text-sm">{message}</p> <div> <label>Email</label> <input name="email" type="email" required /> </div> <div> <label>Password</label> <input name="password" type="password" required /> </div> <button type="submit">Sign in</button> </form> <script> const [message, setMessage] = pp.state(""); async function handleSubmit(e) { e.preventDefault(); const formData = new FormData(e.target); const data = Object.fromEntries(formData); try { // Pass 'next' param if available const payload = { ...data, next: searchParams.get("next") }; // Server returns redirect command automatically handled by client await pp.rpc("do_login", payload); } catch (error) { setMessage("Invalid email or password"); } } </script>
Protecting Routes
You can protect individual actions using the
@rpc decorator, or
protect entire pages.
Action Level (RPC)
Best for securing specific buttons or form submissions. The client receives a 401/403 error.
@rpc(require_auth=True) async def delete_account(): # Only runs if authenticated await prisma.user.delete(...)
Page Level
Best for securing entire views. Redirects unauthenticated users to the sign-in page.
from casp.auth import require_auth @require_auth def page(): return load_page(__file__)