Skip to main content

JWT authentication client for pico-fastapi. Provides automatic Bearer token validation, SecurityContext, role-based access control, and JWKS key rotation.

Project description

Pico-Client-Auth

PyPI Ask DeepWiki License: MIT CI (tox matrix) codecov Docs

Pico-Client-Auth provides JWT authentication for pico-fastapi applications. It integrates with the pico-ioc container to deliver automatic Bearer token validation, a request-scoped SecurityContext, role-based access control, and JWKS key rotation support.

Requires Python 3.11+ Built on pico-fastapi + pico-ioc Fully async-compatible Real JWKS-based token validation Auth by default with opt-out via @allow_anonymous


Why pico-client-auth?

Concern DIY Middleware pico-client-auth
Token validation Implement yourself Built-in with JWKS
Key rotation Manual handling Automatic 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 Build your own fixtures RSA keypair + make_token pattern

Core Features

  • Auth by default on all routes
  • @allow_anonymous to opt out specific endpoints
  • @requires_role("admin") for declarative role-based authorization
  • @requires_group("team-id") for group-based access control
  • SecurityContext accessible from controllers, services, and any code within a request
  • JWKS fetch with TTL cache and automatic key rotation
  • Extensible RoleResolver protocol
  • Fail-fast startup if issuer/audience are missing
  • Auto-discovered via pico_boot.modules entry point

Installation

pip install pico-client-auth

Quick Example

# application.yaml
auth_client:
  issuer: https://auth.example.com
  audience: my-api
from pico_fastapi import controller, get
from pico_client_auth import SecurityContext, allow_anonymous, requires_role, requires_group

@controller(prefix="/api")
class ApiController:

    @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}
from pico_boot import init
from pico_ioc import configuration, YamlTreeSource
from fastapi import FastAPI

config = configuration(YamlTreeSource("application.yaml"))
container = init(modules=["controllers"], config=config)
app = container.get(FastAPI)
# pico-client-auth is auto-discovered — all routes are now protected

Quick Example (without pico-boot)

from pico_ioc import init, configuration, YamlTreeSource
from fastapi import FastAPI

config = configuration(YamlTreeSource("application.yaml"))
container = init(
    modules=[
        "controllers",
        "pico_fastapi",
        "pico_client_auth",  # Required without pico-boot
    ],
    config=config,
)
app = container.get(FastAPI)

SecurityContext

Access authenticated user information from anywhere within a request:

from pico_client_auth import SecurityContext

# In controller, service, or repository
claims = SecurityContext.require()    # TokenClaims (raises if not auth'd)
claims = SecurityContext.get()         # TokenClaims | None
roles  = SecurityContext.get_roles()   # list[str]
SecurityContext.has_role("admin")      # bool
SecurityContext.require_role("admin")  # raises InsufficientPermissionsError
groups = SecurityContext.get_groups()  # tuple[str, ...]
SecurityContext.has_group("team-id")   # bool
SecurityContext.require_group("team")  # raises InsufficientPermissionsError

Custom Role Resolver

Override how roles are extracted from tokens:

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", [])

Configuration

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)

Testing

from pico_client_auth import SecurityContext, TokenClaims
from pico_client_auth.errors import MissingTokenError

def test_require_raises_when_empty():
    SecurityContext.clear()
    with pytest.raises(MissingTokenError):
        SecurityContext.require()

def test_authenticated_flow():
    claims = TokenClaims(sub="u1", email="a@b.com", role="admin",
                         org_id="o1", jti="j1")
    SecurityContext.set(claims, ["admin"])
    assert SecurityContext.require().sub == "u1"
    assert SecurityContext.has_role("admin")
    SecurityContext.clear()

For full e2e testing with mock JWKS and signed tokens, see the Testing Guide.


How It Works

  • AuthFastapiConfigurer (priority=10) registers as an inner middleware
  • Every request: extract Bearer token → validate JWT via JWKS → resolve roles → populate SecurityContext
  • @allow_anonymous endpoints skip validation entirely
  • @requires_role endpoints check resolved roles, return 403 if missing
  • @requires_group endpoints check group membership, return 403 if missing
  • SecurityContext is cleared in finally — no leakage between requests

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pico_client_auth-0.3.0.tar.gz (50.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pico_client_auth-0.3.0-py3-none-any.whl (14.3 kB view details)

Uploaded Python 3

File details

Details for the file pico_client_auth-0.3.0.tar.gz.

File metadata

  • Download URL: pico_client_auth-0.3.0.tar.gz
  • Upload date:
  • Size: 50.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pico_client_auth-0.3.0.tar.gz
Algorithm Hash digest
SHA256 aba7f5bfda7d7a14adc3724f0773ee4101b37a61901382cb22e6ad5043ee7f0e
MD5 baf49bd4e867baadac09ebb9f8dbb567
BLAKE2b-256 f568df9a4737406f16c0826a59b3fc55ba001d28dfb675f2d6897feac7c04388

See more details on using hashes here.

Provenance

The following attestation bundles were made for pico_client_auth-0.3.0.tar.gz:

Publisher: publish-to-pypi.yml on dperezcabrera/pico-client-auth

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pico_client_auth-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pico_client_auth-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5fc8f098d7cb6a22ba4bf21718d97b34fd807a7078402fbc522754944afeddc3
MD5 e1d31cd8002774a0ff2efaa69a347a62
BLAKE2b-256 e50e7f8049f04f2223866fc355adfe65e7b377cc7674a0d551fa2e8b87ac7e61

See more details on using hashes here.

Provenance

The following attestation bundles were made for pico_client_auth-0.3.0-py3-none-any.whl:

Publisher: publish-to-pypi.yml on dperezcabrera/pico-client-auth

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page