from fasthtml.common import *
from ship_kit.auth import user_auth_before
# Configure authentication beforeware
= Beforeware(
beforeware # Uses default /login path
user_auth_before, =[
skip'/login', # Don't require auth for login page
'/signup', # Don't require auth for signup
'/static/.*', # Static files don't need auth
r'.*\.css', # CSS files
r'.*\.js' # JavaScript files
]
)
# Or with custom login path:
def auth_custom_login(req, sess):
return user_auth_before(req, sess, login_path='/auth/login')
= Beforeware(
beforeware
auth_custom_login,=['/auth/login', '/auth/signup', '/static/.*']
skip
)
# Create app with authentication
= fast_app(before=beforeware)
app, rt
# This route is protected - redirects to login if not authenticated
@rt('/')
def get(auth): # auth is automatically injected from req.scope['auth']
return H1(f"Welcome! You are {'authenticated' if auth else 'not authenticated'}")
Authentication
Imports and utils
Quick Start
Ship Kit’s authentication module provides everything you need to add secure authentication to your FastHTML app in minutes:
from ship_kit.auth import *
from fasthtml.common import *
# 1. Initialize the database
= init_auth_tables() # Creates users table with indexes
db
# 2. Create a new user
= create_user(db, 'john_doe', 'john@example.com', 'secure_password123')
user
# 3. Authenticate users during login
= authenticate_user(db, 'john@example.com', 'secure_password123')
user if user:
'auth'] = True
sess['user'] = {'id': user['id'], 'username': user['username']}
sess[
# 4. Protect routes with authentication
= Beforeware(user_auth_before, skip=['/login', '/signup'])
beforeware = fast_app(before=beforeware)
app, rt
# 5. Use validation helpers
if is_username_available(db, 'new_user'):
# Username is available for registration
pass
That’s it! Your routes are now protected and users must authenticate to access them.
Overview
This module provides comprehensive authentication features:
Core Functions
Function | Purpose | When to Use |
---|---|---|
hash_password |
Securely hash passwords with bcrypt | User registration/password updates |
verify_password |
Check if password matches hash | User login |
user_auth_before |
Beforeware to protect routes | App initialization |
get_user_from_session |
Extract user data from session | Inside route handlers |
create_auth_token |
Generate secure tokens | Remember me/API auth |
verify_auth_token |
Validate tokens | Token-based auth |
Database Functions
Function | Purpose | When to Use |
---|---|---|
init_auth_tables |
Create database tables and indexes | App startup |
create_user |
Register new user | User signup |
get_user_by_id |
Fetch user by ID | User profile/admin |
get_user_by_email |
Fetch user by email | Login/verification |
get_user_by_username |
Fetch user by username | Login/profile lookup |
update_user |
Update user attributes | Profile editing |
delete_user |
Remove user from database | Account deletion |
Helper Functions
Function | Purpose | When to Use |
---|---|---|
authenticate_user |
Verify credentials and return user | Login endpoint |
check_permission |
Verify user has required role | Access control |
track_login |
Record login events | Security/analytics |
is_username_available |
Check username uniqueness | Registration |
is_email_available |
Check email uniqueness | Registration |
validate_user_data |
Validate registration data | Form validation |
Password Hashing
Launch Kit uses bcrypt for password hashing - the industry standard for secure password storage. Our implementation uses a cost factor of 12, providing excellent security while maintaining reasonable performance (~200ms per hash on modern hardware).
hash_password
hash_password (password:str)
Hash a password using bcrypt with a cost factor of 12.
Type | Details | |
---|---|---|
password | str | The password to hash |
Returns | str | The hashed password |
bcrypt has a maximum password length of 72 bytes. Passwords longer than this are silently truncated. For applications requiring longer passwords, consider hashing with SHA-256 first:
import hashlib
= "very_long_password" * 10
long_password # Hash with SHA-256 first, then bcrypt
= hashlib.sha256(long_password.encode()).hexdigest()
sha_hash = hash_password(sha_hash) final_hash
verify_password
verify_password (password:str, hashed:str)
Verify a password against a bcrypt hash.
Type | Details | |
---|---|---|
password | str | The password to verify |
hashed | str | The hashed password to compare against |
Returns | bool | True if password matches hash, False otherwise |
User Model
Launch Kit provides a simple, dataclass-based User model that works seamlessly with FastHTML’s MiniDataAPI.
Note: While the User model is defined as a dataclass, fastlite returns query results as dictionaries for flexibility and performance. This is the expected behavior and aligns with fastlite’s design philosophy.
User
User (username:str, email:str, password_hash:str, role:str='user', is_active:bool=True, created_at:datetime.datetime=<factory>, updated_at:datetime.datetime=<factory>, id:Optional[int]=None)
User model for authentication.
init_auth_tables
init_auth_tables (db_path:str='data.db')
Creates the users table with proper schema and indexes. Uses FastHTML’s MiniDataAPI for simple, transparent database operations.
Type | Default | Details | |
---|---|---|---|
db_path | str | data.db | The path to the SQLite database file |
Returns | Database | The FastHTML Database instance configured with the User table |
User CRUD Operations
Simple, transparent database operations following MiniDataAPI patterns:
create_user
create_user (db:apswutils.db.Database, username:str, email:str, password:str, role:str='user', table_name:str='user')
Create a new user in the database.
Type | Default | Details | |
---|---|---|---|
db | Database | The FastHTML Database instance | |
username | str | The unique username | |
str | The unique email address | ||
password | str | The plain text password | |
role | str | user | The user role |
table_name | str | user | The table name |
Returns | Optional | The created user dict or None if user already exists |
get_user_by_id
get_user_by_id (db:apswutils.db.Database, user_id:int, table_name:str='user')
Get user by ID.
Type | Default | Details | |
---|---|---|---|
db | Database | The FastHTML Database instance | |
user_id | int | The user ID | |
table_name | str | user | The table name |
Returns | Optional | The user dict or None if not found |
get_user_by_email
get_user_by_email (db:apswutils.db.Database, email:str, table_name:str='user')
Get user by email address.
Type | Default | Details | |
---|---|---|---|
db | Database | The FastHTML Database instance | |
str | The email address | ||
table_name | str | user | The table name |
Returns | Optional | The user dict or None if not found |
get_user_by_username
get_user_by_username (db:apswutils.db.Database, username:str, table_name:str='user')
Get user by username.
Type | Default | Details | |
---|---|---|---|
db | Database | The FastHTML Database instance | |
username | str | The username | |
table_name | str | user | The table name |
Returns | Optional | The user dict or None if not found |
update_user
update_user (db:apswutils.db.Database, user_id:int, table_name:str='user', **kwargs)
Update user attributes.
Type | Default | Details | |
---|---|---|---|
db | Database | The FastHTML Database instance | |
user_id | int | The user ID | |
table_name | str | user | The table name |
kwargs | VAR_KEYWORD | ||
Returns | Optional | The updated user dict or None if not found |
delete_user
delete_user (db:apswutils.db.Database, user_id:int, table_name:str='user')
Delete a user from the database.
Type | Default | Details | |
---|---|---|---|
db | Database | The FastHTML Database instance | |
user_id | int | The user ID | |
table_name | str | user | The table name |
Returns | bool | True if deleted, False if not found |
Authentication Helpers
Convenient functions for common authentication operations:
check_permission
check_permission (user:__main__.User, required_role:str)
*Check if a user has the required role or higher.
Role hierarchy: - ‘admin’ has all permissions - ‘user’ has basic permissions - Custom roles can be added as needed*
Type | Details | |
---|---|---|
user | User | The User object to check |
required_role | str | The required role |
Returns | bool | True if user has permission, False otherwise |
authenticate_user
authenticate_user (db:apswutils.db.Database, username_or_email:str, password:str, table_name:str='user')
Authenticate a user by username/email and password.
Type | Default | Details | |
---|---|---|---|
db | Database | The FastHTML Database instance | |
username_or_email | str | The username or email address | |
password | str | The plain text password | |
table_name | str | user | The table name |
Returns | Optional | The authenticated user dict or None if invalid credentials |
track_login
track_login (db:apswutils.db.Database, user_id:int, ip_address:Optional[str]=None, user_agent:Optional[str]=None, table_name:str='user_logins')
*Track user login for security and analytics using MiniDataAPI pattern.
Creates a login record with timestamp and metadata. Returns the created login record.*
Type | Default | Details | |
---|---|---|---|
db | Database | The FastHTML Database instance | |
user_id | int | The user ID | |
ip_address | Optional | None | The IP address of the login |
user_agent | Optional | None | The user agent string |
table_name | str | user_logins | The table name |
Returns | Dict | The login record |
UserLogin
UserLogin (user_id:int, ip_address:Optional[str]=None, user_agent:Optional[str]=None, login_time:datetime.datetime=<factory>, id:Optional[int]=None)
Login tracking record for security and analytics.
Comparison: Raw SQL vs MiniDataAPI
Aspect | Current (Raw SQL) | Proposed (MiniDataAPI) |
---|---|---|
Table Creation | Manual SQL with CREATE TABLE | db.create(UserLogin) |
Data Insertion | SQL INSERT with placeholders | logins.insert(login_data) |
Return Value | None | Login record dict |
Type Safety | No type hints for data | Dataclass with types |
Consistency | Different from other functions | Same pattern as User CRUD |
Database Portability | SQLite-specific syntax | Works across databases |
Foreign Keys | Defined in CREATE TABLE | Need separate index creation |
Migration Considerations
To migrate existing databases: 1. The table structure created by MiniDataAPI is compatible with the existing schema 2. Foreign key constraints would need to be added separately 3. Existing data would remain intact
Validation Helpers
Functions to ensure data integrity and check uniqueness:
is_username_available
is_username_available (db:apswutils.db.Database, username:str, table_name:str='user')
Check if username is available for registration.
Type | Default | Details | |
---|---|---|---|
db | Database | The FastHTML Database instance | |
username | str | The username to check | |
table_name | str | user | The table name |
Returns | bool | True if username is available, False if taken |
is_email_available
is_email_available (db:apswutils.db.Database, email:str, table_name:str='user')
Check if email is available for registration.
Type | Default | Details | |
---|---|---|---|
db | Database | The FastHTML Database instance | |
str | The email address to check | ||
table_name | str | user | The table name |
Returns | bool | True if email is available, False if taken |
validate_user_data
validate_user_data (username:str, email:str, password:str)
Validate user registration data.
Type | Details | |
---|---|---|
username | str | The username to validate |
str | The email address to validate | |
password | str | The password to validate |
Returns | List | The list of validation errors (empty if valid) |
Session Management
FastHTML uses server-side sessions to maintain authentication state. Launch Kit provides utilities to make session management simple and secure.
Protecting Routes with Beforeware
user_auth_before
user_auth_before (req, sess, login_path='/login')
*Beforeware function to check authentication status.
Following FastHTML’s standard authentication pattern: - Sets req.scope[‘auth’] for automatic injection in route handlers - Returns None if authenticated (continue processing) - Returns RedirectResponse if not authenticated*
Type | Default | Details | |
---|---|---|---|
req | The FastHTML Request object | ||
sess | The FastHTML Session object | ||
login_path | str | /login | The login path |
The user_auth_before
follows FastHTML’s recommended authentication pattern. It sets req.scope[‘auth’] for automatic injection in route handlers.
Session Schema
Launch Kit uses a consistent session schema: - sess['auth']
: Boolean or auth token - indicates if user is authenticated - sess['user']
: Dict with user data (id, username, email, role, etc.)
This separation allows flexibility while maintaining consistency:
# During login:
'auth'] = True # or sess['auth'] = create_auth_token(user['id'])
sess['user'] = {
sess['id': user['id'],
'username': user['username'],
'email': user['email'],
'role': user['role']
}
Usage:
= Beforeware(
beforeware
user_auth_before,=['/login', '/signup', '/static/.*']
skip
)
# Or with a custom login path like /auth/login:
def auth_custom_login(req, sess):
return user_auth_before(req, sess, login_path='/auth/login')
= Beforeware(
beforeware
auth_custom_login,=['/auth/login', '/auth/signup', '/static/.*']
skip )
get_user_from_session
get_user_from_session (sess)
Extract user data dictionary from session.
Type | Details | |
---|---|---|
sess | The FastHTML Session object | |
Returns | Optional | The user data dict or None if not authenticated |
Note: With FastHTML’s authentication pattern, you can also access auth status via the ‘auth’ parameter in route handlers.
Working with User Sessions
Authentication Tokens
Tokens enable stateless authentication for APIs and “remember me” functionality. Launch Kit provides simple utilities for secure token generation and verification.
create_auth_token
create_auth_token (user_id:int)
Create a secure authentication token for a user.
Type | Details | |
---|---|---|
user_id | int | The user ID |
Returns | str | The authentication token |
In production, you’d store this token in your database with an expiration date and associate it with the user.
verify_auth_token
verify_auth_token (token:str)
Verify an authentication token and return the user ID.
Type | Details | |
---|---|---|
token | str | The authentication token |
Returns | Optional | The user ID or None if invalid |
In production, you’d look up this token in your database and check expiration.
Security Best Practices
Following these practices is essential for maintaining a secure authentication system.
🔐 Password Security
Practice | Implementation |
---|---|
Minimum Length | Enforce 12+ characters |
Complexity | Require mixed case, numbers, symbols |
Common Passwords | Check against haveibeenpwned.com |
Password History | Prevent reuse of last 5 passwords |
Account Lockout | Lock after 5 failed attempts |
🍪 Session Security
Practice | Implementation |
---|---|
Secure Cookies | secure=True, httponly=True, samesite='Lax' |
Session Timeout | 30 min idle, 12 hour absolute |
Session Rotation | New ID after login |
CSRF Protection | Use CSRF tokens for state changes |
🎫 Token Security
Practice | Implementation |
---|---|
Storage | Hash tokens before storing |
Expiration | 30 days for remember me |
Rotation | New token on each use |
Revocation | Allow users to revoke all tokens |
🌐 General Security
Practice | Implementation |
---|---|
HTTPS | Enforce TLS 1.2+ everywhere |
Rate Limiting | 5 attempts per minute |
Audit Logging | Log all auth events |
2FA | Support TOTP/WebAuthn |
Complete Database Example
Here’s how to use the User model and database functions together:
from ship_kit.auth import *
from fasthtml.common import *
# Initialize database
= init_auth_tables('app.db')
db
# Create FastHTML app with auth
= Beforeware(
beforeware
user_auth_before,=['/login', '/signup', '/']
skip
)= fast_app(before=beforeware)
app, rt
@rt('/')
def get():
return Div(
'Welcome to Ship Kit Auth Demo'),
H1('Sign Up', href='/signup'),
A(' | ',
'Login', href='/login')
A(
)
@rt('/signup')
def get():
return Div(
'Sign Up'),
H2(
Form(='username', placeholder='Username', required=True),
Input(name='email', type='email', placeholder='Email', required=True),
Input(name='password', type='password', placeholder='Password', required=True),
Input(name'Sign Up', type='submit'),
Button(='post'
method
)
)
@rt('/signup')
def post(username: str, email: str, password: str):
# Validate input
= validate_user_data(username, email, password)
errors if errors:
return Div(
*[P(error, style='color: red') for error in errors],
get()
)
# Check availability
if not is_username_available(db, username):
return Div(P('Username already taken', style='color: red'), get())
if not is_email_available(db, email):
return Div(P('Email already registered', style='color: red'), get())
# Create user
= create_user(db, username, email, password)
user if user:
return Redirect('/login')
return Div(P('Registration failed', style='color: red'), get())
@rt('/login')
def get():
return Div(
'Login'),
H2(
Form(='username_or_email', placeholder='Username or Email', required=True),
Input(name='password', type='password', placeholder='Password', required=True),
Input(name'Login', type='submit'),
Button(='post'
method
)
)
@rt('/login')
def post(username_or_email: str, password: str, sess, req):
# Authenticate user
= authenticate_user(db, username_or_email, password)
user
if user:
# Set session
'auth'] = True
sess['user'] = {'id': user['id'], 'username': user['username'], 'email': user['email']}
sess[
# Track login (now returns a dictionary with login details)
= req.headers.get('x-forwarded-for', req.client.host)
ip = req.headers.get('user-agent')
user_agent = track_login(db, user['id'], ip, user_agent)
login_record
# You can use the login_record if needed, e.g. for analytics
# login_record contains: id, user_id, ip_address, user_agent, login_time
return Redirect('/dashboard')
return Div(P('Invalid credentials', style='color: red'), get())
@rt('/dashboard')
def get(auth, sess):
= get_user_from_session(sess)
user_data if not user_data:
return Redirect('/login')
return Div(
f"Welcome, {user_data['username']}!"),
H2(f"Email: {user_data['email']}"),
P('Logout', href='/logout')
A(
)
@rt('/logout')
def get(sess):
sess.clear()return Redirect('/')
Database Tests
Testing the User model and database operations:
Quick Example
Complete Login/Signup Example
Here’s how to implement a complete authentication flow:
from fasthtml.common import *
from ship_kit.auth import hash_password, verify_password
# Initialize FastHTML app
= fast_app()
app, rt
# Simulated user database
= {}
users
@rt('/signup')
def post(email: str, password: str):
# Check if user exists
if email in users:
return "User already exists", 400
# Create new user with hashed password
= {
users[email] 'email': email,
'password': hash_password(password)
}return "Signup successful! Please login."
@rt('/login')
def post(email: str, password: str, sess):
# Get user from database
= users.get(email)
user
if user and verify_password(password, user['password']):
# Set session authentication
'auth'] = True
sess['user_id'] = email
sess[return Redirect('/')
return "Invalid credentials", 401
# User registration
= "MySecureP@ssw0rd!"
user_password = hash_password(user_password)
hashed_password print(f"Store this in database: {hashed_password[:20]}...")
# User login
= "MySecureP@ssw0rd!"
login_attempt if verify_password(login_attempt, hashed_password):
print("✅ Login successful!")
else:
print("❌ Invalid password")
# Wrong password attempt
if not verify_password("wrong_password", hashed_password):
print("❌ Invalid credentials rejected")
Store this in database: $2b$12$J7K88iTYaU2pD...
✅ Login successful!
❌ Invalid credentials rejected
Token Usage Example
Remember Me Implementation
Here’s how to implement “remember me” functionality using tokens:
from fasthtml.common import *
from ship_kit.auth import *
# Initialize app
= fast_app()
app, rt
# Helper functions (would be in your database module)
def get_user_by_email(email):
# Fetch from database
pass
def store_remember_token(user_id, token, days):
# Store token with expiration in database
pass
def is_token_valid_in_db(token):
# Check token validity in database
pass
@rt('/login')
def post(email: str, password: str, remember_me: bool, sess, resp):
= get_user_by_email(email)
user
if user and verify_password(password, user['password']):
# Set session auth
'auth'] = True
sess['user_id'] = user['id']
sess[
# If remember me is checked, create a token
if remember_me:
= create_auth_token(user['id'])
token # Store token in database with expiration
'id'], token, days=30)
store_remember_token(user[# Set cookie
'remember_token', token, max_age=30*24*60*60)
resp.set_cookie(
return Redirect('/')
return "Invalid credentials"
@rt('/')
def get(sess, req):
# Check session first
if sess.get('auth'):
return "Welcome back!"
# Check remember me token
= req.cookies.get('remember_token')
token if token:
= verify_auth_token(token)
user_id if user_id and is_token_valid_in_db(token):
# Restore session
'auth'] = True
sess['user_id'] = user_id
sess[return "Welcome back (remembered)!"
return Redirect('/login')
# Generate a token for user 42
= 42
user_id = create_auth_token(user_id)
token print(f"Generated token: {token[:20]}...")
# Later, verify the token
= verify_auth_token(token)
verified_user_id if verified_user_id:
print(f"✅ Valid token for user {verified_user_id}")
else:
print("❌ Invalid or expired token")
# Invalid tokens return None
assert verify_auth_token("tampered_token") is None
print("❌ Tampered tokens are rejected")
Generated token: 42:l97Cko11shUwHASMC...
✅ Valid token for user 42
❌ Tampered tokens are rejected
Complete Example: Interactive Demo
Here’s a complete FastHTML application demonstrating all authentication features. Run it right here in the notebook!
from fasthtml.common import *
from ship_kit.auth import *
# Simulated database
= {'test@example.com': {'id': 1, 'email': 'test@example.com', 'password': hash_password('password123')}}
users_db
# Configure authentication with custom login path
# Create a wrapper function instead of using partial
def auth_with_custom_login(req, sess):
return user_auth_before(req, sess, login_path='/auth/login')
= Beforeware(
beforeware
auth_with_custom_login,=[r'/auth/.*', r'/public/.*', '/']
skip
)= fast_app(before=beforeware)
app, rt
# Public home page
@rt('/')
def get():
return Div(
'Welcome to SecureApp'),
H1('A demo of Ship Kit authentication'),
P('Login', href='/auth/login', cls='button'),
A('Protected Area', href='/protected', cls='button')
A(
)
# Login page
@rt('/auth/login')
def get():
return Div(
'Login'),
H2(
Form(='email', type='email', placeholder='Email', required=True),
Input(name='password', type='password', placeholder='Password', required=True),
Input(name'Login', type='submit'),
Button(='post'
method
)
)
# Handle login
@rt('/auth/login')
def post(email: str, password: str, sess):
= users_db.get(email)
user if user and verify_password(password, user['password']):
'auth'] = True
sess['user'] = user
sess[return Redirect('/protected')
return Div(P('Invalid credentials', style='color: red'), get())
# Protected page
@rt('/protected')
def get(auth, sess):
= get_user_from_session(sess)
user return Div(
f"Welcome {user['email']}!"),
H2('This is a protected page.'),
P('Logout', href='/auth/logout', cls='button')
A(
)
# Logout
@rt('/auth/logout')
def get(sess):
sess.clear()return Redirect('/')
# Start the server in Jupyter [https://fastht.ml/docs/tutorials/jupyter_and_fasthtml.html]
from fasthtml.jupyter import JupyUvi
= JupyUvi(app)
server print("Server running at http://localhost:8000")
print("\nTest credentials:")
print("- Email: test@example.com")
print("- Password: password123")
Server running at http://localhost:8000
Test credentials:
- Email: test@example.com
- Password: password123
# View the app right here in the notebook by uncommenting the line below
from fasthtml.jupyter import HTMX
#HTMX()
Automated Testing
You can also test the authentication flow programmatically:
import httpx
import asyncio
# Test the authentication flow
async def test_auth_flow():
async with httpx.AsyncClient(base_url="http://localhost:8000", follow_redirects=False) as client:
print("Testing authentication flow...")
# 1. Try protected page (should redirect)
= await client.get("/protected")
resp assert resp.status_code == 303
assert resp.headers['location'] == '/auth/login'
print("✓ Protected page redirects to /auth/login")
# 2. Login with valid credentials
= await client.post("/auth/login", data={
resp "email": "test@example.com",
"password": "password123"
})assert resp.status_code == 303
assert resp.headers['location'] == '/protected'
print("✓ Login successful")
# 3. Access protected page
= await client.get("/protected", follow_redirects=True)
resp assert "Welcome test@example.com" in resp.text
print("✓ Protected page accessible")
# 4. Logout
= await client.get("/auth/logout")
resp assert resp.status_code == 303
print("✓ Logout successful")
print("\n✅ All tests passed!")
# Run the tests
await test_auth_flow()
Testing authentication flow...
✓ Protected page redirects to /auth/login
✓ Login successful
✓ Protected page accessible
✓ Logout successful
✅ All tests passed!
Manual Testing
You can test the app manually in a browser by visiting: - http://localhost:8000 - Home page - http://localhost:8000/protected - Will redirect to login - http://localhost:8000/auth/login - Login with test@example.com / password123
# Stop the server gracefully
# Note: Always run this after testing to clean up otherwise there will be a dangling thread
# https://fastht.ml/docs/tutorials/jupyter_and_fasthtml.html#graceful-shutdowns
print("Stopping server...")
server.stop()print("✓ Server stopped gracefully")
print("\n🎉 All tests passed! Authentication utilities are working correctly with FastHTML.")
Stopping server...
✓ Server stopped gracefully
🎉 All tests passed! Authentication utilities are working correctly with FastHTML.
Auth Tests
Performance Benchmarks
Understanding the performance characteristics of authentication operations is crucial for capacity planning.
Summary
Ship Kit’s authentication module provides a complete, secure foundation for FastHTML applications:
- Simple API - Core authentication functions plus comprehensive database operations
- Database Integration - User model with FastHTML’s MiniDataAPI for transparent persistence
- Secure by Default - Industry-standard bcrypt hashing with sensible defaults
- FastHTML Native - Seamless integration with sessions, beforeware, and database patterns
- Production Ready - Battle-tested patterns with comprehensive security guidelines
- Flexible - Works for both traditional web apps and API authentication
- Complete Solution - From user registration to login tracking, all in one module
Breaking Changes in v2.0
- Consistent Session Schema - Always use
sess['auth']
for auth status andsess['user']
for user data - Simplified user_auth_before - Now explicitly documents return behavior and sets req.scope[‘auth’]