Case Study: Santander UK - Open Banking DCR That Actually Works

When PingFederate didn't support UK OBUK-style Dynamic Client Registration out of the box, we built it - safely, deterministically, and auditably - and enabled Santander UK to onboard TPPs at pace.

Client Overview

Santander UK needed to enable Open Banking onboarding for third-party providers (TPPs) using the UK Open Banking (OBUK) profile. The bank runs PingFederate as its OpenID Provider and required standards-compliant DCR based on SSA from the Open Banking Directory - across multiple segments/brands and channels.

The Challenge

  • No native OBUK DCR in PingFederate: PF's out-of-box DCR did not accept/validate OBUK SSAs or enforce all required metadata rules.
  • Security hardening: CBAT was not yet available, but token binding to the client's key material was required.
  • Lifecycle & audit: The original SSA needed to be retrievable for client management (update/delete) and audits.
  • Operational hygiene: Avoiding duplicate client creation from repeated SSA submissions; aligning PF data model with UK OB DCR profile.

What We Built

  1. Golang DCR Extension for PingFederate (OBUK SSA)

    We implemented a Golang service/plugin that augments PingFederate's DCR to fully process OBUK SSAs (RFC 7591 + OIDC Registration profile), validate payloads and signature (PS256), and map OB claims to PF client metadata.

    • Deterministic client_id derived from software_id + software_version to stop duplicate sprawl.
    • Strict precedence rules: SSA values take priority over outer JSON as per RFC 7591.
    • Validation of jwks_uri vs software_jwks_endpoint; redirect URIs subset checks; algorithm policy (PS256/RSA-OAEP/A256GCM).
  2. CBAT-style Transport Cert Binding (pre-CBAT)

    Together with Dan, we enforced a match between the incoming mTLS transport certificate and the client's cnf value (key confirmation) - split across PingFederate and the Golang extension - providing certificate-bound access behaviour before CBAT was formally available.

  3. SSA Persistence & RFC 7592-friendly Management

    We extended PingFederate's Extended Properties to store the original SSA and wrote the SSA hash into the PF database for integrity, idempotency and audit trails. This enabled later client read/update/delete flows to retrieve the exact SSA used at registration time.

  4. Infrastructure & Gateway Integration

    Integrated with the mTLS gateway to ensure cert validity (issuer trust, expiry, OCSP) and propagated cert details via headers to upstream services for policy enforcement.

The Results

  • Standards-aligned DCR for OBUK with robust SSA handling and policy enforcement.
  • Improved security posture via certificate-bound behaviour (transport cert vs cnf checks).
  • Operational control: deterministic client IDs, fewer duplicates, cleaner lifecycle management.
  • Auditability: SSA stored and hashed for retrieval, investigation and compliance evidence.
  • Developer experience: predictable onboarding flows for TPPs; simpler support for multi-segment routing.

Technical Deep Dive

Scope & Objectives

  • Implement OBUK DCR using Software Statement Assertions (SSA) with PingFederate as OP.
  • Provide deterministic client_id allocation to prevent duplicate client sprawl.
  • Enforce certificate-bound access before CBAT existed: transport mTLS cert must match client's cnf key material.
  • Persist the original SSA + SSA hash for RFC 7592-style management, audit, and idempotency.

High-Level Architecture

TPP (mTLS) ──► TLS Termination/Gateway (OCSP, trust) ──► PF DCR Extension (Golang) ──► PingFederate Admin API
                                               │                                            │
                                               │                                            └──► PF OAuth Client Store (PostgreSQL)
                                               └──► Propagates cert headers to upstream
    

Gateway → Upstream Certificate Header Mapping

  • X-BANK-Certificate-DN: subject DN
  • X-BANK-Certificate-Serial: serial
  • X-BANK-Certificate-Verify: result
  • BANK-TLS-Certificate: base64 PEM of client cert

Sequence: DCR with SSA (OBUK)

Sequence diagram: OBUK DCR via PingFederate and Golang extension (click to open full size in a new tab)

Click the diagram to open it full size in a new tab.

Example Payloads (sanitised)

DCR Request (excerpt)

       {
  "application_type": "web",
  "grant_types": ["client_credentials","authorization_code","refresh_token","implicit"],
  "response_types": ["code id_token"],
  "token_endpoint_auth_method": "tls_client_auth",
  "introspection_endpoint_auth_method": "tls_client_auth",
  "revocation_endpoint_auth_method": "tls_client_auth",
  "tls_client_auth_subject_dn": "CN=<software_id>,O=Open Banking,C=UK",
  "id_token_signed_response_alg": "PS256",
  "request_object_signing_alg": "PS256",
  "request_object_encryption_alg": "RSA-OAEP",
  "request_object_encryption_enc": "A256GCM",
  "require_signed_request_object": true,
  "require_pushed_authorization_requests": true,
  "jwks_uri": "https://keystore.sandbox.directory.openbanking.org.uk/.../application.jwks",
  "redirect_uris": ["https://oauth.example/cb"],
  "software_statement": "<SSA JWT (PS256)>"
}
 

DCR Response (RFC 7592-style when enabled)

 {
  "client_id": "b8986a43-e730-460f-97f9-9ad90f4e31d2-1.00",
  "grant_types": ["implicit","authorization_code","client_credentials","refresh_token"],
  "response_types": ["code id_token"],
  "jwks_uri": "https://keystore.../application.jwks",
  "registration_client_uri": "https://matls.santander.bank/as/clients.oauth2/{client_id}",
  "registration_access_token": "....",
  "require_signed_request_object": true,
  "tls_client_auth_subject_dn": "CN=...,O=Open Banking,C=UK"
}
 

