What is Open Banking
The idea behind Open Banking is to give the consumers of financial institutions the ability to exchange their financial information with fintech applications safely. Open Banking is becoming a global phenomenon and is being implemented in an increasing number of jurisdictions worldwide. Each jurisdiction establishes a legal framework that permits the development of a regional Open Banking ecosystem. The ecosystem aims to ensure that Recipients (such as fintech) and Data Holders (such as financial institutions) exchange data safely and efficiently.
About Financial Data Exchange
Financial Data Exchange (FDX) is a non-profit industry standards body that is handling Open Banking adoption in the United States and Canada. FDX is committed to the following five fundamental concepts of user-permitted data sharing: Control, Access, Transparency, Traceability, and Security. Its primary purpose is to promote, improve, and work toward widespread adoption of the FDX API technical standard.
FDX Ecosystem
End users use software applications (also known as Data Recipients) to manage their finances or employ financial services. Data Recipients can either use Data Access Platforms or Data Aggregators to connect to financial institutions (Data Holders) or connect to them directly.
The diagram below illustrates an example flow of a Data Recipient gaining access to a user’s account.
Data Recipient Application
The implementation assumes the existence of a Data Provider Platform that connects to a financial institution. The article describes the authentication flow and provides details on the implementation in Node.js.
In FDX, the Data Recipient Application initiates the authorization request. For authorization, the Pushed Authorization Request (PAR) grant type can be applied. PAR ensures the highest security level for applications using it. This grant type is among the prerequisites of compliance with the FAPI security profile.
The flow starts with the application sending authorization_details
to the PAR endpoint. Such a
call requires some information from the OpenID server. Besides the
/par
endpoint URL, we must also know the /token
and /authorize
URLs.
We can retrieve these URLs from the metadata that the OpenID server publishes through the
.well-known
endpoint, typically at: /.well-known/openid-configuration
.
Since the authentication method for the par
and token
endpoints is set to “Self-Signed
TLS Client Authentication,” we need to generate a Self-Signed Certificate with Private Key (X.509 PEM
Format) and provide the JWKS—a Public and Private Keypair Set where the Private Key contains the x5c
value—to the Data Access Platform.
Below are the certificates and JWKS. We will use them later.
-
Self-Signed Certificate (
client.crt
)-----BEGIN CERTIFICATE----- MIIE6jCCAtKgAwIBAgIGAYTjbUsNMA0GCSqGSIb3DQEBCwUAMDYxNDAyBgNVBAMM K2VnRzV5em1IVjZob3dVOXN5UHd0T011bGlrUWlnaF9lUE5pTHlJQWJ5NFEwHhcN MjIxMjA1MTc1NTAxWhcNMjMxMDAxMTc1NTAxWjA2MTQwMgYDVQQDDCtlZ0c1eXpt SFY2aG93VTlzeVB3dE9NdWxpa1FpZ2hfZVBOaUx5SUFieTRRMIICIjANBgkqhkiG 9w0BAQEFAAOCAg8AMIICCgKCAgEAoXERLEmuaLV6v878SWgZPyatk76sjlhiwMML rnEpiYsyGwr1prbB4kLGHgAubW/pI7gQaSQppacjLZurB9OTRzqY3L/j8r1xuW7i khzKBrH178SpCkcVHPQtQia0wIQ4vevC1hD3ze8k3eNGm6qR1tFDd2Y6D2cHMaPA 0OuC6pjsyrAtfXhWLHdtY+nEbDjNR1PwKBXg918yZRZJPy/o4tMAwJw66X74vU6R Pxfv9VkAgIS9j7aVbWEsr8LaN7UHVP2BRRhFASqioFeXJA2Mvnxa7Yi7uPeWr4DQ HZJPXjSPp9ElFsMvVdge1Q7cJPUzKKcoy+CHAZS6klqITPL0VEXUBoExUhmaEa0c p1oSV4cwmaC6MGbNlOR4Tv7BKa0tPOdWvEq3WmEnvVDUSqPd36vS/qnkjcdm1+hy BVkmTQPoslj7UR+i24EsVE5rrnIVtMf57AJXTf1BJjrYdYJJmHwQAv7Oybs1arkX /Nbujzigi7TLi30Rt/DYvQCQT6xd2HEStQTOKHQihwsNl3o4o4asEsJnoJB1co5h 5VjfhQU++njGPUIMeMGErnoX3vfls6pw4B9i2VwuwFUzK5nHSFGryXbQGbzWHr/e z3QJK601WgmbNxQ3mVCfQmikxM0j0n9G2C4IHdzayk5enAmV3PNbtooilFOAzC3x Q6v99mMCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAbWxcrXpFI3ouTuMoc1srdoEu 9Vr876gAikDHNklLYus/jRfsRlIbYCDNLCrsANzt3GBp5uvXAJvICDTwhdKdUr5F KwNzzIoS+IpOT6FVZBzLiqCqCPZAKV0pii82zSOY/eVTbM095wE0W9vzdUwmNki+ e/KQFKosMXfMo+NCTRpQVZGVI+sBuuO3zz5QId3nSLDPus+UZKQFqYIEOySEXDWW zgkpliiHsGoOZqlGPK04zxCN9smKHvjznyWb+Ew8acf4ccL83Ly0Hk7hWrGM/6qM 2AG/9W5uuRNdOa9bSZaf9vd24K1wc8mzuKYVDQ7o0v+SomVdXff/gEk/iLs9lSrQ 2PyEM4C6TUWMWKPHghDnGmy/7f+jXjr3NAUCygRZrUxOc+E2z2Gro6Yu3a/X4Zcf LLHj4jr0Ri9uHwXaX1EzMh1zpWoU2+aWer8ZPpqlpkh5mKLOIIKOeEZlxU5pUypd CIP/Gx6Fh1qfb5PGt+knpKIU/zaPKAeRdFVbetrNAno3gTXHax4kapJXXbQtz7oL m195DlUWYIyljbwY3slig4cMht7NJIBUprTxGOyydHsrxow9wcjyfkBQAcweKltg mWwLZ5wahwVz8ehwg6HBV9EyqBKp4ESmdPnSiWADCqojMdH27pDsRFyR3ZggRtPF KSfu7p4nc0pbRdOTr8E= -----END CERTIFICATE-----
-
Private Key (
client.key
)-----BEGIN PRIVATE KEY----- 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 57QtKgn2h8dJ/x0WSSdt1li/QmP4 -----END PRIVATE KEY-----
-
JWKS
{ "keys": [ { "p": "1rzNLTR00cXCBkdH6SsGwIS8C2D1HNsVk7oyCkshLuN-t2loyfRGMTETWE0a_VHd7MKD5-UqVa0UCgJhY0iUqfd5hyeom1ovCFz220ApQy9n1TxGfo4Q0q9KGJRIUj6yfmTvy8sWPz-4tcV06c3k4duGobHp1JsRT-JtxGglK28GfBwwrbyEMOM9L-bL6sHlPoSN5TjwbXAPEPiehiA-pYPjaUuAZwTnfjsw2tHLe-zsx2nyTPXV8fukQVxFzV3Cs_9zUmLwB9mTPc2_e8OcCUlXGG4oJ8zrPZevr6RYm-LXGDZ0sV7f0R23fEkF_B-AL_arjOS4UBnKFKb4HMO9gw", "kty": "RSA", "q": "wHaUJI0SpTGGnz25X3w7Q-mb79OsVFvqF8LjUenfF2HfAWs23-CU59Mut4jf14Bt4uwiSTVM0ZormM29Zj8FWzsv7LOglNQsiN1sZuf-Cdmxwujs6NngIYpQsMRy1gSeL1_aKw_hNaY-cOy7fjy_FrFYs4QJHnkoA9qxscRzkUrQ8N9uCW2SBCQOHyoymbrKQLOyvrL8G1Pgcwlac6wqxBO79lGDTmm9tzL37OLqzZKq1el6FDoj9ndFwmQmhPcni-qt-YCb8sBjjD4pq281t6RTaZ8mk2P66dlEHFAK82IXgKmma0ZcrLIzJPh4r57hE3b66bU_XFFUQjTlopFtoQ", "d": "YOG6fYpmCN5HoI21ZtP6uqQabFxzrySpdqZV_0RXufv3gLUOABMSvRZ3UA-pzPDoxw-xgXc7KvbnLHqLo7G7Y3Ztv6PERqqbcb1yaF6wel4XieDJ1KodqHd8JYOW2yU96_NsxAwAueu2gxXeDJLK3FBOnXuSgiXA8nAjyPxcXy6Qg35AflhV1m-BDQF_Z7aasiBUMaoXjDvrwTAjmrDEoEIh5HSdAfy5htl8XpGd5e2YWIfQPktC1xYGvB9B10Zi9QUAq5dJ6c5NM838nJ0pBjMYWg3BIdoq6n1x5x0jVhRS1VG0YTmW0XxV3GQDhoZDQjU2TUsbmb5ITIXZ39u49VlofFjATKlW6S8OOZGgYpNKwhxzzvvgdwJv-QFr_XiM06lUWZvO8LU43cF-HCTS_NOSWCl8lSFYdIftBbDQevhSA3qEw15W2mQDHDXQ3GxGaWbF0FbiKEG_7lWZYhqeRD8vczUrg9e_3EpRnuI5ErcsVzgxTQ4aOv5LUTp4ZpLfJ-Dtfevv9yc5z5oblyt4tJQ4VDMI51vqkgoK0I2pqNq7DvdFfwy8tfQOnAMWcJZgHptcPlY4r1IDujS8mL9mib88KRBBTzzt3skt3BjmVaZuyo6dgX7Tf3bKHFD5pBF-DeP5aaLiZP8lRl_b5gCnzMkiIzGlvOSkJOO7KqRgE0E", "e": "AQAB", "use": "sig", "kid": "egG5yzmHV6howU9syPwtOMulikQigh_ePNiLyIAby4Q", "qi": "Znp2n7cIuh5LYG6plnF1vSLKrIUxFqs2Cd-dJRfD6m0Pnyvyw3wqPjhOvStN61U5wHthFlmqWia9Edsrdbcg-ngIzR9fxLHHC-ZpCCnBvjVnn7ctW32Bl-P45qeYMQ7xNu47tB4PBJFotno3tro63YVf-mYxGNe1TvFebE-I6LpaTWe6v_66ddwP71pXcEhH_l45h0Rm-KcHrGi4qM1NcrKkU0egSUCG934oKrSHNks2Zlbe5euhr2kEoY9fVF4r6xGFeF0eUKq2177P0Ap3Mvebu4ulE23oLdiJ4t6CIeewaFfF5ftVnPTunOe0LSoJ9ofHSf8dFkknbdZYv0Jj-A", "dp": "OtTBFahwdORMMvImdnAuSmJTLn7iyIQRVkbF3OIWdkUne1JqviGpdWKvnRqK0WozdeEYyFiVj6F6gv-B9PR2nlZJukiYPe-oQQ9PJmReqaR8OwRnf9amL0KM90zwv2-j8VLwC5JZRvzLOymczU12bpZ5PSWEHZZaWNt9HTk3Lb4eNbR3dbHXOZmYMt51cZyrlMQ2J93AqQrxZcHnlHGTGD-Y90kTJaZHUeMfkGO37vzLJhsVohYcBk7eAnYoPYp5hFzxGFo4zr3CREVxN5Qw1K5-KRhM8hKHRHmhTWqmSBcOvPh2Yf3PLfiJQ4iyFg__S6S1_UTQZkmafqv7_810xw", "alg": "RS256", "dq": "FZ07hd9R-LtXIwsLhm2tOjNCD-z1rYOlBoYcgVaCd13GbaJcx0excVeGJ5Z3jPwfWxypvQ7_UYq-2qpAWvs43sYG285QemZZmgVD50qmfPS1we3mrT_Kq_mclj15qA9ctHVPSwa9hAjnekcbx1GLJEFGdOKfI4z3sXcd_ZjSYb7FOEffU6-6djyWWWmjxR_1Fs8Xg9MV_PuueTzTq6nqI5sTwNXmqSe25pwSu8fAFtYh2WQfEaZ143gBnZMhwE0BY_U3FI4c9WYlx9ozU81pevY5Z-M3sdXIl_qOa5-Rm0Oo4AotZl7AiaR5me1NoGt3W3hBNX3lX7jCfazzHQ64AQ", "n": "oXERLEmuaLV6v878SWgZPyatk76sjlhiwMMLrnEpiYsyGwr1prbB4kLGHgAubW_pI7gQaSQppacjLZurB9OTRzqY3L_j8r1xuW7ikhzKBrH178SpCkcVHPQtQia0wIQ4vevC1hD3ze8k3eNGm6qR1tFDd2Y6D2cHMaPA0OuC6pjsyrAtfXhWLHdtY-nEbDjNR1PwKBXg918yZRZJPy_o4tMAwJw66X74vU6RPxfv9VkAgIS9j7aVbWEsr8LaN7UHVP2BRRhFASqioFeXJA2Mvnxa7Yi7uPeWr4DQHZJPXjSPp9ElFsMvVdge1Q7cJPUzKKcoy-CHAZS6klqITPL0VEXUBoExUhmaEa0cp1oSV4cwmaC6MGbNlOR4Tv7BKa0tPOdWvEq3WmEnvVDUSqPd36vS_qnkjcdm1-hyBVkmTQPoslj7UR-i24EsVE5rrnIVtMf57AJXTf1BJjrYdYJJmHwQAv7Oybs1arkX_Nbujzigi7TLi30Rt_DYvQCQT6xd2HEStQTOKHQihwsNl3o4o4asEsJnoJB1co5h5VjfhQU--njGPUIMeMGErnoX3vfls6pw4B9i2VwuwFUzK5nHSFGryXbQGbzWHr_ez3QJK601WgmbNxQ3mVCfQmikxM0j0n9G2C4IHdzayk5enAmV3PNbtooilFOAzC3xQ6v99mM", "x5c": [ "MIIE6jCCAtKgAwIBAgIGAYTjbUsNMA0GCSqGSIb3DQEBCwUAMDYxNDAyBgNVBAMMK2VnRzV5em1IVjZob3dVOXN5UHd0T011bGlrUWlnaF9lUE5pTHlJQWJ5NFEwHhcNMjIxMjA1MTc1NTAxWhcNMjMxMDAxMTc1NTAxWjA2MTQwMgYDVQQDDCtlZ0c1eXptSFY2aG93VTlzeVB3dE9NdWxpa1FpZ2hfZVBOaUx5SUFieTRRMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoXERLEmuaLV6v878SWgZPyatk76sjlhiwMMLrnEpiYsyGwr1prbB4kLGHgAubW/pI7gQaSQppacjLZurB9OTRzqY3L/j8r1xuW7ikhzKBrH178SpCkcVHPQtQia0wIQ4vevC1hD3ze8k3eNGm6qR1tFDd2Y6D2cHMaPA0OuC6pjsyrAtfXhWLHdtY+nEbDjNR1PwKBXg918yZRZJPy/o4tMAwJw66X74vU6RPxfv9VkAgIS9j7aVbWEsr8LaN7UHVP2BRRhFASqioFeXJA2Mvnxa7Yi7uPeWr4DQHZJPXjSPp9ElFsMvVdge1Q7cJPUzKKcoy+CHAZS6klqITPL0VEXUBoExUhmaEa0cp1oSV4cwmaC6MGbNlOR4Tv7BKa0tPOdWvEq3WmEnvVDUSqPd36vS/qnkjcdm1+hyBVkmTQPoslj7UR+i24EsVE5rrnIVtMf57AJXTf1BJjrYdYJJmHwQAv7Oybs1arkX/Nbujzigi7TLi30Rt/DYvQCQT6xd2HEStQTOKHQihwsNl3o4o4asEsJnoJB1co5h5VjfhQU++njGPUIMeMGErnoX3vfls6pw4B9i2VwuwFUzK5nHSFGryXbQGbzWHr/ez3QJK601WgmbNxQ3mVCfQmikxM0j0n9G2C4IHdzayk5enAmV3PNbtooilFOAzC3xQ6v99mMCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAbWxcrXpFI3ouTuMoc1srdoEu9Vr876gAikDHNklLYus/jRfsRlIbYCDNLCrsANzt3GBp5uvXAJvICDTwhdKdUr5FKwNzzIoS+IpOT6FVZBzLiqCqCPZAKV0pii82zSOY/eVTbM095wE0W9vzdUwmNki+e/KQFKosMXfMo+NCTRpQVZGVI+sBuuO3zz5QId3nSLDPus+UZKQFqYIEOySEXDWWzgkpliiHsGoOZqlGPK04zxCN9smKHvjznyWb+Ew8acf4ccL83Ly0Hk7hWrGM/6qM2AG/9W5uuRNdOa9bSZaf9vd24K1wc8mzuKYVDQ7o0v+SomVdXff/gEk/iLs9lSrQ2PyEM4C6TUWMWKPHghDnGmy/7f+jXjr3NAUCygRZrUxOc+E2z2Gro6Yu3a/X4ZcfLLHj4jr0Ri9uHwXaX1EzMh1zpWoU2+aWer8ZPpqlpkh5mKLOIIKOeEZlxU5pUypdCIP/Gx6Fh1qfb5PGt+knpKIU/zaPKAeRdFVbetrNAno3gTXHax4kapJXXbQtz7oLm195DlUWYIyljbwY3slig4cMht7NJIBUprTxGOyydHsrxow9wcjyfkBQAcweKltgmWwLZ5wahwVz8ehwg6HBV9EyqBKp4ESmdPnSiWADCqojMdH27pDsRFyR3ZggRtPFKSfu7p4nc0pbRdOTr8E=" ] } ] }
In addition, the Data Provider must allow scopes
we intend to send and get the redirect_uri
address where we want to navigate later during the flow. So we pass the values for the required
scopes
and redirect_uri
as well.
Implementation
Import Required Packages and Start Express Server
const axios = require("axios");
const https = require("https");
const fs = require("fs");
const qs = require("qs");
const express = require("express");
const app = express();
const port = 3005;
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
Pass Required Information From the Data Provider and Read the CRT and KEY Files
const url = "[DATA_PROVIDER_PAR_URL]";
const client_id = "[DATA_PROVIDER_CLIENT_ID]";
const redirect_uri = `http://localhost:${port}/response-par`;
const httpsAgent = new https.Agent({
cert: fs.readFileSync("client.crt"),
key: fs.readFileSync("client.key"),
});
Create First Express Endpoint to Initiate the PAR Flow
app.get("/par", async (_, res) => {
try {
const {
data: { request_uri },
} = await axios({
method: "POST",
url,
httpsAgent,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
data: qs.stringify({
client_id,
redirect_uri,
response_type: "code",
authorization_details: JSON.stringify([
{
type: "fdx_v1.0",
consentRequest: {
durationType: "ONE_TIME",
lookbackPeriod: 60,
resources: [
{
resourceType: "ACCOUNT",
dataClusters: [
"ACCOUNT_DETAILED",
"TRANSACTIONS",
"STATEMENTS",
],
},
{
resourceType: "CUSTOMER",
dataClusters: ["CUSTOMER_CONTACT"],
},
],
},
},
]),
}),
});
const authorizeUrl = `[DATA_PROVIDER_AUTHORIZE_URL]?client_id=${client_id}&request_uri=${request_uri}&scope=ACCOUNT_BASIC UPDATE_CONSENTS READ_CONSENTS`;
res.redirect(authorizeUrl);
} catch (err) {
console.log(error);
res.sendStatus(500);
}
});
Prepare Endpoint to Handle Response From PAR and Get Consent
app.get("/response-par", async (req, res) => {
const { code } = req.query;
const mtls_token_url = "[DATA_PROVIDER_TOKEN_URL]";
const data = qs.stringify({
grant_type: "authorization_code",
code,
client_id,
redirect_uri,
});
try {
const authResponse = await axios({
method: "POST",
url: mtls_token_url,
httpsAgent,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
data,
});
const { access_token, grant_id } = authResponse.data;
const consentsResponse = await axios({
method: "GET",
url: `[DATA_PROVIDER_URL]/consents/${grant_id}`,
httpsAgent,
headers: {
Authorization: `Bearer ${access_token}`,
},
});
res.send(`
<pre>
${JSON.stringify(authResponse.data, null, 2)}
</pre>
<pre>
${JSON.stringify(consentsResponse.data, null, 2)}
</pre>
`);
} catch (err) {
console.log(err);
res.sendStatus(500);
}
});
Run the Application
Launch the application. Once it’s running, a user can visit http://localhost:3005/par
.
-
The application requests a consent. It calls the PAR endpoint and provides a Rich Authorization Request object describing the consent.
-
The user authenticates with the Data Provider.
-
The Data Provider redirects the user to the consent page.
-
The consent page asks for information about the requested consent and user accounts. This info is returned by the Data Holder.
-
Depending on the user’s choice, the consent page notifies the Data Provider if the user accepted or rejected the request. When accepted, the user is redirected to
redirect_uri
provided by the application (http://localhost:3005/response-par
). -
Since the PAR authorization grant with
response_type: code
is used, the response endpoint receives a code for the subsequent request to obtain an access token. -
The token and grant identifier obtained can be used to access FDX consents.
Conclusion
Upon reading this tutorial, you know the prerequisites for implementing FDX with a NodeJS application. When you need more info—our FDX-dedicated how-tos section is just a click away for you to leverage your FDX-compliant app further on.
Like what you see? Register for free to get access to a Cloudentity tenant and start exploring our platform!