Overview
The Banno integration uses a two-layer authentication model:- Layer 1 — Banno OIDC + PKCE — external sign-in with Banno. Produces a Banno
access_token(for Consumer API calls) and anid_token(for member identity). - Layer 2 — Internal JWT — a short-lived HS256 token issued by the RAF service, stored in an httpOnly cookie, that gates every RAF view and API call after sign-in.
user_id. The client only ever sees the internal JWT cookie.
Layer 1 — Banno OIDC + PKCE
PKCE Pair Generation
On every auth start, the service generates a fresh PKCE pair and a CSRF state token:code_verifier and state are stored in the Django cache keyed by a session identifier and retrieved during callback validation.
Authorization Request
claims parameter:
Token Exchange
id_token Claims Consumed
Server-Side Token Cache
Banno tokens are cached server-side, keyed byuser_id, with a TTL matching expires_in. The client never sees them:
Layer 2 — Internal JWT
After the token exchange, the RAF service issues its own internal JWT and drops it into an httpOnly cookie.Properties
Claims
Silent Refresh
The@cookie_jwt_required decorator transparently re-issues the internal JWT as long as the server-side Banno token cache still holds a valid access token:
- Internal JWT present and valid → proceed.
- Internal JWT expired and Banno cache still valid → re-issue internal JWT silently, set new cookie, proceed.
- Internal JWT expired and Banno cache empty/expired → redirect to
/auth/for full re-auth.
Callback Validation
JWKS signature verification for the Bannoid_tokenis planned; the current build decodes withoutverify_signature. Add JWKS validation before production rollout.
Bearer Token API
The JSON API (/api/...) accepts the same internal JWT as a Bearer token in the Authorization header, for clients that can’t use cookies (e.g. server-to-server or a native mobile harness). The helper _get_bearer_user_id_or_error(request) parses and validates the header.
