# 🔐 Authentication


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

------------------------------------------------------------------------

## ⏰ Sliding Session Configuration

Configure session timeout behavior with sliding expiry.

<table style="width:100%;">
<colgroup>
<col style="width: 33%" />
<col style="width: 27%" />
<col style="width: 39%" />
</colgroup>
<thead>
<tr>
<th>Parameter</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>max_age</code></td>
<td><code>3600</code></td>
<td>Session expires after this many seconds of inactivity</td>
</tr>
<tr>
<td><code>sliding</code></td>
<td><code>True</code></td>
<td>Refresh session on each request</td>
</tr>
<tr>
<td><code>absolute_max</code></td>
<td><code>None</code></td>
<td>Optional hard limit regardless of activity (e.g., 86400 for
24h)</td>
</tr>
<tr>
<td><code>secure</code></td>
<td><code>True</code></td>
<td>HTTPS-only cookies in production</td>
</tr>
<tr>
<td><code>same_site</code></td>
<td><code>"lax"</code></td>
<td>SameSite cookie policy for CSRF protection</td>
</tr>
</tbody>
</table>

### How Sliding Sessions Work

    Current (Fixed Timeout):
      Login at 10:00 → Expires at 11:00 (regardless of activity)
      User active at 10:55 → STILL logged out at 11:00 ❌

    Sliding Timeout:
      Login at 10:00 → Expires at 11:00
      User active at 10:55 → Expires at 11:55 ✓
      User active at 11:50 → Expires at 12:50 ✓
      User idle for 1 hour → Logged out ✓

------------------------------------------------------------------------

## 🔄 Sliding Session Middleware

Custom middleware that extends Starlette’s SessionMiddleware to refresh
cookie `max_age` on each authenticated request.

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L48"
target="_blank" style="float:right; font-size:smaller">source</a>

### SessionConfig

``` python

def SessionConfig(
    max_age:int=3600, sliding:bool=True, absolute_max:int=None, secure:bool=True, same_site:str='lax',
    http_only:bool=True
)->None:

```

*Configuration for sliding session behavior.*

Attributes: max_age: Session expires after this many seconds of
inactivity. Default: 3600 (1 hour) sliding: If True, refresh session
expiry on each request. Default: True absolute_max: Optional hard limit
in seconds regardless of activity. Default: None secure: If True, cookie
only sent over HTTPS. Default: True same_site: SameSite cookie policy
(‘lax’, ‘strict’, ‘none’). Default: ‘lax’ http_only: If True, cookie not
accessible via JavaScript. Default: True

Example: \>\>\> config = SessionConfig() \# 1 hour inactivity timeout,
sliding enabled \>\>\> config = SessionConfig(max_age=1800) \# 30 min
inactivity timeout \>\>\> config = SessionConfig(max_age=3600,
absolute_max=86400) \# 1h sliding, 24h hard limit

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L87"
target="_blank" style="float:right; font-size:smaller">source</a>

### SlidingSessionMiddleware

``` python

def SlidingSessionMiddleware(
    app, secret_key:str, session_config:SessionConfig=None, session_cookie:str='session', path:str='/'
):

```

*Session middleware with sliding expiry.*

Extends Starlette’s SessionMiddleware to refresh the session cookie
max_age on each request, implementing sliding session expiry.

Sessions expire after `max_age` seconds of INACTIVITY, not from login
time.

Args: app: ASGI application secret_key: Secret key for signing cookies
session_config: SessionConfig instance for timeout settings
session_cookie: Cookie name. Default: ‘session’ path: Cookie path.
Default: ‘/’

Example: \>\>\> from starlette.applications import Starlette \>\>\> app
= Starlette() \>\>\> config = SessionConfig(max_age=3600) \# 1 hour
inactivity \>\>\> app = SlidingSessionMiddleware(app, secret_key=‘…’,
session_config=config)

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L173"
target="_blank" style="float:right; font-size:smaller">source</a>

### create_session_middleware

``` python

def create_session_middleware(
    secret_key:str, session_config:SessionConfig=None, session_cookie:str='session'
)->SlidingSessionMiddleware:

```

*Factory to create SlidingSessionMiddleware for FastHTML apps.*

