Minimal JWT auth server for the Pico ecosystem.
Pico-Auth is a ready-to-run authentication server built on top of the pico-framework stack. It provides:
- JWT tokens with auto-generated key pairs (RS256 default, ML-DSA-65/87 optional)
- Refresh token rotation with SHA-256 hashed storage
- RBAC with four built-in roles:
superadmin,org_admin,operator,viewer - Group management with CRUD API, membership, and
groupsJWT claim - OIDC discovery endpoints (
.well-known/openid-configuration, JWKS) - Bcrypt password hashing (72-byte input limit enforced)
- Post-quantum ready: ML-DSA-65 / ML-DSA-87 signing via optional
pqcextra - Zero-config startup with auto-created admin user
Requires Python 3.11+
Pico-Auth uses the full Pico stack with dependency injection:
| Layer | Component | Decorator |
|---|---|---|
| Config | AuthSettings |
@configured(prefix="auth") |
| Models | User, RefreshToken, Group, GroupMember |
SQLAlchemy AppBase |
| Repository | UserRepository, RefreshTokenRepository, GroupRepository |
@component |
| Service | AuthService, GroupService |
@component |
| Security | JWTProvider, PasswordService, LocalJWKSProvider |
@component |
| Routes | AuthController, GroupController, OIDCController |
@controller |
pip install -e ".[dev]"
# With post-quantum (ML-DSA) support
pip install -e ".[dev,pqc]"python -m pico_auth.mainThe server starts on http://localhost:8100 with:
- An auto-created admin user (
admin@pico.local/admin) - SQLite database at
auth.db - Auto-generated keys at
~/.pico-auth/(RSA PEM or ML-DSA binary, depending on algorithm)
curl -X POST http://localhost:8100/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "alice@example.com", "password": "secret123", "display_name": "Alice"}'curl -X POST http://localhost:8100/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "alice@example.com", "password": "secret123"}'Returns:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "a1b2c3d4...",
"token_type": "Bearer",
"expires_in": 900
}curl http://localhost:8100/api/v1/auth/me \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/v1/auth/register |
No | Register a new user |
| POST | /api/v1/auth/login |
No | Login and get tokens |
| POST | /api/v1/auth/refresh |
No | Refresh access token |
| GET | /api/v1/auth/me |
Bearer | Get current user profile |
| POST | /api/v1/auth/me/password |
Bearer | Change password |
| GET | /api/v1/auth/users |
Admin | List all users |
| PUT | /api/v1/auth/users/{id}/role |
Admin | Update user role |
| GET | /api/v1/auth/jwks |
No | JSON Web Key Set |
| POST | /api/v1/groups |
Admin | Create a group |
| GET | /api/v1/groups |
Bearer | List groups (by org) |
| GET | /api/v1/groups/{id} |
Bearer | Get group with members |
| PUT | /api/v1/groups/{id} |
Admin | Update group |
| DELETE | /api/v1/groups/{id} |
Admin | Delete group |
| POST | /api/v1/groups/{id}/members |
Admin | Add member to group |
| DELETE | /api/v1/groups/{id}/members/{uid} |
Admin | Remove member |
| GET | /.well-known/openid-configuration |
No | OIDC discovery |
All settings are loaded from application.yaml and can be overridden with environment variables:
auth:
data_dir: "~/.pico-auth" # Key storage directory
access_token_expire_minutes: 15 # JWT lifetime
refresh_token_expire_days: 7 # Refresh token lifetime
issuer: "http://localhost:8100" # JWT issuer claim
audience: "pico-bot" # JWT audience claim
algorithm: "RS256" # RS256, ML-DSA-65, or ML-DSA-87
auto_create_admin: true # Create admin on startup
admin_email: "admin@pico.local" # Default admin email
admin_password: "admin" # Default admin password
database:
url: "sqlite+aiosqlite:///auth.db" # Database URL
echo: false # SQL logging
auth_client:
enabled: true # Enable auth middleware
issuer: "http://localhost:8100" # Must match auth.issuer
audience: "pico-bot" # Must match auth.audience
accepted_algorithms: # Algorithms accepted for verification
- "RS256"
- "ML-DSA-65"
- "ML-DSA-87"
fastapi:
title: "Pico Auth API"
version: "0.2.0"Environment variable override example:
AUTH_ISSUER=https://auth.myapp.com AUTH_ADMIN_PASSWORD=strong-password python -m pico_auth.main
# With ML-DSA-65 post-quantum algorithm
AUTH_ALGORITHM=ML-DSA-65 python -m pico_auth.mainAccess tokens include:
| Claim | Description |
|---|---|
sub |
User ID |
email |
User email |
role |
User role (superadmin, org_admin, operator, viewer) |
org_id |
Organization ID |
groups |
Group IDs the user belongs to |
iss |
Issuer URL |
aud |
Audience |
iat |
Issued at (Unix timestamp) |
exp |
Expiration (Unix timestamp) |
jti |
Unique token ID |
Pico-Auth is built on:
| Package | Role |
|---|---|
| pico-ioc | Dependency injection container |
| pico-boot | Bootstrap and plugin discovery |
| pico-fastapi | FastAPI integration with @controller |
| pico-sqlalchemy | Async SQLAlchemy with SessionManager |
| pico-client-auth | JWT auth middleware with SecurityContext |
# Install in dev mode
pip install -e ".[dev]"
# Run tests
pytest tests/ -v
# Run with coverage
pytest --cov=pico_auth --cov-report=term-missing tests/
# Full test matrix
tox
# Lint
ruff check pico_auth/ tests/
# Docker E2E tests (requires Docker and ../pico-client-auth)
pytest tests/test_docker_e2e.py -m docker -vDocker E2E tests build a local image (Dockerfile.local), start a container, and run HTTP tests against it. They are excluded from the default test suite — use -m docker to run them. See Docker E2E Test for details.
MIT - LICENSE