Getting Started¶
This guide walks you through adding JWT authentication to a pico-fastapi application in 5 minutes.
Prerequisites¶
- Python 3.11 or newer
- A working pico-fastapi application
- A JWT issuer (auth server) that exposes a JWKS endpoint
Installation¶
This installs:
pico-client-auth- JWT authentication clientpico-fastapi- FastAPI integration (dependency)pico-ioc- Core DI container (dependency)python-jose- JWT decodinghttpx- JWKS HTTP clientliboqs-python- ML-DSA signature verification (only with[pqc]extra)
Understanding the Basics¶
Key Concepts¶
| Concept | Description |
|---|---|
| SecurityContext | Static accessor for the current user's claims and roles |
| TokenClaims | Frozen dataclass with sub, email, role, org_id, jti |
| @allow_anonymous | Skip authentication for specific endpoints |
| @requires_role | Enforce role-based access on an endpoint |
| RoleResolver | Protocol to customize how roles are extracted from tokens |
Import Pattern¶
# Decorators and context from pico-client-auth
from pico_client_auth import SecurityContext, allow_anonymous, requires_role
# Controller decorators from pico-fastapi
from pico_fastapi import controller, get, post
# Container initialization from pico-boot
from pico_boot import init
Step 1: Configure Your Auth Server¶
Create or update your application.yaml:
# application.yaml
auth_client:
issuer: https://auth.example.com
audience: my-api
jwks_ttl_seconds: 300 # Cache JWKS for 5 minutes (default)
# jwks_endpoint: "" # Auto-derived: {issuer}/api/v1/auth/jwks
Fail-Fast Validation
If auth_client.enabled is true (the default) but issuer or audience are empty, the application will raise AuthConfigurationError at startup. This prevents deploying with missing auth configuration.
Step 2: Add Auth Decorators to Your Controllers¶
# controllers.py
from pico_fastapi import controller, get
from pico_client_auth import SecurityContext, allow_anonymous, requires_role
@controller(prefix="/api")
class MyController:
@get("/me")
async def get_current_user(self):
"""Returns the authenticated user's info. Requires a valid JWT."""
claims = SecurityContext.require()
return {
"sub": claims.sub,
"email": claims.email,
"roles": SecurityContext.get_roles(),
}
@get("/health")
@allow_anonymous
async def health_check(self):
"""Public endpoint - no token required."""
return {"status": "ok"}
@get("/admin/dashboard")
@requires_role("admin")
async def admin_dashboard(self):
"""Only accessible to users with the 'admin' role."""
return {"dashboard": "admin data"}
Step 3: Bootstrap the Application¶
# main.py
from pico_boot import init
from pico_ioc import configuration, YamlTreeSource
from fastapi import FastAPI
def create_app() -> FastAPI:
config = configuration(YamlTreeSource("application.yaml"))
container = init(
modules=[
"controllers",
],
config=config,
)
return container.get(FastAPI)
app = create_app()
That's it. pico-client-auth is auto-discovered via the pico_boot.modules entry point. All routes are now protected by default.
Step 4: Test It¶
# No token - 401
curl http://localhost:8000/api/me
# {"detail": "Missing or invalid Authorization header"}
# Public endpoint - 200
curl http://localhost:8000/api/health
# {"status": "ok"}
# With valid token - 200
curl -H "Authorization: Bearer eyJ..." http://localhost:8000/api/me
# {"sub": "user-123", "email": "user@example.com", "roles": ["admin"]}
How It Works¶
Request Flow:
[Outer Middleware (CORS, etc.)]
|
[PicoScopeMiddleware] -- Creates request scope
|
[AuthFastapiConfigurer] -- priority=10 (inner middleware)
1. Find matched route endpoint
2. Check @allow_anonymous → skip if present
3. Extract Bearer token from Authorization header
4. TokenValidator.validate(token) → claims + raw_claims
5. RoleResolver.resolve(claims, raw_claims) → roles
6. SecurityContext.set(claims, roles)
7. Check @requires_role → 403 if missing
8. call_next(request)
9. SecurityContext.clear() in finally
|
[Controller Handler] -- SecurityContext available here
Configuration Reference¶
| Key | Default | Description |
|---|---|---|
auth_client.enabled | true | Enable/disable auth middleware |
auth_client.issuer | "" | Expected JWT issuer (iss claim) |
auth_client.audience | "" | Expected JWT audience (aud claim) |
auth_client.jwks_ttl_seconds | 300 | JWKS cache TTL in seconds |
auth_client.jwks_endpoint | "" | JWKS URL (default: {issuer}/api/v1/auth/jwks) |
auth_client.accepted_algorithms | ["RS256"] | Accepted JWT signing algorithms |
Next Steps¶
- Tutorial - Step-by-step walkthrough building a protected API
- User Guide - Deep dive into SecurityContext and roles
- How-To Guides - Practical examples
- FAQ - Common questions