Args: secret_key: Secret key for signing session cookies (required)
session_config: SessionConfig instance. Default: SessionConfig.default()
session_cookie: Cookie name. Default: ‘session’

Returns: Configured SlidingSessionMiddleware instance

Example: \>\>\> from fasthtml.common import FastHTML \>\>\> \>\>\> \#
Create app WITHOUT default session middleware \>\>\> app =
FastHTML(sess_cls=None) \# Disable default sessions \>\>\> \>\>\> \# Add
sliding session middleware \>\>\> config = SessionConfig(max_age=3600)
\# 1 hour inactivity \>\>\> app =
create_session_middleware(‘your-secret-key’, config)(app) \>\>\> \>\>\>
\# Or wrap during app creation (recommended) \>\>\> middleware =
create_session_middleware(‘your-secret-key’, config) \>\>\> \# Then
configure FastHTML to use it

Note: FastHTML’s default SessionMiddleware needs to be disabled first.
See integration documentation for patterns.

## 🎯 Overview

<table>
<colgroup>
<col style="width: 34%" />
<col style="width: 37%" />
<col style="width: 27%" />
</colgroup>
<thead>
<tr>
<th>Category</th>
<th>Functions</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td>⏰ Sliding Sessions</td>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#sessionconfig"><code>SessionConfig</code></a>,
<a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#slidingsessionmiddleware"><code>SlidingSessionMiddleware</code></a>,
<a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#create_session_middleware"><code>create_session_middleware</code></a></td>
<td>Sliding session expiry (inactivity timeout)</td>
</tr>
<tr>
<td>🛡️ Beforeware</td>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#create_auth_beforeware"><code>create_auth_beforeware</code></a></td>
<td>Protect routes, auto-setup tenant DB</td>
</tr>
<tr>
<td>🔑 OAuth Client</td>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#get_google_oauth_client"><code>get_google_oauth_client</code></a></td>
<td>Initialize Google OAuth</td>
</tr>
<tr>
<td>🔒 CSRF</td>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#generate_oauth_state"><code>generate_oauth_state</code></a>,
<a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#verify_oauth_state"><code>verify_oauth_state</code></a></td>
<td>Prevent session hijacking</td>
</tr>
<tr>
<td>👤 Users</td>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#create_or_get_global_user"><code>create_or_get_global_user</code></a>,
<a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#get_user_membership"><code>get_user_membership</code></a>,
<a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#verify_membership"><code>verify_membership</code></a></td>
<td>User &amp; membership management</td>
</tr>
<tr>
<td>🏗️ Provisioning</td>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#provision_new_user"><code>provision_new_user</code></a></td>
<td>Auto-create tenant for new users</td>
</tr>
<tr>
<td>📋 Session</td>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#create_user_session"><code>create_user_session</code></a>,
<a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#get_current_user"><code>get_current_user</code></a>,
<a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#clear_session"><code>clear_session</code></a></td>
<td>Session management</td>
</tr>
<tr>
<td>🚦 Routing</td>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#auth_redirect"><code>auth_redirect</code></a>,
<a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#route_user_after_login"><code>route_user_after_login</code></a>,
<a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#require_tenant_access"><code>require_tenant_access</code></a></td>
<td>Authorization &amp; routing</td>
</tr>
<tr>
<td>🌐 Handlers</td>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#handle_login_request"><code>handle_login_request</code></a>,
<a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#handle_oauth_callback"><code>handle_oauth_callback</code></a>,
<a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#handle_logout"><code>handle_logout</code></a></td>
<td>Route implementations</td>
</tr>
</tbody>
</table>

------------------------------------------------------------------------

## 🏗️ Architecture

    ┌─────────────────────────────────────────────────────────────────┐
    │                    OAuth Authentication Flow                     │
    ├─────────────────────────────────────────────────────────────────┤
    │  1. User clicks "Login with Google"                             │
    │  2. Generate CSRF state token → store in session                │
    │  3. Redirect to Google OAuth                                    │
    │  4. Google authenticates → redirects with code + state          │
    │  5. Verify CSRF state matches session                           │
    │  6. Exchange code for user info                                 │
    │  7. Create/get GlobalUser in host DB                            │
    │  8. Check membership OR auto-provision new tenant               │
    │  9. Create session → redirect to dashboard                      │
    └─────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
    ┌─────────────────────────────────────────────────────────────────┐
    │                       Tenant Model                              │
    ├─────────────────────────────────────────────────────────────────┤
    │  Many Users → One Tenant (many-to-one)                          │
    │  Each user belongs to exactly ONE tenant                        │
    │  Each tenant can have MANY users                                │
    │  New users auto-create their own tenant                         │
    └─────────────────────────────────────────────────────────────────┘

