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
-
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 fromsoftware_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
vssoftware_jwks_endpoint
; redirect URIs subset checks; algorithm policy (PS256/RSA-OAEP/A256GCM).
- Deterministic
-
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. -
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.
-
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 DNX-BANK-Certificate-Serial
: serialX-BANK-Certificate-Verify
: resultBANK-TLS-Certificate
: base64 PEM of client cert
Sequence: DCR with SSA (OBUK)

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
withopenid
).
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:
- Gateway ensures mTLS; forwards base64 PEM of client cert in
BANK-TLS-Certificate
. - 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). - 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
Threat | Mitigation |
---|---|
Duplicate registrations / client sprawl | Deterministic client_id + SSA hash idempotency |
Rogue SSA / tampering | PS256 verification against OB Directory JWKS; SSA precedence |
Key mismatch / token theft | Transport cert public key vs cnf comparison; PF policy enforcement |
Revoked TPP cert used | Gateway OCSP check per request (RFC 2560) |
Algo downgrade | Strict alg policy (PS256, RSA-OAEP, A256GCM) |
Replay within SSA lifetime | iat 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.