Pico-Client-Auth Documentation
pico-client-auth is a JWT authentication client for pico-fastapi applications. It provides automatic Bearer token validation, a request-scoped SecurityContext, role-based access control, and JWKS key rotation support.
Quick Install
pip install pico-client-auth
For auto-discovery with pico-boot:
pip install pico-boot pico-fastapi pico-client-auth
30-Second Example
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_me(self):
claims = SecurityContext.require()
return {"sub": claims.sub, "email": claims.email}
@get("/health")
@allow_anonymous
async def health(self):
return {"status": "ok"}
@get("/admin")
@requires_role("admin")
async def admin_panel(self):
return {"admin": True}
# application.yaml
auth_client:
issuer: https://auth.example.com
audience: my-api
Key Features
| Feature | Description |
| Auth by Default | All routes require a valid JWT unless explicitly marked @allow_anonymous |
| SecurityContext | Access authenticated user claims anywhere in the request lifecycle |
| Role-Based Access | @requires_role("admin") decorator for fine-grained authorization |
| Group-Based Access | @requires_group("team-id") decorator for group-level authorization |
| JWKS Caching | Automatic key fetch with TTL and key rotation support |
| Extensible Roles | Implement RoleResolver protocol to customise role extraction |
| Fail-Fast | Missing configuration raises AuthConfigurationError at startup |
| Zero Config | Auto-discovered when using pico-boot |
Documentation Structure
Core APIs at a Glance
SecurityContext
from pico_client_auth import SecurityContext
# In any handler or service (within a request)
claims = SecurityContext.require() # Raises if not authenticated
claims = SecurityContext.get() # Returns None if not authenticated
roles = SecurityContext.get_roles() # Resolved roles list
SecurityContext.has_role("admin") # Boolean check
SecurityContext.require_role("admin") # Raises InsufficientPermissionsError
groups = SecurityContext.get_groups() # Group IDs tuple
SecurityContext.has_group("team-id") # Boolean group check
SecurityContext.require_group("team") # Raises InsufficientPermissionsError
Decorators
from pico_client_auth import allow_anonymous, requires_role, requires_group
@allow_anonymous # Skip token validation for this endpoint
async def health(): ...
@requires_role("admin") # 403 if user lacks the role
async def admin(): ...
@requires_group("eng") # 403 if user not in group
async def team(): ...
Custom Role Resolver
from pico_ioc import component
from pico_client_auth import RoleResolver, TokenClaims
@component
class MyRoleResolver:
async def resolve(self, claims: TokenClaims, raw_claims: dict) -> list[str]:
return raw_claims.get("roles", [])
Why Pico-Client-Auth?
| Concern | Manual Auth Middleware | pico-client-auth |
| Token validation | DIY per project | Automatic via JWKS |
| Key rotation | Manual handling | Auto-refresh on unknown kid |
| Security context | request.state ad-hoc | Typed SecurityContext with ContextVar |
| Role checking | Scattered if/else | @requires_role decorator |
| Configuration | Hardcoded | @configured from YAML/env |
| Testing | Mock everything | Replace RoleResolver, use test tokens |
Next Steps
- New to pico-client-auth? Start with Getting Started
- Want examples? Check the How-To Guides
- Need custom roles? Read Custom Role Resolver