------------------------------------------------------------------------

## 📚 Quick Reference

### Security Features

<table>
<thead>
<tr>
<th>Feature</th>
<th>Protection</th>
</tr>
</thead>
<tbody>
<tr>
<td>CSRF State Token</td>
<td>Prevents session hijacking attacks</td>
</tr>
<tr>
<td>Membership Validation</td>
<td>Ensures cross-tenant isolation</td>
</tr>
<tr>
<td>Audit Logging</td>
<td>Tracks all authentication events</td>
</tr>
</tbody>
</table>

### Token Expiry

<table>
<colgroup>
<col style="width: 40%" />
<col style="width: 26%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr>
<th>Token Type</th>
<th>Expiry</th>
<th>Behavior</th>
</tr>
</thead>
<tbody>
<tr>
<td>Google OAuth</td>
<td>1 hour</td>
<td>User must re-login (refresh tokens: future)</td>
</tr>
<tr>
<td>Session (sliding)</td>
<td>1 hour inactivity</td>
<td>Refreshes on each request via <a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#slidingsessionmiddleware"><code>SlidingSessionMiddleware</code></a></td>
</tr>
</tbody>
</table>

> 💡 **Sliding Sessions**: Use
> [`SlidingSessionMiddleware`](https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#slidingsessionmiddleware)
> or
> [`create_session_middleware()`](https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#create_session_middleware)
> to keep users logged in while active. Sessions only expire after
> configured inactivity period.

``` python
from nbdev.showdoc import show_doc
```

------------------------------------------------------------------------

## 🎭 Role-Based Access Control

Control access based on user roles within the tenant.

### Role Hierarchy

<table>
<thead>
<tr>
<th>Role</th>
<th>Level</th>
<th>Automatic For</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>admin</code></td>
<td>3</td>
<td>Tenant owners</td>
</tr>
<tr>
<td><code>editor</code></td>
<td>2</td>
<td>—</td>
</tr>
<tr>
<td><code>viewer</code></td>
<td>1</td>
<td>—</td>
</tr>
</tbody>
</table>

### Key Functions

<table>
<colgroup>
<col style="width: 52%" />
<col style="width: 47%" />
</colgroup>
<thead>
<tr>
<th>Function</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#has_min_role"><code>has_min_role</code></a></td>
<td>Check if user meets minimum role requirement</td>
</tr>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#require_role"><code>require_role</code></a></td>
<td>Route decorator for role-based protection</td>
</tr>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#get_user_role"><code>get_user_role</code></a></td>
<td>Derive effective role from session + tenant DB</td>
</tr>
</tbody>
</table>

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L226"
target="_blank" style="float:right; font-size:smaller">source</a>

### has_min_role

``` python

def has_min_role(
    user:dict, required_role:str
)->bool:

```

*Check if user meets the minimum role requirement.*

Args: user: User dict with ‘role’ field (from request.state.user)
required_role: Minimum role needed (‘admin’, ‘editor’, ‘viewer’)

Returns: True if user’s role \>= required_role in hierarchy

Example: \>\>\> user = {‘role’: ‘editor’} \>\>\> has_min_role(user,
‘viewer’) \# True - editor \> viewer \>\>\> has_min_role(user, ‘admin’)
\# False - editor \< admin

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L247"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_user_role

``` python

def get_user_role(
    session:dict, tenant_db:Database=None
)->str:

```

*Derive effective role from session and tenant database.*

Rules: 1. Tenant owner (session\[‘tenant_role’\] == ‘owner’) → ‘admin’
2. System admin → ‘admin’ 3. Otherwise → lookup TenantUser.local_role
from tenant DB 4. Fallback → None (user must be explicitly assigned a
role)

Args: session: User session dict tenant_db: Tenant database connection
(optional)

Returns: Effective role string: ‘admin’, ‘editor’, ‘viewer’, or None

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L292"
target="_blank" style="float:right; font-size:smaller">source</a>

### require_role

``` python

def require_role(
    min_role:str
):

```

*Decorator to protect routes with minimum role requirement.*

Args: min_role: Minimum role required (‘admin’, ‘editor’, ‘viewer’)

Returns: Decorator that checks request.state.user\[‘role’\] Returns 403
Forbidden if user lacks required role

Example: \>\>\> @app.get(‘/admin/settings’) \>\>\>
@require_role(‘admin’) \>\>\> def admin_settings(request): … return
“Admin only content”

    >>> @app.get('/reports')  
    >>> @require_role('viewer')  # All authenticated users
    >>> def view_reports(request):
    ...     return "Reports"

------------------------------------------------------------------------

## ⚡ Session Caching

For HTMX-heavy apps with many partial requests, the beforeware can cache
auth data in the session to avoid database queries on every request.

### Configuration

<table>
<thead>
<tr>
<th>Parameter</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>session_cache</code></td>
<td><code>False</code></td>
<td>Enable caching user dict in session</td>
</tr>
<tr>
<td><code>session_cache_ttl</code></td>
<td><code>300</code></td>
<td>Cache TTL in seconds (5 minutes)</td>
</tr>
</tbody>
</table>

### How It Works

    Request arrives
        │
        ▼
    Check session cache
        │
        ├── Cache valid? → Use cached user data (0 DB queries)
        │
        └── Cache miss/expired? → Query DB → Update cache

### Cache Invalidation

<table>
<thead>
<tr>
<th>Event</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td>Logout</td>
<td>Automatic (session cleared)</td>
</tr>
<tr>
<td>Role change</td>
<td>Call <code>invalidate_auth_cache(session)</code></td>
</tr>
<tr>
<td>TTL expiry</td>
<td>Automatic refresh on next request</td>
</tr>
</tbody>
</table>

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L349"
target="_blank" style="float:right; font-size:smaller">source</a>

### invalidate_auth_cache

``` python

def invalidate_auth_cache(
    session:dict
):

```

*Clear the auth cache from session.*

Call this when: - User role or permissions change - User is
added/removed from tenant - Admin changes user’s local_role

Args: session: User session dict

Example: \>\>\> \# After admin changes user role \>\>\>
tenant_user.local_role = ‘editor’ \>\>\>
tables\[‘tenant_users’\].update(tenant_user) \>\>\>
invalidate_auth_cache(session) \# Force fresh lookup

### Usage Example

``` python
from fh_saas.utils_auth import invalidate_auth_cache

# Enable caching (recommended for HTMX apps)
app = FastHTML(
    before=create_auth_beforeware(
        session_cache=True,
        session_cache_ttl=300  # 5 minutes
    )
)

# After changing user role, invalidate their cache
@app.post('/admin/users/{user_id}/role')
def update_role(request, user_id: str, new_role: str):
    tables = request.state.tables
    user = tables['tenant_users'].get(user_id)
    user.local_role = new_role
    tables['tenant_users'].update(user)
    
    # If changing own role, invalidate cache
    if user_id == request.state.user['user_id']:
        invalidate_auth_cache(request.session)
    
    return "Role updated"
```

### Usage in Routes

``` python
from fh_saas.utils_auth import require_role, has_min_role

# Option 1: Decorator for route-level protection
@app.get('/admin/users')
@require_role('admin')
def manage_users(request):
    return "Admin-only user management"

@app.get('/dashboard')
@require_role('viewer')  # All roles can access
def dashboard(request):
    return "Dashboard for all users"

# Option 2: Inline check for conditional logic
@app.get('/data')
def view_data(request):
    user = request.state.user
    
    if has_min_role(user, 'admin'):
        return "All data with admin controls"
    elif has_min_role(user, 'editor'):
        return "Data with edit buttons"
    else:
        return "Read-only data view"
```

### Role Derivation Flow

    ┌─────────────────────────────────────────────────────────────────┐
    │                    Role Derivation (in beforeware)              │
    ├─────────────────────────────────────────────────────────────────┤
    │  1. Check session['tenant_role']                                │
    │     └── If 'owner' → effective role = 'admin'                  │
    │                                                                 │
    │  2. Check session['is_sys_admin']                               │
    │     └── If True → effective role = 'admin'                     │
    │                                                                 │
    │  3. Lookup TenantUser.local_role in tenant DB                   │
    │     └── Returns assigned role or None                          │
    │                                                                 │
    │  4. Attach to request.state.user['role']                        │
    │     └── Used by require_role() and has_min_role()              │
    └─────────────────────────────────────────────────────────────────┘

------------------------------------------------------------------------

## 🛡️ Auth Beforeware

Protect routes by checking for authenticated sessions with auto tenant
DB setup.

<table>
<colgroup>
<col style="width: 55%" />
<col style="width: 44%" />
</colgroup>
<thead>
<tr>
<th>Function</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#create_auth_beforeware"><code>create_auth_beforeware</code></a></td>
<td>Factory to create route protection middleware</td>
</tr>
</tbody>
</table>

> 💡 **Use case**: Pass to FastHTML’s `before=` parameter for app-wide
> authentication

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L422"
target="_blank" style="float:right; font-size:smaller">source</a>

### create_auth_beforeware

``` python

def create_auth_beforeware(
    redirect_path:str='/login', session_key:str='user_id', skip:list=None, include_defaults:bool=True,
    setup_tenant_db:bool=True, schema_init:Callable=None, session_cache:bool=False, session_cache_ttl:int=300,
    require_subscription:bool=False, subscription_redirect:str=None, grace_period_days:int=3,
    session_config:SessionConfig=None
):

```

*Create Beforeware that checks for authenticated session and sets up
request.state.*

Args: redirect_path: Where to redirect unauthenticated users
session_key: Session key for user ID skip: List of regex patterns to
skip auth include_defaults: Include default skip patterns
setup_tenant_db: Auto-setup tenant database on request.state
schema_init: Optional callback to initialize tables dict. Signature:
(tenant_db: Database) -\> dict\[str, Table\] Result stored in
request.state.tables session_cache: Enable caching user dict in session
to reduce DB queries. Recommended for HTMX-heavy apps. Default: False
session_cache_ttl: Cache TTL in seconds. Default: 300 (5 minutes)
require_subscription: If True, check for active subscription and return
402 if not found. Default: False subscription_redirect: Optional URL to
redirect if subscription required. If None, returns 402 Payment Required
response. grace_period_days: Days to allow access after payment failure
(default: 3) session_config: Optional SessionConfig for absolute timeout
enforcement. Note: Sliding expiry requires SlidingSessionMiddleware.
This parameter only enforces absolute_max if configured.

Returns: Beforeware instance for FastHTML apps

Sets on request.state: - user: dict with user_id, email, tenant_id,
role, is_owner - tenant_id: str - tenant_db: Database connection -
tables: dict of Table objects (if schema_init provided) - subscription:
Subscription object (if require_subscription enabled)

Example: \>\>\> \# Basic usage \>\>\> beforeware =
create_auth_beforeware()

    >>> # With session caching for HTMX apps
    >>> beforeware = create_auth_beforeware(
    ...     session_cache=True,
    ...     session_cache_ttl=300
    ... )

    >>> # With schema initialization
    >>> def get_app_tables(db):
    ...     return {'users': db.create(User, pk='id')}
    >>> beforeware = create_auth_beforeware(schema_init=get_app_tables)

    >>> # With subscription requirement
    >>> beforeware = create_auth_beforeware(
    ...     require_subscription=True,
    ...     subscription_redirect='/pricing'
    ... )

------------------------------------------------------------------------

## 🔑 OAuth Client

<table>
<colgroup>
<col style="width: 55%" />
<col style="width: 44%" />
</colgroup>
<thead>
<tr>
<th>Function</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#get_google_oauth_client"><code>get_google_oauth_client</code></a></td>
<td>Initialize Google OAuth client from env vars</td>
</tr>
</tbody>
</table>

> ⚠️ **Required env vars**: `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L595"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_google_oauth_client

``` python

def get_google_oauth_client(
    
):

```

*Initialize Google OAuth client with credentials from environment.*

------------------------------------------------------------------------

## 🔒 CSRF Protection

Prevent session hijacking via state token validation.

<table>
<colgroup>
<col style="width: 55%" />
<col style="width: 44%" />
</colgroup>
<thead>
<tr>
<th>Function</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#generate_oauth_state"><code>generate_oauth_state</code></a></td>
<td>Create random UUID for CSRF protection</td>
</tr>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#verify_oauth_state"><code>verify_oauth_state</code></a></td>
<td>Validate callback state matches session</td>
</tr>
</tbody>
</table>

### Attack Without CSRF Protection

    1. Attacker initiates OAuth → gets auth code
    2. Attacker sends victim: yourapp.com/auth/callback?code=ATTACKER_CODE
    3. Victim clicks → session created for attacker's account
    4. Victim enters data → attacker sees it all

> 🛡️ **Solution**: State token generated at login, verified at callback

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L611"
target="_blank" style="float:right; font-size:smaller">source</a>

### verify_oauth_state

``` python

def verify_oauth_state(
    session:dict, callback_state:str
):

```

*Verify OAuth callback state matches stored session state (CSRF
protection).*

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L606"
target="_blank" style="float:right; font-size:smaller">source</a>

### generate_oauth_state

``` python

def generate_oauth_state(
    
):

```

*Generate cryptographically secure random state token for CSRF
protection.*

------------------------------------------------------------------------

## 👤 User Management

<table>
<colgroup>
<col style="width: 55%" />
<col style="width: 44%" />
</colgroup>
<thead>
<tr>
<th>Function</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#create_or_get_global_user"><code>create_or_get_global_user</code></a></td>
<td>Create or retrieve user from host DB</td>
</tr>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#get_user_membership"><code>get_user_membership</code></a></td>
<td>Get user’s active tenant membership</td>
</tr>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#verify_membership"><code>verify_membership</code></a></td>
<td>Validate user has access to tenant</td>
</tr>
</tbody>
</table>

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L663"
target="_blank" style="float:right; font-size:smaller">source</a>

### verify_membership

``` python

def verify_membership(
    host_db:HostDatabase, user_id:str, tenant_id:str
)->bool:

```

*Verify user has active membership for specific tenant.*

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L655"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_user_membership

``` python

def get_user_membership(
    host_db:HostDatabase, user_id:str
):

```

*Get single active membership for user.*

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L624"
target="_blank" style="float:right; font-size:smaller">source</a>

### create_or_get_global_user

``` python

def create_or_get_global_user(
    host_db:HostDatabase, oauth_id:str, email:str, oauth_info:dict=None
):

```

*Create or retrieve GlobalUser from host database.*

------------------------------------------------------------------------

## 🏗️ Auto-Provisioning

Create tenant infrastructure for first-time users.

<table>
<colgroup>
<col style="width: 55%" />
<col style="width: 44%" />
</colgroup>
<thead>
<tr>
<th>Function</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#provision_new_user"><code>provision_new_user</code></a></td>
<td>Create tenant DB, catalog entry, membership, and TenantUser</td>
</tr>
</tbody>
</table>

### Provisioning Steps

    1. Create physical tenant database (PostgreSQL/SQLite)
    2. Register tenant in host catalog
    3. Create membership (user → tenant, role='owner')
    4. Create TenantUser profile (local_role='admin')
    5. Initialize core tenant schema
    6. Log audit event

> 💡 **Future**: Insert payment screen before step 1

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L671"
target="_blank" style="float:right; font-size:smaller">source</a>

### provision_new_user

``` python

def provision_new_user(
    host_db:HostDatabase, global_user:GlobalUser
)->str:

```

*Auto-provision new tenant for first-time user.*

------------------------------------------------------------------------

## 📋 Session Management

<table>
<colgroup>
<col style="width: 55%" />
<col style="width: 44%" />
</colgroup>
<thead>
<tr>
<th>Function</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#create_user_session"><code>create_user_session</code></a></td>
<td>Populate session after successful OAuth</td>
</tr>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#get_current_user"><code>get_current_user</code></a></td>
<td>Extract user info from session</td>
</tr>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#clear_session"><code>clear_session</code></a></td>
<td>Clear all session data (logout)</td>
</tr>
</tbody>
</table>

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L785"
target="_blank" style="float:right; font-size:smaller">source</a>

### clear_session

``` python

def clear_session(
    session:dict
):

```

*Clear all session data (logout).*

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L762"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_current_user

``` python

def get_current_user(
    session:dict
)->dict | None:

```

*Extract current user info from session.*

Returns: dict with keys: user_id, email, tenant_id, tenant_role,
is_sys_admin

Note: The ‘role’ and ‘is_owner’ fields are added by
create_auth_beforeware after deriving the effective role from
TenantUser.local_role. Access via request.state.user\[‘role’\] in
routes.

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L745"
target="_blank" style="float:right; font-size:smaller">source</a>

### create_user_session

``` python

def create_user_session(
    session:dict, global_user:GlobalUser, membership:Membership
):

```

*Create authenticated session after successful OAuth login.*

Sets session keys for user identity and tracking: - user_id, email,
tenant_id, tenant_role, is_sys_admin: Identity - login_at: Timestamp
when user logged in - session_started_at: Unix timestamp for absolute
session timeout tracking

------------------------------------------------------------------------

## 🚦 Route Helpers

<table>
<colgroup>
<col style="width: 55%" />
<col style="width: 44%" />
</colgroup>
<thead>
<tr>
<th>Function</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#auth_redirect"><code>auth_redirect</code></a></td>
<td>HTMX-aware redirect to login page</td>
</tr>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#route_user_after_login"><code>route_user_after_login</code></a></td>
<td>Determine redirect URL based on user type</td>
</tr>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#require_tenant_access"><code>require_tenant_access</code></a></td>
<td>Get tenant DB with membership validation</td>
</tr>
</tbody>
</table>

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L790"
target="_blank" style="float:right; font-size:smaller">source</a>

### auth_redirect

``` python

def auth_redirect(
    request, redirect_url:str='/login'
):

```

*HTMX-aware redirect for authentication flows.*

When HTMX makes a partial request and receives a standard redirect
(302/303), it follows the redirect and swaps the response into the
target element. This causes the login page to appear inside the partial
content area.

This function detects HTMX requests and uses the `HX-Redirect` header to
trigger a full page navigation instead.

Args: request: Starlette request object redirect_url: URL to redirect to
(default: ‘/login’)

Returns: Response with appropriate redirect mechanism

Example:
`python     @app.get('/dashboard')     def dashboard(request):         if not get_current_user(request.session):             return auth_redirect(request)         return render_dashboard()`

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L839"
target="_blank" style="float:right; font-size:smaller">source</a>

### require_tenant_access

``` python

def require_tenant_access(
    request_or_session
):

```

*Get tenant database with membership validation.*

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L830"
target="_blank" style="float:right; font-size:smaller">source</a>

### route_user_after_login

``` python

def route_user_after_login(
    global_user:GlobalUser, membership:Membership=None
)->str:

```

*Determine redirect URL based on user type and membership.*

## 🌐 OAuth Route Handlers

Complete OAuth 2.0 flow handlers:

<table>
<colgroup>
<col style="width: 52%" />
<col style="width: 47%" />
</colgroup>
<thead>
<tr>
<th>Function</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#handle_login_request"><code>handle_login_request</code></a></td>
<td>Initiate OAuth with CSRF protection</td>
</tr>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#handle_oauth_callback"><code>handle_oauth_callback</code></a></td>
<td>Process provider response</td>
</tr>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-saas/utils_auth.html#handle_logout"><code>handle_logout</code></a></td>
<td>Clear session and redirect</td>
</tr>
</tbody>
</table>

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L921"
target="_blank" style="float:right; font-size:smaller">source</a>

### handle_logout

``` python

def handle_logout(
    session
):

```

*Clear session and redirect to login page.*

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L877"
target="_blank" style="float:right; font-size:smaller">source</a>

### handle_oauth_callback

``` python

def handle_oauth_callback(
    code:str, state:str, request, session
):

```

*Complete OAuth flow: CSRF verify → user info → provision → session →
redirect.*

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-saas/blob/main/fh_saas/utils_auth.py#L861"
target="_blank" style="float:right; font-size:smaller">source</a>

### handle_login_request

``` python

def handle_login_request(
    request, session
):

```

*Generate Google OAuth URL with CSRF state protection.*
