Skip to main content

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 an id_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.
Banno tokens are never sent to the browser. They live in a server-side cache (Django cache) keyed by 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:
The code_verifier and state are stored in the Django cache keyed by a session identifier and retrieved during callback validation.

Authorization Request

Additional claims requested in the claims parameter:

Token Exchange

Response:

id_token Claims Consumed

Server-Side Token Cache

Banno tokens are cached server-side, keyed by user_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.
This keeps the iframe session alive during normal Banno dashboard use without bouncing members back through OIDC every 10 minutes.

Callback Validation

JWKS signature verification for the Banno id_token is planned; the current build decodes without verify_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.

Security Controls