Enable Banking OAuth and PSD2 Authentication#
Sure uses Enable Banking as a PSD2-compliant intermediary to connect European bank accounts. The integration is a three-party OAuth flow: Sure (TPP) → Enable Banking API → individual bank (ASPSP). Sure holds developer credentials issued by Enable Banking; users authenticate directly with their bank via Enable Banking's hosted pages. Only European banks are supported (EU member states plus Iceland, Liechtenstein, Norway, and the UK).
Key source files:
| File | Role |
|---|---|
app/models/provider/enable_banking.rb | API client — JWT auth, all HTTP calls |
app/controllers/enable_banking_items_controller.rb | OAuth flow controller — authorize, callback, reauthorize |
app/models/enable_banking_item.rb | Session/consent model — begin_authorization!, complete_authorization |
app/helpers/application_helper.rb | enable_banking_callback_url helper |
Three-Party OAuth Flow#
Step 1 — Start authorization. The authorize controller action calls begin_authorization! on the EnableBankingItem. This re-fetches live ASPSP metadata via GET /aspsps, auto-selects the best auth method (priority: REDIRECT > DECOUPLED > EMBEDDED — see §Auth Method Selection below), then calls Provider::EnableBanking#start_authorization which posts to POST /auth. The state parameter is set to the item's database ID for safe round-trip lookup.
Step 2 — User authenticates at bank. Sure redirects the user to the URL returned by POST /auth. Only HTTPS URLs from enablebanking.com, api.enablebanking.com, or auth.enablebanking.com are trusted; anything else is blocked as an open-redirect guard.
Step 3 — Callback. Enable Banking redirects the user back to the callback URL. The callback action (CSRF-exempt) parses code and state, resolves the item, then calls complete_authorization(code:) which exchanges the code for a session via POST /sessions. The resulting session_id, session_expires_at, and accounts are persisted. A background sync is queued immediately.
Reauthorization (expired sessions) follows the same path via reauthorize, which calls begin_authorization! with the existing aspsp_name instead of prompting for a bank selection.
JWT Authentication with Enable Banking API#
Every request to https://api.enablebanking.com carries a short-lived JWT in the Authorization: Bearer header. The token is generated fresh per request by generate_jwt:
- Algorithm: RS256 (asymmetric)
- Key: RSA private key extracted from the operator-supplied
client_certificatePEM - Header
kid: the Enable Bankingapplication_id - Claims:
iss: "enablebanking.com",aud: "api.enablebanking.com",iat+exp(1 hour window)
The private key is parsed at construction time by extract_private_key; an OpenSSL::PKey::RSAError raises EnableBankingError(:invalid_certificate). A 401 response from the API surfaces as EnableBankingError(:unauthorized) ("Invalid credentials or expired JWT"), and 403 as EnableBankingError(:access_forbidden).
The Provider::EnableBanking client is initialized with an application_id and client_certificate sourced from the EnableBankingItem record's stored credentials.
Callback URL Configuration#
The callback URL must be registered in the Enable Banking developer portal and must use HTTPS. Sure resolves it via enable_banking_callback_url in ApplicationHelper:
- Production: uses the Rails-generated route
callback_enable_banking_items_url - Development / self-hosted: reads
DEV_WEBHOOKS_URLenv var (e.g., an ngrok tunnel URL) and appends/enable_banking_items/callback; falls back toroot_urlif unset
The controller delegates to the same helper via helpers.enable_banking_callback_url. A REDIRECT_URI_NOT_ALLOWED error from Enable Banking surfaces as a user-facing alert showing the exact callback URL to register, aiding diagnosis.
For local/self-hosted deployments without HTTPS, the setup flow will not work without an HTTPS tunnel — this is an Enable Banking platform requirement, not a Sure limitation.
Auth Method Selection and Decoupled (PSD2) Authentication#
PSD2 banks may require Strong Customer Authentication (SCA) via a decoupled flow (push notification, photoTAN, chipTAN) rather than a browser redirect. Sure selects the auth method automatically from the ASPSP's auth_methods list using priority order REDIRECT > DECOUPLED > EMBEDDED, excluding hidden methods. If no method matches the requested PSU type, it falls back to untyped methods.
The selected auth_method name is included in the POST /auth body ; when nil, Enable Banking falls back to the bank's default. Decoupled and embedded banks proceed through Enable Banking's hosted SCA page — Sure never handles the SCA challenge directly.
Historical note: Prior to v0.7.2 (PR #2174, merged June 4, 2026), Sure blocked banks with decoupled-only auth methods with the error "This bank uses a separate device authentication method which is not yet supported." This affected banks like DKB and Buddy Bank. The fix landed in v0.7.2-alpha.3 and shipped in the v0.7.2 stable release.
ASPSP metadata (including aspsp_auth_approach, aspsp_required_psu_headers, aspsp_maximum_consent_validity) is stored on the EnableBankingItem record and re-fetched fresh at the start of each authorization to stay current.
Error Handling Reference#
All API calls go through handle_response which raises typed EnableBankingError instances:
| HTTP code | error_type | Meaning |
|---|---|---|
| 400 | :bad_request | Malformed request (e.g. bad ASPSP name) |
| 401 | :unauthorized | Invalid credentials or expired JWT |
| 403 | :access_forbidden | App permissions not configured |
| 404 | :not_found | Session or account not found |
| 408 | :timeout | Enable Banking API timeout |
| 422 | :validation_error | Includes response data; used for WRONG_TRANSACTIONS_PERIOD auto-retry |
| 429 | :rate_limited | Rate limit exceeded |
Network failures (SocketError, Net::OpenTimeout, Net::ReadTimeout) are rescued and re-raised as EnableBankingError(:request_failed).
The :validation_error type has special handling for transaction date range errors: if response_data[:error] == "WRONG_TRANSACTIONS_PERIOD", get_account_transactions automatically retries once with the corrected date_from extracted from the API error response.
Operator setup errors — 401/403 at auth time almost always mean the application_id or client_certificate is wrong, or the Enable Banking developer application is not yet activated. A REDIRECT_URI_NOT_ALLOWED error in the authorize action means the callback URL hasn't been registered in the developer portal.