Authentication (D-SCA)
Authentication Provider setup and guidelines
Every Integrator is required to provide an Authentication Provider that will be used to verify the authenticity of the
payment request. This is done by
providing an encrypted data object in any enrolment/payment request that contains data generated by the Authentication
Provider.
Authentication Provider requirements
To facilitate an authenticated payment, the Authentication Provider must provide a signed JWS token
verified by EPP.
This JWS needs to contain a PermissionGrant
object that is encoded in the JWS token. It must have
the structure as seen in the Components overview.
You must use a robust signing algorithm. We accept ES256
or PS256
as the signing algorithm.
The Digest
field in the PermissionGrant
object is a Base64
encoded SHA-256 hash of the following data points.
For enrolment:
nonce: Must be the same as the corresponding nonce in the PermissionGrant object.
accountNumber: The Account Number of the enrolment session.
tokenRequestorName: The Token Requestor Name
The Token Requestor Name is part of the information exchange as seen in
our checklist.
For payment:
nonce: Must be the same as the corresponding nonce in the PermissionGrant object.
merchantReference: Set by Wallet.
creditorName: Merchant display name. Set by Wallet.
amount: Set by Wallet.
currency: Set by Wallet.
Authentication field guidelines.
Some values from (./getting-started.md#checklist-for-information-exchange) must be used to create the DSCA digest
and
encrypted data object.
Value |
Usage |
Token Requestor Name |
Used in enrolment for the digest.TokenRequestorName value |
Encryption Issuer |
The id of the Token Requestor assigned to you. To be used in the EnrolmentCardholderAuthenticationData.iss field and in the PaymentCardholderAuthenticationData.iss field. |
Permission Grant Issuer |
The Authentication Provider Id, this is to be placed in the PermissionGrant.Iss field |
Wallet requirements
Any request must contain encryptedCardholderAuthenticationData
which matches the
verifiedCardholderAuthenticationSignedData
object once decrypted.
The verifiedCardholderAuthenticationSignedData
object must be encrypted using the provided public certificate from EPP
as received in point 7 in setting up your EPP integration.
The verifiedCardholderAuthenticationSignedData
object can be reviewed in
our Components overview.
The ISS
field is received from EPP and acts as a correlation to the Authentication provider that was configured to
your Profile.
A corresponding Token Requestor Name
that represent the human-readable name of the token requestor will also be
provided.
This needs to be encoded in the Authentication Provider's PermissionGrant
object.
Illustrated EPP-Wallet-Authentication Provider interoperability
The Permission Grant with corresponding Digest validation is based on
the Open Banking Europe
and Berlin Group NextGen PSD2 guidelines.
This ensures the integrity of the user's authentication and the enrolment/payment.
First a PermissionGrant
object is created. Its structure can be reviewed in
the Components overview.
This object is then encoded in a signed JWS token signed using the private key of the Authentication Provider.
The signature is then validated in EPP.
This leads to the following sequence.
Note that in some scenarios, the Authentication Provider and the Integrator might
be the same organizational entity depending on your operating model.
sequenceDiagram
participant Integrator
participant AuthenticationProvider
participant ePaymentPlatform
Integrator ->> AuthenticationProvider: Request JWS token. Providing Digest input.
note right of AuthenticationProvider: Note that the required Digest input is different <br/>in enrolment and payment requests.
AuthenticationProvider ->> AuthenticationProvider: Create Digest cryptographic hash.
AuthenticationProvider ->> AuthenticationProvider: Create PermissionGrant object.
AuthenticationProvider ->> AuthenticationProvider: Create and Sign JWS token. Using AuthenticationProvider private key.
AuthenticationProvider ->> Integrator: Return JWS token.
Integrator ->> Integrator: Encrypt required data resulting in corresponding encryptedCardholderAuthenticationData.
note left of Integrator: Note that the required <br/>encryptedCardholderAuthenticationData <br/> input is different in enrolment<br/> and payment requests. <br/> Encrypt using EPP public key.
Integrator ->> ePaymentPlatform: enrolment/Payment Request.
ePaymentPlatform ->> ePaymentPlatform: Verify request
note right of ePaymentPlatform: Verify IAT timestamp, ISS Matching, <br/> AuthenticationProvider signature and Digest match.
Enrolment example
erDiagram
PermissionGrant ||--|| Digest: "Base64-encoded SHA-256 hash"
PermissionGrant {
string type
int iat
string iss
string nonce
string sub
string permissionId
string Digest
}
Digest {
string nonce
string acountNumber
string tokenRequestorName
}
This is then encoded in a JWS token signed by the Authentication Provider's private key.
The JWS token is then sent to EPP for validation as part of the Integrator's Payment/enrolment request.
Enrolment used as example, same fundamental structure applies for payment.
erDiagram
EnrolmentCardholderAuthenticationData ||--|| verifiedCardholderAuthenticationSignedData: "JWS token signed by Authentication Provider"
EnrolmentCardholderAuthenticationData {
object enrolmentData
string iss
integer iat
string verifiedCardholderAuthenticationSignedData
}
verifiedCardholderAuthenticationSignedData {
object PermissionGrant
}
This data object must then be encrypted using the public certificate provided by EPP.
This is then made a part of every enrolment/payment request towards EPP in the encryptedCardholderAuthenticationData
.
The combined data structure for a request can also be considered as seen below.
Enrolment used as an example, the same fundamental structure applies for payment.
The green color indicates Integrator, while the blue color indicates Authentication provider.
%%{init: {
"theme": "default",
"themeCSS": [
".er.relationshipLabel { fill: black; }",
".er.relationshipLabelBox { fill: white; }",
".er.entityBox { fill: lightgray; }",
"[id^=entity-EnrolmentRequest] .er.entityBox { fill: lightgreen;} ",
"[id^=entity-encryptedEnrolmentCardholderAuthenticationData] .er.entityBox { fill: lightgreen;} ",
"[id^=entity-EnrolmentCardholderAuthenticationData] .er.entityBox { fill: lightgreen;} ",
"[id^=entity-verifiedCardholderAuthenticationSignedData] .er.entityBox { fill: powderblue;} ",
"[id^=entity-PermissionGrant] .er.entityBox { fill: powderblue;} ",
"[id^=entity-Digest] .er.entityBox { fill: powderblue;} "
]
}}%%
erDiagram
EnrolmentRequest ||--|| encryptedCardholderAuthenticationData: "Encrypts data using EPP public key"
encryptedCardholderAuthenticationData {
object EnrolmentCardholderAuthenticationData
}
encryptedCardholderAuthenticationData ||--|| EnrolmentCardholderAuthenticationData: "Encrypted data"
EnrolmentCardholderAuthenticationData ||--|| verifiedCardholderAuthenticationSignedData: "JWS token signed by Authentication Provider"
EnrolmentCardholderAuthenticationData {
object EnrolmentData
string iss "ISS is received from EPP, and identifies the Wallet"
integer iat "An Epoch timestamp that is validated as being less than 15 minutes old"
string verifiedCardholderAuthenticationSignedData
}
verifiedCardholderAuthenticationSignedData {
object PermissionGrant
}
verifiedCardholderAuthenticationSignedData ||--|| PermissionGrant: "Decodes to"
PermissionGrant ||--|| Digest: "Base64-encoded SHA-256 hash"
PermissionGrant {
string type "for enrollment: 'approveAccount.v1'. For payment: 'payment.v1'"
string iss "ISS is received from EPP, and identifies the Authentication Provider"
integer iat "An Epoch timestamp that is validated as being less than 15 minutes old"
string nonce "The nonce for the permission statement"
string sub "The primary identifier of the subject that granted the permission."
string permissionId "Unique id of the permission request"
string Digest "The input in the Digest need to match corresponding values in the EnrolmentRequest"
}
Digest {
string nonce "The nonce for the permission statement"
string acountNumber "The Account Number of the enrolment session"
string tokenRequestorName "The Token Requestor Name"
}
Payment example
Payment PermissionGrant.
erDiagram
PermissionGrant ||--|| Digest: "Base64-encoded SHA-256 hash"
PermissionGrant {
string type
int iat
string iss
string nonce
string sub
string permissionId
string Digest
}
Digest {
string nonce
string merchantReference "The merchant reference from the root of the payment request"
List payments "Relevant payments"
}
Digest ||--|| Payments: "Contains"
Payments {
string merchantReference "The merchant reference from the root of the payment request"
string displayName "The merchant display name from the root of the payment request"
string amount "The amount from the root of the payment request. IMPORTANT: The amount must be given with fractional digits The decimal separator is a dot!"
string currency "The currency from the root of the payment request"
}
IMPORTANT NOTE: The Amount must be given with fractional digits.
The decimal separator is a dot.
This is different from the Minor Units format in the root of the Payment Request.
EncryptedPaymentCardholderAuthenticationData should result in the following PaymentCardholderAuthenticationData object.
erDiagram
PaymentCardholderAuthenticationData ||--|| verifiedCardholderAuthenticationSignedData: "JWS token signed by Authentication Provider"
PaymentCardholderAuthenticationData {
object paymentToken
string iss
integer iat
string verifiedCardholderAuthenticationSignedData
}
verifiedCardholderAuthenticationSignedData {
object PermissionGrant
}
This data object must then be encrypted using the public certificate provided by EPP.
This is then made a part of every enrolment/payment request towards EPP in the encryptedCardholderAuthenticationData
.
The combined data structure for a request can also be considered as seen below.
The green color indicates Integrator, while the blue color indicates Authentication provider.
%%{init: {
"theme": "default",
"themeCSS": [
".er.relationshipLabel { fill: black; }",
".er.relationshipLabelBox { fill: white; }",
".er.entityBox { fill: lightgray; }",
"[id^=entity-PaymentRequest] .er.entityBox { fill: lightgreen;} ",
"[id^=entity-encryptedEnrolmentCardholderAuthenticationData] .er.entityBox { fill: lightgreen;} ",
"[id^=entity-EnrolmentCardholderAuthenticationData] .er.entityBox { fill: lightgreen;} ",
"[id^=entity-verifiedCardholderAuthenticationSignedData] .er.entityBox { fill: powderblue;} ",
"[id^=entity-PermissionGrant] .er.entityBox { fill: powderblue;} ",
"[id^=entity-Digest] .er.entityBox { fill: powderblue;} "
]
}}%%
erDiagram
PaymentRequest ||--|| encryptedCardholderAuthenticationData: "Encrypts data using EPP public key"
encryptedCardholderAuthenticationData {
object EnrolmentCardholderAuthenticationData
}
encryptedCardholderAuthenticationData ||--|| PaymentCardholderAuthenticationData: "Encrypted data"
PaymentCardholderAuthenticationData ||--|| verifiedCardholderAuthenticationSignedData: "JWS token signed by Authentication Provider"
PaymentCardholderAuthenticationData {
object paymentToken
string iss "ISS is received from EPP, and identifies the Wallet"
integer iat "An Epoch timestamp that is validated as being less than 15 minutes old"
string verifiedCardholderAuthenticationSignedData
}
verifiedCardholderAuthenticationSignedData {
object PermissionGrant
}
verifiedCardholderAuthenticationSignedData ||--|| PermissionGrant: "Decodes to"
PermissionGrant ||--|| Digest: "Base64-encoded SHA-256 hash"
PermissionGrant {
string type "for enrollment: 'approveAccount.v1'. For payment: 'payment.v1'"
string iss "ISS is received from EPP, and identifies the Authentication Provider"
integer iat "An Epoch timestamp that is validated as being less than 15 minutes old"
string nonce "The nonce for the permission statement"
string sub "The primary identifier of the subject that granted the permission."
string permissionId "Unique id of the permission request"
string Digest "The input in the Digest need to match corresponding values in the EnrolmentRequest"
}
Digest {
string nonce
string merchantReference "The merchant reference from the root of the payment request"
List payments "Relevant payments"
}
Digest ||--|| Payments: "Contains"
Payments {
string merchantReference "The merchant reference from the root of the payment request"
string displayName "The merchant display name from the root of the payment request"
string amount "The amount from the root of the payment request. IMPORTANT: The amount must be given with fractional digits The decimal separator is a dot!"
string currency "The currency from the root of the payment request"
}
Digest validation examples
While the exact implementation may vary per Integrator/Coding language, the resulting digest logic must match the
following result
Bash example Payment
echo -n '{"nonce":"550e8400-e29b-41d4-a716-446655440000","id":"merchantReference","payments":[{"paymentId":"merchantReference","amount":"100","currency":"NOK","creditorName":"merchantDisplayName"}]}' \
| sha256sum - \
| awk '{print $1}' \
| xxd -r -p \
| base64 \
| tr -d '=' \
| tr '/+' '_-'
Script explanation: A SHA-256 hash is created from the JSON object. The resulting hash is then converted to binary and
encoded as a Base64 string. First tr
is used to remove padding. Then tr
is used to make the Base64 URL safe.
The script above should produce the following output:
QomjM9YUvFcj0bd0Xjr39uMTaKzb1D54H_YAbHicy4Q
You may use this to verify your own implementation.
Bash example Enrolment
echo -n '{"nonce":"a05b53be-718e-4df2-80ac-83696b711111”,”accountNumber”:”1000000001","merchantName”:”test-enrolment-name"}' \
| sha256sum - \
| awk '{print $1}' \
| xxd -r -p \
| base64 \
| tr -d '=' \
| tr '/+' '_-'
The script above should produce the following output:
VSYBjYtrHT6pPxuIU68MDxC5T77jG_fNbWxBbeEHqWk
You may use this to verify your own implementation.
Java example for Payment
record PaymentPermissionStatement(
String nonce,
String id,
List<PaymentPermissionStatement.Payment> payments
) {
record Payment(
String paymentId,
String amount,
String currency,
String creditorName
) {
}
String digest() throws NoSuchAlgorithmException, JsonProcessingException {
return Base64.getUrlEncoder()
.withoutPadding()
.encodeToString(MessageDigest.getInstance("SHA-256").digest(new ObjectMapper().writeValueAsBytes(this)));
}
}
Follow by
new PaymentPermissionStatement(
"550e8400-e29b-41d4-a716-446655440000",
"merchantReference",
List.of(new Payment(
"merchantReference",
"100.50",
"NOK",
"merchantDisplayName"
))).
digest();