How to create an Open Banking client app in Node.js for FDX
Learn how to create a Data Recipient application for Financial Data Exchange. Read about the authentication flow and see an example implementation in Node.js.
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-----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=-----END CERTIFICATE----- -
Private Key (
client.key)-----BEGIN PRIVATE KEY-----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-----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_uriprovided by the application (http://localhost:3005/response-par). -
Since the PAR authorization grant with
response_type: codeis 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.