Workflow for OP PSD2 Account Information Service API
In this document, we will guide you through each step you need to take in order to access our PSD2 Account Information Service API.
All API calls described here are made using mutually authenticated TLS.
NOTE: Calling PSD2 APIs with Postman is not supported at this time.
See PSD2 AIS API documentation
References
Open Banking Security Profile
FAPI - FAPI Read-Only profile
Open Banking Security Profile
Terminology & abbreviations
- TPP: the Third-Party Provider accessing the API.
- Client App: an application developed by the TPP for accessing the API.
Prerequisites
Before you can start using our PSD2 APIs, ensure that you fulfill the requirements outlined in this section.
Sandbox
PSD2 APIs are available in sandbox free of charge for both licensed and unlicensed developers. Full details for sandbox access.
Production
Following the regulation, using PSD2 APIs is free of charge also in production. However, a Third-Party Provider has to apply for an AISP/PISP license from a financial authority (e.g. FIN-FSA in Finland) and obtain valid QWAC and QSEAL certificates. Full details for production access.
Overview
Succesfully using the PSD2 Account Information Service API requires completing the following steps:
- End user requests to access protected Account Information.
- Client App authenticates with Client Credentials Flow
- Client App POSTs to /authorizations to register intent
- End user authentication and authorization
- Return to Client w/ Authorization Code & ID Token; Client verifies that the returning user began the process & exchanges the code for an Access Token
- TPP accesses the Accounts API using the access token.
Now we'll go over these steps one by one in detail.
1. End user requests access to protected Account Information
This step typically happens in the UI of the TPP application and is purely a matter between the TPP and the end user.
2. TPP application authenticates
To begin interacting with the account information service, the client application authenticates with the Oauth Client Credentials Flow using scope accounts. Note that any given request may only contain one scope.
The authentication request has the following format:
POST /oauth/token HTTPS/1.1
Host: psd2.mtls.sandbox.apis.op.fi
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&scope=accounts&client_id=<APP_CLIENT_ID>&client_secret=<APP_CLIENT_SECRET>
The following cURL call can be used for completing this step. The call assumes that your current directory contains your client certificate (client.crt) and the related private key (key.pem) - both are created by the Registration Helper App.
curl -vk --key key.pem --cert client.crt https://psd2.mtls.sandbox.apis.op.fi/oauth/token -d 'grant_type=client_credentials&scope=accounts&client_id=<client_id>&client_secret=<client_secret>'
If the request is successful, the will have the following content:
{
"token_type": "bearer",
"access_token": "Axqx362CnSmLABgzqcBasG0pxBj9",
"scope": "accounts",
"status": "approved",
"expires_in": "86399",
}
Save the access_token for use in the next step.
3. TPP registers intent to access Account Information
Now the TPP application is in possession of an access token. To advance, the TPP establishes mutually authenticated TLS with the host and uses the access token to make a POST call to the /authorizations endpoint. This registers the TPP's intent to access protected data.
Example call
curl -vk --key key.pem --cert client.crt https://psd2.mtls.sandbox.apis.op.fi/accounts-psd2/v1/authorizations \
-H 'x-api-key: 30VJGNf9QuRaLm1FL8HMgccKHyZaVPR7' \
-H 'Authorization: Bearer ecvAPboih8ff3xPVDFYJ' \
-H 'x-fapi-financial-id: test' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"expires":"2019-03-14T11:24:13.889Z"}'
Refer to the POST method on endpoint /authorizations in the API documentation of our Accounts API. By using the transactionFrom and transactionTo parameters in the request body, you may request to access transactions up to 730 days in the past. Note that the only way to test paging in the sandbox is to request a long enough authorization period.
Sample response
{
"authorizationId": "ae5j-we56n-mz5e7-ze57nze5n7ez",
"created": "",
"status": "Unauthorized",
"expires": "2019-09-14T14:24:14+03:00"
}
The field authorizationId identifies the account request and serves as the intent ID for this session. Immediately after creation, the authorizationId will have its status set as Unauthorized: the authorization is pending end user authentication and authorization.
The following is a list for possible authorization statuses. Each of these statuses will be accompanied by HTTP status code 20X. In all error situations, a generic HTML error page will be returned.
Status | Explanation |
---|---|
Unauthorized | Intent has been succesfully created. The end user must still authenticate and confirm the authorization. This value pertains to the status of the authorization, not the associated HTTP request. |
Authorized | The end user has successfully authorized the application to access customer-owned resources to the extent represented by the intent/authorization. |
Rejected | The end user has rejected the authorization during the authorization flow. |
Revoked | The customer has revoked the TPP's right to access. Originally, authorization was completed successfully, but at a later point the end user has decided to revoke access. |
Expired | The authorization has expired. |
4. Authentication and authorization
Authentication and authorization take place between the end user and the auth server of the account servicer. The role of the TPP is to prepare an appropriate JWT request, fill in the necessary query parameters, and redirect to the authorization address.
4.1 Preparing the JWT Request
The request is a JSON Web token that has been signed with a QSEALC private key (or a key which emulates QSEALC) using RS256 or ES256. The corresponding public key must be made available at a registered JWKS endpoint (not required in sandbox).
Padding characters ("=") should be omitted as per https://tools.ietf.org/html/rfc7515#section-2.
The header and payload of the JWT request have the following structure:
{ "alg": "ES256",
"typ": "JWT",
"kid": "Vi3hb" --> This value varies per key
}
.
{
"aud": "https://mtls.apis.op.fi",
"iss": "1ztgnp8NZzUmlmiAdNoC",
"response_type": "code id_token",
"client_id": "1ztgnp8NZzUmlmiAdNoC",
"redirect_uri": "https://localhost:8181/oauth/access_token",
"scope": "openid accounts",
"state": "305fd555ca38d610d28a50af",
"nonce": "b2859187cc25e029c663fed9",
"max_age": 86400,
"exp": 1613548257,
"iat": 1613547357,
"claims": {
"userinfo": {
"authorizationId": {
"value": "c4ea8d31-e097-48f6-9ba5-643c10f41a4b",
"essential": true
}
},
"id_token": {
"authorizationId": {
"value": "c4ea8d31-e097-48f6-9ba5-643c10f41a4b",
"essential": true
},
"acr": {
"essential": true,
"values": [
"urn:openbanking:psd2:sca"
]
}
}
}
}
.
<signature>
Field | Type | Required | Legend |
---|---|---|---|
typ | string | true | Type of the token. Always gets value JWT. |
alg | string | true | The signing algorithm used. Possible values are RS256 and ES256. |
kid | string | true | ID of the signing key. A key corresponding to this value must be available at a public JWKS endpoint. If you have used the registration app in sandbox, this value is found in the field ssaSigningKid in registration-result.txt (the key is in ssa-signing-key-pem). |
aud | string | true | Intended audience of the JWT, i.e. the host URL of the API. |
iss | string | true | Issuer of the token, i.e. Client ID of the TPP. |
response_type | string | true | Specifies the type of the authorization flow. Allowed values are code and code id_token. |
client_id | string | true | Unique identifier of the client application. |
redirect_uri | string | true | The address to which the end user wil be redirected after authentication and authorization. The access token or error descriptors will be attached to this uri as query parameters. |
scope | string | true | Scope names that identify the extent of the requested authorization, as a list of space-separated names. Per the OIDC specification all requests must contain the scope openid. Only one of the following scopes is allowed for each request: accounts, payments, fundsconfirmations. For AIS, the default required value is "openid accounts". |
state | string | true | A string that will be returned to the TPP. Used for validating responses and correlating requests with responses. Must only contain supported characters, specified in RFC 4648 Section 5, Table 2. |
nonce | string | true | A unique string that is used for protecting against replay attacks. Generated by the Client App. |
max_age | number | true | Maximum allowed age for the authentication, in seconds. |
exp | number | true | Token expiry timestamp |
iat | number | true | Token issued at timestamp |
claims | object | true | Contains information related to the request. The field authorizationId contains the authorizationId obtained while registering intent. |
userinfo | object | true | Contains information related to the request. The field authorizationId contains the authorizationId obtained while registering intent. |
id_token | object | true | The contents of this field are similar to userinfo. |
acr | obj | true | Specifies the requested authorization contexts. Currently, the only allowed value is urn:openbanking:psd2:sca. |
Once the body and header have been constructed, follow these steps: 1. Base64Urlencode the header and body separately. Append the body to the header, separating the two parts with a dot. 2. Create a signature by taking a copy of the newly created string and encoding it with your private key (from QSEALC or emulated) using the algorithm specified in the header. This is the signature. 3. Attach the signature to the encoded header and body with a dot. You'll get something like this:
eyJhbGciOiJIUzI1NiJ9.eyJtc2ciOiJIZWxsbyJ9.N6-M8oOwCq9x820PR6HMtWo-_FL5GeWUSqQ66H2F5kA
Remember to leave out padding characters ("=").
See our example code for signing JWTs. This code assumes that you have private and public jwks readily available. In sandbox, developers may use the Registration Helper App to auto-generate JWKS's.
ES256 or RS256 are allowed as signing algorithms.
import * as fs from 'fs';
import { JWKS, JWK, JWT } from 'jose';
//request is a valid pre-created JWT body
const asymmetricSign = (request: object, privateJwksPath: string) => {
const privateJwks = JSON.parse(fs.readFileSync(privateJwksPath, 'utf8').toString());
const privateJwk = JWKS.asKeyStore(privateJwks, {calculateMissingRSAPrimes: true}).get({alg: 'RS256'});
return JWT.sign(JSON.stringify(request), privateJwk);
};
You can validate the generated JWT with your public jwks as follows:
import * as fs from 'fs';
import { JWT } from 'jose';
const publicJwks = JWKS.asKeyStore(JSON.parse(fs.readFileSync('path/to/publicjwks.json', 'utf8').toString()));
JWT.verify(jwt, publicJwks, {
algorithms: ['ES256', 'RS256', 'PS256'],
clockTolerance: '120s',
maxTokenAge: '60s',
complete: true,
});
Learn more about JWTs on jwt.io. Note that the JWT editor is currently incapable of processing this type of key.
4.2 Redirection
Once the request is complete, the TPP will redirect the customer to OP's auth server. The client app must append the following query parameters to the redirection URI:
- request (the JWT request)
- response_type
- client_id
- scope
The values of the parameters MUST match those in the JWT request. Remember that all query parameters must be URLencoded.
The redirect URI will have the following structure:
https://authorize.psd2-sandbox.op.fi/oauth/authorize
?request=<your_JWT_string>
&response_type=code+id_token
&client_id=******
&scope=openid%20accounts
Direct the customer to this URI in the browser.
4.3 The customer authenticates and authorizes
Authentication and authorization are the business of the customer and the auth server. The customer logs in and selects the authorizations they wish to confirm or reject.
The following test credentials are available in our sandbox:
Username | Password | Key code / mobile key |
---|---|---|
88888881 | 1122 | 1122 |
88888882 | 1122 | 1122 |
4.4 Back to Client Application
The auth server will redirect the end user to the redirect URI specified in the request. There are two possible outcomes:
Success:
<REDIRECT_URI>?state=<state>&code=<authorization_code>
Success with ID token:
<REDIRECT_URI>#state=<state>&code=<authorization_code>&id_token=<id_token>
Error:
<REDIRECT_URI>?state=<state>&error=<error_description>
For example:
https://localhost:8181/oauth/access_token?error=temporarily_unavailable&state=373b23cd-a1b3-449a-810f-cd4b36f7a60c
As the user returns to the TPP's application, the TPP should verify that the returning user is the same as the user who began started the authorization process. This can be achieved by e.g. inspecting the user's session ID.
ID tokens are created and returned on two occasions:
- When using response type code id_token in the authorization request. The id token will be delivered in a query parameter attached to the client app's redirect URI. Note that when an ID token is present, the query string is in fact an URI fragment (begins with "#" instead of "?").
- When requesting a token from a token endpoint and scope includes openid (required with OP PSD2 APIs.). The token will be returned in the response body to the token request.
The ID token contains information about the authorization, and provides an additional layer of security.
The ID token contains the following information:
{
"typ": JWT,
"alg": "RS256",
"kid": "12345"
}
.
{
"iss": "https://authorize.psd2-sandbox.op.fi",
"iat": 1234569795,
"sub": "28c52f98-7541-42a2-9ca0-b86ceac47692",
"acr": "urn:openbanking:psd2:sca",
"authorizationId": "28c52f98-7541-42a2-9ca0-b86ceac47692,
"aud": "s6BhdRkqt3",
"nonce": "n-0S6_WzA2Mj",
"exp": 1311281970,
--ALTERNATIVE 1: AUTHORIZATION CODE FLOW---
"s_hash": "76sa5dd",
"c_hash": "asd097d"
--ALTERNATIVE 2: TOKEN ENDPOINT---
"rt_hash": "wFsWWP49hHo4sXwwVSHX-A",
"at_hash": "bSUTLJiqrooANhJJ2X7pPw"
}
.
{
<<signature>>
}
Field | Type | Legend |
---|---|---|
iss | string/URI | Issuer of the token. |
iat | timestamp | Token issuance time as seconds since 1970-01-01T0:0:0Z. |
sub | string | Subject of the token: the authorizationId related with the ID token. |
acr | string | Authentication Context Class Reference, i.e. field that describes the level of authentication achieved. Currently, only urn:openbanking:psd2:sca is supported. |
authorizationId | string | The authorizationId related with the ID token. |
aud | string/client ID | Intended audience of the ID token. Will contain the Client Id of the Client Application. |
nonce | string | A value originally provided by the TPP in the request, used for protecting against replay attacks. |
exp | timestamp | Time of expiry, i.e. the time after which the token is no longer valid. |
s_hash | string, case-sensitive | Returned with authorization code. State hash value: base64Url encoding of the left-most half of the hash of the octets of the ASCII representation of the state value, where the hash algorithm used is the hash algorithm used in the algHeader Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, hash the code value with SHA-256, then take the left-most 256 bits and base64url encode them. The s_hashvalue is a case sensitive string. |
c_hash | string, case-sensitive | Returned with authorization code. Code hash value: base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the code value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE Header. |
rt_hash | string | Returned from token endpoint. Refresh token hash value: base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the refresh_token value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, hash the refresh_token value with SHA-256, then take the left-most 128 bits and base64url encode them. The rt_hash value is a case sensitive string. |
at_hash | string | Returned from token endpoint. Authorization token hash value: base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the access_token value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, hash the access_token value with SHA-256, then take the left-most 128 bits and base64url encode them. The at_hash value is a case sensitive string. |
The ID token must be validated following the OIDC spec. In addition, the TPP should do the following:
Alternative 1: ID token received in redirection URI
- Validate the authorizationId against that received earlier
- Validate the signature of the ID token
- Validate s_hash (state validation)
- Validate c_hash (code validation).
Alternative 2: ID token in access token response body
- Validate the authorizationId against that received earlier
- Validate the signature of the ID token
- Validate s_hash (state validation)
- Validate c_hash (code validation).
The public key for validating ID token signatures is available at https://authorize.psd2-sandbox.op.fi/oauth/jwks/PSD2_OIDC.
5. Token exchange
At this point the TPP is in possession of an authorization code. Thus must now be exchanged for an access token with a POST call of the following type:
POST /oauth/token HTTPS/1.1
Host: psd2.mtls.sandbox.apis.op.fi
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=<code>&redirect_uri=<redirect_uri>&client_id=<APP_CLIENT_ID>&client_secret=<APP_CLIENT_SECRET>
cURL:
curl -vk --key key.pem --cert client.crt https://psd2.mtls.sandbox.apis.op.fi/oauth/token -d 'grant_type=authorization_code&client_id=VxEjWZCQ98ngGN0imOOd&client_secret=WOf1v30GGeJq7MZL6FXs&code=wpj94iuZRzFvmHHpIFba7b1QiZweXm&redirect_uri=https%3A%2F%2Flocalhost%3A8181'
A successful request will yield a response with the following content:
{
"access_token":"TfOJ0yOywdg8cwthvKUE",
"refresh_token":"TYiOBpLHMWSgXsTg0SJ7TlqVMhSsWnJ6vxL6dUk9",
"id_token": "eyJraWQiOiI2ZzNRRU9SY0hNOVdVZTZlcVowZi1wdjNEUEV1Y2NHVTRUdDFVMHo0cFIwIiwiYWxnIjoiUlMyNTYifQ.eyJydF9oYXNoIj---90ZXN0X2NsaWVudCJ9.OMxF_fyVTS0af_---e9sQXEGMHW_S7bsA",
"scope":"openid accounts",
"token_type":"bearer",
"expires_in":3599,
}
Field | type | Legend |
---|---|---|
access_token | string | This is the access token. Use as a bearer token in the Authorization header to access protected resources. |
refresh_token | string | Can be used to request additional access tokens. Valid for up to 45 days (Less if the authorization granted is shorter). |
id_token | string | A signed JWT containing information about the authorization. See ID tokens for a full description on the contents and recommended validation procedures for ID tokens. |
scope | string | A space-separated list of scope names describing the services to which the token grants authorization. |
token_type | string | Token type. Always has the value "bearer". |
expires_in | number | The period for which the access token is valid, in seconds. |
Refreshing tokens
Refresh tokens may be used for requesting additional access tokens, as well as for renewing refresh tokens. Refresh tokens are valid for a maximum of 45 days.
Regardless of the length of the authorization, the end user must re-authorize the Client Application if the application fails to renew the refresh token within 45 days.
Refresh requests have the following structure:
POST /oauth/token HTTPS/1.1
Host: psd2.mtls.sandbox.apis.op.fi
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=<refresh_token>&client_id=<APP_CLIENT_ID>&client_secret=<APP_CLIENT_SECRET>
The response body to this request is identical to that of a normal token request.
6. API Access
Finally, the TPP application can access protected account information using the bearer token acquired in step 5. See the API Documentation for full details.
GET /accounts-psd2/v1/accounts HTTPS/1.1
Host: psd2.mtls.sandbox.apis.op.fi
Accept: application/json
x-api-key: <APP_API_KEY>
Authorization: Bearer <authorization>
x-fapi-interaction-id: <UUID>
x-customer-user-agent: <user_agent>
x-fapi-customer-ip-address: <ip_address>
Paging
For paging transactions, PSD2 Account Information Service makes use of continuation tokens. Each time the TPP requests a list of transactions (or some part thereof), the service will return a continuation token that unambiguously defines the point where the current page of transactions ended.
Continuation tokens are only returned the number of transactions is sufficient. As such, for testing purposes TPPs should request an authorization period of 730 days by using the transactionFrom and transactionTo parameters.