Skip to content

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.issfield.
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();