Enable Banking Consent Management#
Enable Banking (European Open Banking / PSD2) uses a session-based consent model: a user authorizes access to their bank (ASPSP) once, which creates a time-bounded session on the Enable Banking API. Sure stores the session ID and its expiry locally and keeps that expiry in continuous sync with the API's authoritative value during every import.
Key model: EnableBankingItem (enable_banking_items table) — one record per bank connection per family. It holds all consent-relevant fields: session_id, session_expires_at, authorization_id, status (enum: good / requires_update), and ASPSP metadata columns.
Authorization and Session Creation#
The consent flow has two steps, both driven by EnableBankingItem:
-
Start authorization —
begin_authorization!(orstart_authorizationwhen called directly) callsGET /aspspsto re-fetch fresh ASPSP metadata, then callsPOST /authon the Enable Banking API viaProvider::EnableBanking#start_authorization. The response'saccess.valid_untilis derived fromaspsp_maximum_consent_validity(capped at 90 days). The returnedauthorization_idis stored on the item. -
Complete authorization —
complete_authorizationexchanges the OAuth callbackcodefor a session viaPOST /sessions. The resultingsession_idandaccess.valid_untilare persisted; if the API omitsvalid_until, Sure defaults to 90 days from now. Status is reset to:goodand provider accounts are imported from the session response.
Session Validity Checks#
Three methods on EnableBankingItem gate all session-dependent operations :
| Method | Condition |
|---|---|
session_valid? | session_id present AND (session_expires_at is nil OR in the future) |
session_expired? | session_id present AND session_expires_at present AND in the past |
needs_authorization? | negation of session_valid? |
The EnableBankingItem::Syncer calls session_valid? as a guard at the top of every sync. If the session is invalid, it immediately sets status: :requires_update and aborts — no API calls are made.
Continuous Session Expiry Reconciliation#
Because the Enable Banking API is the authoritative source of session expiry, Sure re-syncs the local session_expires_at on every import via reconcile_session_expiry!.
Call path: EnableBankingItem::Syncer → import_latest_enable_banking_data → EnableBankingItem::Importer#import → fetch_session_data (calls GET /sessions/{session_id}) → reconcile_session_expiry!(session_data)
The reconciliation method extracts access.valid_until from the API response and updates session_expires_at only if the value has changed. It is deliberately fault-tolerant: bad timestamps (ArgumentError/TypeError) and database update failures are caught, logged as warnings, and swallowed so a sync is never derailed by expiry bookkeeping.
This prevents two drift states:
- Premature expiry: local record thinks the session expired, but the API extended it
- Stale validity: local record thinks the session is still valid, but the API revoked it
ASPSP Metadata Storage#
During start_authorization, Sure captures bank-capability metadata from the GET /aspsps response and stores it on the item :
| Column | Type | Purpose |
|---|---|---|
aspsp_required_psu_headers | jsonb (array) | Headers that must be forwarded with API calls to this bank |
aspsp_maximum_consent_validity | integer (seconds) | Bank's max allowed consent duration; caps the valid_until in POST /auth |
aspsp_auth_approach | string | Selected auth approach: REDIRECT, DECOUPLED, or EMBEDDED |
aspsp_psu_types | jsonb (array) | PSU types the bank supports (personal, business) |
last_psu_ip | string | User IP for PSU context headers (PII — see GDPR note below) |
These columns were added in migration 20260405120000.
Auth method selection: select_auth_method picks the best authentication approach from the ASPSP's auth_methods list using a priority order of REDIRECT > DECOUPLED > EMBEDDED, excluding hidden methods. If the requested PSU type has no matching methods, it falls back to methods without a declared type.
PSU type validation: A model-level validator (psu_type_in_aspsp_types) ensures the configured PSU type is always one the ASPSP advertises.
⚠️ GDPR / PII note:
last_psu_ipcontains the user's IP address required for PSU context headers. A TODO in the model flags this for a data retention policy (nullify after session expiry or 90 days).
Session Revocation#
revoke_session calls DELETE /sessions/{session_id} and clears session_id, session_expires_at, and authorization_id regardless of whether the API call succeeds. API errors are logged as warnings and suppressed so local cleanup always completes.
Key Source Files#
| File | Purpose |
|---|---|
app/models/enable_banking_item.rb | Core model: session methods, ASPSP logic, reconciliation |
app/models/provider/enable_banking.rb | API client: /auth, /sessions, /aspsps endpoints |
db/migrate/20260405120000_add_aspsp_metadata_to_enable_banking.rb | ASPSP metadata columns migration |
app/models/enable_banking_item/syncer.rb | Sync orchestration with session guard |
app/models/enable_banking_item/importer.rb | Import logic + fetch_session_data → reconcile_session_expiry! |