Note: UK OB profile typically omits the 7592 management token; we supported both modes.

Validation Rules (selected)

  • SSA signature verified (PS256) using OB Directory JWKS; iss, iat (<= 5 min), exp checked.
  • SSA precedence: where fields appear in both JSON body and SSA, SSA wins (RFC 7591 §3.1).
  • jwks_uri must equal SSA software_jwks_endpoint; redirect URIs ⊆ SSA list.
  • Algorithms constrained: id_token_signed_response_alg=PS256, request_object_signing_alg=PS256, enc=A256GCM, alg=RSA-OAEP.
  • Auth methods consistent across token/introspection/revocation (typically tls_client_auth).
  • Roles→Scopes mapping enforced (AIS→accounts, PIS→payments, CBPII→fundsconfirmations with openid).

Deterministic client_id Algorithm

 // Pseudocode (Golang-ish)
func deriveClientID(softwareID, softwareVersion string) string {
  // Normalise version (e.g., "1.00"); ensure ASCII/upper for DN safety if used.
  v := normaliseVersion(softwareVersion)
  return fmt.Sprintf("%s-%s", softwareID, v)
}

// On POST:
// 1) compute cid := deriveClientID(SSA.software_id, SSA.software_version)
// 2) if client exists with cid: treat as idempotent (optionally compare SSA hash); else create
 

This prevented duplicate client sprawl when the same SSA was submitted multiple times.

Pre-CBAT Certificate Binding (transport cert vs cnf)

Before CBAT was mainstream, we enforced a practical equivalent:

  1. Gateway ensures mTLS; forwards base64 PEM of client cert in BANK-TLS-Certificate.
  2. DCR service extracts the leaf public key from the PEM and compares it to the cnf (or equivalent) key material associated with the client's registration (from SSA/JWKS).
  3. PingFederate policy (with Dan's changes) rejects tokens/requests when the presented transport key does not match the registered client key material.
assert samePublicKey( transportCert.PublicKey, registeredClientKey ) == true
 

SSA Persistence & Hashing

  • Stored original SSA JWT in Extended Properties on the PF client record (for read/update/audit).
  • Computed and stored SSA hash (e.g., SHA-256) to support idempotency checks and integrity verification.
  • Adjusted PF DB schema where necessary to avoid truncation (extended property column length → unlimited).

PF Admin API Usage

POST   /oauth/clients               // create-by-ID (RFC 7592 friendly)
GET    /oauth/clients/{id}          // read
PUT    /oauth/clients/{id}          // update
DELETE /oauth/clients/{id}          // delete
GET    /extendedProperties
PUT    /extendedProperties          // define SSA & SSA_HASH keys for client metadata 

DB Schema Note (PostgreSQL)

-- Example (illustrative): increase storage for extended properties
ALTER TABLE pf_oauth_client_attributes
ALTER COLUMN attribute_value TYPE text;  -- from varchar(1024) to text/unlimited

RFC 7592-style Management (when enabled)

POST   /dcr                         -> create (returns registration_access_token)
GET    /dcr/{client_id}             -> read   (bearer: registration_access_token)
PUT    /dcr/{client_id}             -> update (bearer: registration_access_token)
DELETE /dcr/{client_id}             -> delete (bearer: registration_access_token)

UK OB profile variant instead uses client credentials for GET/PUT/DELETE and omits the management token - we supported both patterns.

Error Handling Mapping

  • invalid_redirect_uri - redirect not in SSA set.
  • invalid_client_metadata - alg/policy/roles/scopes mismatches.
  • invalid_software_statement - bad signature/expired/claims missing.
  • unapproved_software_statement - SSA issuer not trusted / org inactive.
  • 401/403 - transport cert absent/invalid; cnf mismatch.

Threats & Mitigations

ThreatMitigation
Duplicate registrations / client sprawlDeterministic client_id + SSA hash idempotency
Rogue SSA / tamperingPS256 verification against OB Directory JWKS; SSA precedence
Key mismatch / token theftTransport cert public key vs cnf comparison; PF policy enforcement
Revoked TPP cert usedGateway OCSP check per request (RFC 2560)
Algo downgradeStrict alg policy (PS256, RSA-OAEP, A256GCM)
Replay within SSA lifetimeiat window ≤ 5 minutes + hash check

Multi-Segment Routing

Supported single entry with segment routing via subdomains/paths, e.g.:

https://matls.yellow.santander.bank/as/clients.oauth2
https://matls.santander.bank/green/as/clients.oauth2
    

Gateway normalises to one DCR endpoint and injects brand_id header consumed by PingFederate.

Testing & Conformance

  • SSA signature/claim tests with synthetic SSAs and expiry windows.
  • mTLS negative tests: bad issuer, revoked cert (OCSP), wrong key vs cnf.
  • Algorithm policy enforcement unit tests.
  • Lifecycle tests: update/delete with SSA retrieval; idempotent re-POST.

Documentation & Knowledge Transfer

  • Runbooks: DCR operations, client lifecycle, troubleshooting.
  • Admin guides: PF extended properties, management endpoints, schema backup.
  • Security notes: OCSP dependencies, header integrity from gateway, policy checks.

Engagement delivered in collaboration with Raidiam. Craig Greenhouse (via Obsequio Software Ltd - now Opendata Consult Ltd) was principal consultant/lead engineer, working with Dan (Raidiam IAM Consultant) and the team at Santander UK.

👋 Enjoyed the article?

Book a Call with Us