Auth Cookbook¶
This cookbook collects practical authentication patterns for flarchitect projects. It complements the reference Authentication guide with end‑to‑end snippets that you can copy, adapt and deploy.
Contents¶
JWT patterns (HS256 and RS256, refresh, rotation)
Basic authentication
API key strategies (lookup function vs. hashed field)
Role mapping examples (decorators and config‑driven)
Multi‑tenant considerations (claims, scoping, isolation)
JWT patterns¶
Minimal configuration:
app.config.update(
API_AUTHENTICATE_METHOD=["jwt"],
ACCESS_SECRET_KEY=os.environ["ACCESS_SECRET_KEY"],
REFRESH_SECRET_KEY=os.environ["REFRESH_SECRET_KEY"],
API_USER_MODEL=User,
API_USER_LOOKUP_FIELD="username",
API_CREDENTIAL_CHECK_METHOD="check_password",
API_JWT_EXPIRY_TIME=360, # minutes
API_JWT_REFRESH_EXPIRY_TIME=2880, # minutes
API_JWT_ALLOWED_ALGORITHMS=["HS256"],
)
Endpoints:
POST /auth/login
→ returnsaccess_token
andrefresh_token
.POST /auth/refresh
→ accepts JSON{"refresh_token": "<token>"}
(a leading"Bearer "
prefix is tolerated and removed), then rotates the refresh token and issues a new access token. Invalid refresh JWTs respond with401
; unknown/revoked/expired refresh tokens respond with403
.POST /auth/logout
→ clears user context (stateless logout).
Key rotation with RS256¶
Prefer asymmetric keys in production. Keep multiple active keys and use
kid
headers for selection:
app.config.update(
API_AUTHENTICATE_METHOD=["jwt"],
API_JWT_ALGORITHM="RS256",
# Current signing keys (PEM strings). Store via secrets, not in code.
ACCESS_PRIVATE_KEY=os.environ["ACCESS_PRIVATE_KEY"],
REFRESH_PRIVATE_KEY=os.environ["REFRESH_PRIVATE_KEY"],
# Verification keys (public). Support multiple for rotation.
ACCESS_PUBLIC_KEY=os.environ["ACCESS_PUBLIC_KEY"],
REFRESH_PUBLIC_KEY=os.environ["REFRESH_PUBLIC_KEY"],
API_JWT_ALLOWED_ALGORITHMS=["RS256"],
)
When issuing tokens, include a kid
header and keep a small in‑memory map of
active public keys. Rotate by introducing a new keypair, marking the old public
key as still valid for verification, then retiring it after all issued tokens
expire. See authentication for claim settings (iss
, aud
,
leeway
).
Production tips¶
Store secrets in environment variables or file‑based secrets mounted into the container. Never commit secrets to source control.
Restrict algorithms via
API_JWT_ALLOWED_ALGORITHMS
; setiss
andaud
claims and validate them for defence in depth.Keep refresh tokens single‑use (default) and log rotation events for audit.
Basic authentication¶
Simple username/password verification against your user model:
app.config.update(
API_AUTHENTICATE_METHOD=["basic"],
API_USER_MODEL=User,
API_USER_LOOKUP_FIELD="username",
API_CREDENTIAL_CHECK_METHOD="check_password",
)
Send Authorization: Basic <base64(username:password)>
. Protect specific
routes with @architect.schema_constructor(..., auth=True)
or global configs.
API key strategies¶
Lookup function (flexible):
def lookup_user_by_token(token: str) -> User | None:
return User.query.filter_by(api_key=token).first()
app.config.update(
API_AUTHENTICATE_METHOD=["api_key"],
API_KEY_AUTH_AND_RETURN_METHOD=staticmethod(lookup_user_by_token),
)
Hashed field (safer at rest):
app.config.update(
API_AUTHENTICATE_METHOD=["api_key"],
API_USER_MODEL=User,
API_CREDENTIAL_HASH_FIELD="api_key_hash",
API_CREDENTIAL_CHECK_METHOD="check_api_key",
)
Clients send Authorization: Api-Key <token>
.
Role mapping examples¶
Decorator‑based RBAC:
from flarchitect.authentication import require_roles
from flarchitect.core.architect import jwt_authentication
@app.get("/admin")
@jwt_authentication
@require_roles("admin")
def admin_panel():
...
Config‑driven roles (no decorators):
app.config.update(
API_AUTHENTICATE_METHOD=["jwt"],
API_ROLE_MAP={
"GET": ["viewer"],
"POST": {"roles": ["editor", "admin"], "any_of": True},
"PATCH": ["editor", "admin"],
"DELETE": ["admin"],
# Optional catch‑all to require auth for unspecified methods
"ALL": True,
},
)
See Role-based access and the reference Authentication for details.
Multi‑tenant considerations¶
Claims & token shape¶
Include a tenant identifier in JWTs and validate it on requests:
# When issuing tokens
payload = {"sub": user.id, "tenant_id": user.tenant_id, "roles": user.roles}
# During request handling (pseudo‑code)
@jwt_authentication
def view():
tenant_id = current_user.tenant_id # derived from token/user
# Apply tenant scope to queries
items = Item.query.filter_by(tenant_id=tenant_id).all()
Scoping and isolation¶
Persist
tenant_id
on tenant‑owned models; enforce it in query helpers or via a session/mapper event so all generated endpoints auto‑scope results.For config‑driven roles, ensure roles are interpreted within the tenant’s context (e.g.,
admin
within a tenant, not globally).Consider per‑tenant issuers (
iss
) or audiences (aud
) to improve validation and separate concerns across tenants.
Operational practices¶
Key management: rotate signing keys without cross‑tenant leakage; prefer centralised JWKS with short cache TTLs if using multiple issuers.
Testing: add property tests that randomly mix tenants to catch cross‑tenant access regressions.
Logging: include
tenant_id
in structured logs for traceability.
Further reading¶
Reference guide: Authentication
Configuration: Configuration
Error handling: Error Handling