Multi-Service Architecture
A common architecture: one auth server issues tokens, and multiple microservices verify them.
Auth Server
Section titled “Auth Server” auth_server/main.py
from contextlib import asynccontextmanager
from authfort import AuthFort, CookieConfig
from fastapi import FastAPI
auth = AuthFort(
database_url="postgresql+asyncpg://user:pass@localhost/auth_db",
cookie=CookieConfig(domain=".example.com"),
introspect_secret="shared-secret-123",
)
@asynccontextmanager
async def lifespan(app):
yield
await auth.dispose()
app = FastAPI(lifespan=lifespan)
app.include_router(auth.fastapi_router(), prefix="/auth")
app.include_router(auth.jwks_router()) from contextlib import asynccontextmanager
from authfort import AuthFort, CookieConfig
from fastapi import FastAPI
auth = AuthFort(
database_url="postgresql+asyncpg://user:pass@localhost/auth_db",
cookie=CookieConfig(domain=".example.com"),
introspect_secret="shared-secret-123",
)
@asynccontextmanager
async def lifespan(app):
yield
await auth.dispose()
app = FastAPI(lifespan=lifespan)
app.include_router(auth.fastapi_router(), prefix="/auth")
app.include_router(auth.jwks_router()) Run migrations before starting: authfort migrate --database-url "postgresql+asyncpg://user:pass@localhost/auth_db"
Key points:
domain=".example.com"allows subdomains to read cookiesjwks_router()exposes the/.well-known/jwks.jsonendpoint that microservices fetch public keys fromintrospect_secretenables the introspection endpoint
Microservice
Section titled “Microservice” orders_service/main.py
from authfort_service import ServiceAuth
from fastapi import FastAPI, Depends
service = ServiceAuth(
jwks_url="http://auth-server:8000/.well-known/jwks.json",
issuer="authfort",
cookie_name="access_token",
)
app = FastAPI()
@app.get("/api/orders")
async def list_orders(user=Depends(service.current_user)):
return await get_orders_for_user(user.sub)
@app.delete("/api/orders/{id}")
async def delete_order(
id: str,
user=Depends(service.require_role("admin")),
):
await delete_order_by_id(id) from authfort_service import ServiceAuth
from fastapi import FastAPI, Depends
service = ServiceAuth(
jwks_url="http://auth-server:8000/.well-known/jwks.json",
issuer="authfort",
cookie_name="access_token",
)
app = FastAPI()
@app.get("/api/orders")
async def list_orders(user=Depends(service.current_user)):
return await get_orders_for_user(user.sub)
@app.delete("/api/orders/{id}")
async def delete_order(
id: str,
user=Depends(service.require_role("admin")),
):
await delete_order_by_id(id) Key points:
cookie_name="access_token"reads auth cookies set by the auth servercurrent_userverifies JWTs using cached JWKS keys — no database neededrequire_role("admin")checks the role claim in the JWT
Adding Introspection for Sensitive Operations
Section titled “Adding Introspection for Sensitive Operations”For operations where you need real-time revocation checks:
service = ServiceAuth(
jwks_url="http://auth-server:8000/.well-known/jwks.json",
issuer="authfort",
cookie_name="access_token",
introspect_url="http://auth-server:8000/introspect",
introspect_secret="shared-secret-123",
)
@app.post("/api/orders/{id}/refund")
async def refund_order(id: str, user=Depends(service.current_user)):
# Extra real-time check before processing refund
token = request.headers["authorization"].split(" ")[1]
result = await service.introspect(token)
if not result.active:
raise HTTPException(401, "Token revoked")
await process_refund(id) service = ServiceAuth(
jwks_url="http://auth-server:8000/.well-known/jwks.json",
issuer="authfort",
cookie_name="access_token",
introspect_url="http://auth-server:8000/introspect",
introspect_secret="shared-secret-123",
)
@app.post("/api/orders/{id}/refund")
async def refund_order(id: str, user=Depends(service.current_user)):
# Extra real-time check before processing refund
token = request.headers["authorization"].split(" ")[1]
result = await service.introspect(token)
if not result.active:
raise HTTPException(401, "Token revoked")
await process_refund(id) Frontend
Section titled “Frontend” src/auth.ts
import { createAuthClient } from 'authfort-client';
const auth = createAuthClient({
baseUrl: 'https://auth.example.com/auth',
tokenMode: 'cookie',
});
// Requests to any subdomain include the auth cookie
await auth.fetch('https://orders.example.com/api/orders');
await auth.fetch('https://notifications.example.com/api/unread'); import { createAuthClient } from 'authfort-client';
const auth = createAuthClient({
baseUrl: 'https://auth.example.com/auth',
tokenMode: 'cookie',
});
// Requests to any subdomain include the auth cookie
await auth.fetch('https://orders.example.com/api/orders');
await auth.fetch('https://notifications.example.com/api/unread'); Docker Compose Example
Section titled “Docker Compose Example” docker-compose.yml
services:
auth:
build: ./auth_server
ports: ["8000:8000"]
environment:
DATABASE_URL: postgresql+asyncpg://user:pass@db/auth
INTROSPECT_SECRET: shared-secret-123
orders:
build: ./orders_service
ports: ["8001:8001"]
environment:
JWKS_URL: http://auth:8000/.well-known/jwks.json
notifications:
build: ./notifications_service
ports: ["8002:8002"]
environment:
JWKS_URL: http://auth:8000/.well-known/jwks.json
db:
image: postgres:16
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: auth services:
auth:
build: ./auth_server
ports: ["8000:8000"]
environment:
DATABASE_URL: postgresql+asyncpg://user:pass@db/auth
INTROSPECT_SECRET: shared-secret-123
orders:
build: ./orders_service
ports: ["8001:8001"]
environment:
JWKS_URL: http://auth:8000/.well-known/jwks.json
notifications:
build: ./notifications_service
ports: ["8002:8002"]
environment:
JWKS_URL: http://auth:8000/.well-known/jwks.json
db:
image: postgres:16
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: auth