Auth Patterns
SSO. SAML. MFA. RBAC. Multi-tenant orgs. Production B2B auth — pre-wired to Clerk on the first commit. No auth code to write.
The problem
Auth is custom for every app. Every B2B deal asks for SSO.
Auth is the first thing every B2B deal interrogates and the last thing most teams invest in. The deal asks for SSO; your app has password auth. The deal asks for SAML; your app has Google sign-in. The deal asks for MFA; you say "next quarter."
By the time auth is done right, it's been rewritten three times. The team that builds custom auth never builds the agentic product they were hired to ship.
How it works
Clerk SSO/SAML + RBAC + middleware patterns, scaffolded by sage init.
Sage wires Clerk in at scaffold time. The frontend uses Clerk's React components
(<SignIn />, <UserButton />,
<Protect />). The backend validates Clerk JWTs in FastAPI
dependencies, surfaces org_id, role, plan, and feature entitlements on
every request, and isolates data by org.
SSO, SAML, MFA, magic-link — all toggle in the Clerk dashboard with no app code
changes. Clerk Billing plan + features ride in JWT claims for zero-extra-call feature
gating. Test fixtures use the +clerk_test@ convention for deterministic
e2e tests.
- Frontend: <SignIn /> + <UserButton /> + <Protect plan='starter'> Clerk components
- Backend: validate Clerk JWTs in FastAPI dependencies — RBAC + plan/feature checks
- Multi-tenancy: Clerk org_id + role on every request; data isolated by org
- Billing: Clerk Billing plan + features embedded in JWT claims — pla / fea
- Magic-link, SSO, SAML, MFA — all dashboard-toggleable; no app code changes
- Test fixtures: +clerk_test@ convention bypasses verification with magic code 424242
// backend/app/auth/middleware.py
from fastapi import Depends, HTTPException
from clerk_backend_api import Clerk
async def require_user(token: str = Depends(bearer_token)) -> User:
"""Validates Clerk JWT; raises 401 on failure. Resolves org + role."""
claims = await clerk.verify_session(token)
return User(
id=claims.sub,
org_id=claims.org_id,
role=claims.org_role,
plan=claims.pla, # Clerk Billing plan
features=claims.fea, # entitlements
)
# Route usage — RBAC built in:
@router.get("/admin/users")
async def list_users(
user: User = Depends(require_user),
_: None = Depends(require_role("admin")),
):
return await user_service.list(user.org_id) Get Started
B2B-ready auth on the first commit.
SSO, SAML, MFA, RBAC, multi-tenant orgs, plan-based feature gating — all pre-wired to Clerk. The first enterprise deal doesn't surprise your auth layer.