Appearance
Enrolling Users
Enrolling users consists in creating SCA Wallets for them. An SCA Wallet is the secure enclave locally present on a device. This digital safe contains a private key necessary for Strong Customer Authentication.
Below the enrollment steps and device limits per solution:
Device | Enrollment steps | Limits |
---|---|---|
Mobile | 1 – Creation of the SCA Wallet on the user's device 2 – Provisioning of the SCA Wallet with the activationCode 3 – Definition of PIN code or biometry by the user to finalize device enrollment | 1 active SCA Wallet per device |
Web | 1 – Generation of the passkeys on the user's device 2 – Passcode creation and encryption on the user's device 3 – Forwarding of the public key and encrypted passcode to Treezor to finalize the device enrollment | 5 active SCA Wallets per device |
If these limits are exceeded, an HTTP error is returned.
Mobile enrollment
This section describes the main steps involved in mobile enrollment, regardless of the mobile solution you chose. You can learn more in the Mobile SDK and Mobile Abstraction Layer articles.
With mobile solutions, only one active SCA Wallet can be created per device. You can take advantage of the Swap feature to create a new wallet on an existing device without needing to delete the old one.
New User enrollment
When using the SDK, an SCA Wallet can be automatically created when creating a new User. In such cases, the following occurs:
- A key pair is generated for this SCA Wallet.
- The SCA Wallet is associated to the new User.
- Treezor sends you a
sca.wallet.create
webhook notifying you of the SCA Wallet creation.
By default, there is a limit of 1 active SCA Wallet per device when using the mobile solution. If this limit is exceeded, an HTTP error is returned.
Existing User enrollment
You may need to create an SCA Wallet for an existing user, when migrating to SCA services for instance. To manually create an SCA Wallet when choosing a mobile solution, you can use the following request.
Parameters
Below are the parameters to create an SCA Wallet manually.
Attribute | Type | Description |
---|---|---|
userId | string | The unique identifier of the user the SCA Wallet is to be attached to. |
scaWalletTag | string | Custom value for the SCA Wallet. Can be used to name the device for which the new SCA Wallet is created. Max length: 256 characters. |
authMethod | array | The chosen methods for the 2-factor authentication. Can be the following strings: ID , OTP EMAIL , OTP SMS , OTHER |
Request example
bash
curl -X POST {baseUrl}/core-connect/sca/scawallets/ \
--header 'Authorization: Bearer {accessToken}' \
--header 'Content-Type: application/json' \
-d '{payload}'
1
2
3
4
2
3
4
Here is an example of {payload}
:
json
{
"userId":"123456",
"scaWalletTag":"Iphone 13 mini", // Can be the device name (the SCA Wallet is created for)
"authMethod":["ID", "OTP SMS"] // Optional
}
1
2
3
4
5
2
3
4
5
Answers with a 200
HTTP Status Code and the following response.
json
{
"id": "3532d3d44dd999e8957e07cc7e860b2a",
"status": "CREATED",
"subStatus": "CREATED_READY",
"passcodeStatus": "NOT_SET",
"locked": false,
"lockReasons": [],
"lockMessage": null,
"settingsProfile": "default",
"activationCode": "**********************",
"creationDate": "2023-07-12T17:15:25+02:00",
"deletionDate": null,
"activationCodeExpiryDate": "2023-07-12T17:35:25+02:00",
"authenticationMethods": [
{
"type": "DEVICE_BIOMETRIC",
"usages": [
"STRONG_CUSTOMER_AUTHENTICATION"
],
"parameters": {
"validityDuration": 60
}
},
{
"type": "HYBRID_PIN",
"usages": [
"WALLET_MANAGEMENT",
"STRONG_CUSTOMER_AUTHENTICATION"
],
"parameters": {
"maxAttempts": 3,
"validityDuration": 60
}
},
{
"type": "NONE",
"usages": [
"STRONG_CUSTOMER_AUTHENTICATION"
],
"parameters": []
},
{
"type": "CLOUD_PIN",
"usages": [
"WALLET_MANAGEMENT",
"STRONG_CUSTOMER_AUTHENTICATION"
],
"parameters": {
"maxAttempts": 3,
"validityDuration": 60
}
}
],
"invalidActivationAttempts": null,
"userId": "3400118",
"scaWalletTag": "Iphone 13 mini",
"clientId": "0998765"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
Note that the activationCode
attribute is only populated in the response, it cannot be retrieved later.
Treezor also sends you a sca.wallet.create
webhook which contains the activationCode
to use before the activationCodeExpiryDate
to provision the SCA Wallet.
Provision the SCA Wallet
You must provision the SCA Wallet when using the SDK on the end user's device before the activationCodeExpiryDate
using either:
- The
connect(activationCode)
method from the abstraction layer - The
WalletProvisioning#launch(activationCode)
SDK method
The activationCode
is provided in the sca.wallet.create
webhook (or in the response when created manually). You may push silently the code to the end user's device to enable a seamless provisioning.
Once a given SCA Wallet has been provisioned into one app installation, it cannot be provisioned anymore and the activationCode
is consumed.
Note – SCA Wallets must be provisioned before the expiration date
The activationCodeExpiryDate
is available in the sca.wallet.create
webhook. If the provisioning fails due to an expired activationCode
, you must delete the SCA Wallet and create a new one.
Set PIN or Biometry
The user is prompted to authenticate differently depending on the method:
- Pin – Using a 6 to 8 digits PIN code.
- Biometric – Using the local biometric authentication system of the device.
- None – No input required, the operation is transparent.
You may use indistinctly Pin or Biometric, depending on the capabilities of the device.
Regardless of the method, basic checks are carried out to ensure the mobile device is legitimate and has not been altered or cloned.
Swap SCA Wallets
Treezor offers a way for end users to enroll a new device for SCA while automatically deleting the previously enrolled device. It makes for a smoother experience than deleting the SCA Wallet first then creating a new one.
Swapping devices is a sensitive action, so users must be strongly authenticated before proceeding. How it is done depends on whether the user owns the previous device.
Owns previous device | Swapping scenario |
---|---|
The user swaps with the SCA Proof from the previous device. | |
You secure the swap with 2-factor authentication (e.g., OTP, ID check). |
When swapping devices:
- The previous SCA Wallet is deleted (i.e., status set to
DELETED
). - The new SCA Wallet is created, and is activated and/or usable on the new device.
- Treezor sends a
sca.wallet.swap
webhook.
Parameters
Below the key parameters for the swap endpoint. The necessary parameters may differ depending on the use case.
Attribute | Type | Description |
---|---|---|
removeScaWalletId | string | The unique identifier of the SCA Wallet that is to be deleted. |
swapReason | string | The reason for swapping SCA Wallet. Can be: LOST , STOLEN , or OTHER . |
authMethod | array | The chosen methods for the 2-factor authentication. Required if no sca is provided. Must be two of the following (strings): OTP SMS , OTP EMAIL , ID , OTHER . |
sca | string | The valid proof that authenticated the swap. Required if no authMethod is provided. |
scaWalletTag | string | Custom value for the SCA Wallet. Can be used to name the device for which the new SCA Wallet is created. Max length: 256 characters. |
Swap with SCA proof
In this case, the user still owns the previous device and can provide an SCA proof with it;
bash
curl -X POST {baseUrl}/core-connect/sca/scawallets/swap \
--header 'Authorization: Bearer {accessToken}' \
--header 'Content-Type: application/json' \
-d '{payload}'
1
2
3
4
2
3
4
Here is an example of {payload}
(with SCA proof):
json
{
"removeScaWalletId":"5710a5536cf04712b922f04exx9da5e2",
"swapReason": "OTHER",
"sca":"{scaProof}",
"scaWalletTag":"Pixel 6"
}
1
2
3
4
5
6
2
3
4
5
6
Returns the newly created SCA Wallet object when successful.
Treezor also sends a sca.wallet.swap
webhook, with the activationCode
. You must provision before its activationCodeExpiryDate
.
Swap with 2-factor authentication
bash
curl -X POST {baseUrl}/core-connect/sca/scawallets/swap \
--header 'Authorization: Bearer {accessToken}' \
--header 'Content-Type: application/json' \
-d '{payload}'
1
2
3
4
2
3
4
Here is an example of {payload}
(without SCA proof):
json
{
"swapReason":"LOST",
"removeScaWalletId":"5710a5536cf04712b922f04exx9da5e2",
"authMethod":["OTP SMS", "ID"],
"scaWalletTag":"iPhone 13"
}
1
2
3
4
5
6
2
3
4
5
6
Returns the SCA Wallet object when successful.
Treezor also sends a sca.wallet.swap
webhook, with the activationCode
. You must provision before its activationCodeExpiryDate
.
Web Native enrollment
This section describes the main steps involved in enrolling users with the Web Native solution.
By default, there is a limit of 5 active SCA Wallets per device when using the Web Native solution. If this limit is exceeded, an HTTP error is returned. Please note you have to delete an SCA Wallet to enroll a new additional device.
New User enrollment
The device enrollment process pairs the User with their WebAuthn secured passkeys and passcode. This must happen when first creating the User in the Treezor API as the User can't use the Treezor API without an enrolled device.
The following steps are required:
- Generation of the passkeys using WebAuthn on the User's device
- Creation of a passcode and its encryption on the User's device
- Forwarding of the public key and encrypted passcode to Treezor to finalize the device enrollment
The following snippet triggers the passkeys creation process using Web Authentication API on the User's device.
js
const publicKeyCredentialCreationOptions = {
// Mandatory, must be set to "device-enrollment"
challenge: convertToArrayBuffer('device-enrollment'),
user: {
// Mandatory, the User's login (this name will be displayed on WebAuthn key selection screen during the authentication process)
name: 'foobar',
// Optional, the User's full name (Firstname LASTNAME)
displayName: '',
// Mandatory, a random ID for each User's device, this is never displayed to the user and doesn't need to be stored anywhere
id: convertToArrayBuffer(randomId),
},
pubKeyCredParams: [{ alg: -7, type: "public-key" }], // Mandatory, as specified
authenticatorSelection: {
// Optional, can be set to "platform", "cross-platform" or omited to allow all authenticators types
authenticatorAttachment: '',
},
// Mandatory, timeout to complete the enrollment in milliseconds. We recommend keeping a fairly high value, as user might be unfamiliar with the process it could take them some time
600000,
// Mandatory, this value must be set to "direct"
"direct",
{
name: "Your website name",
// FQDN, Must be the same at the current domain where your trigger WebAuthn
id: 'www.example.com',
},
};
// trigger Web Authn to register the current device
const credential = await navigator.credentials.create({
publicKey: publicKeyCredentialCreationOptions,
});
const response = credential.response;
const rawId = arrayBufferToBase64Url(credential.rawId).toString();
const clientDataJSON = arrayBufferToBase64Url(
credential.response.clientDataJSON
).toString();
const attestationObject = arrayBufferToBase64Url(
(credential.response).attestationObject
).toString();
// this base 64 encoded string will be required at a later stage
let encodedResponse = btoa(
JSON.stringify({
response: {
attestationObject,
clientDataJSON,
transports: ["string"] // ajout de cette propriété
},
id: credential.id,
rawId,
type: "public-key",
authenticatorAttachment:"platform" // platform or cross-platform
})
);
// helper function
function convertToArrayBuffer(input) {
return Uint8Array.from(input, (c) => c.charCodeAt(0));
}
// helper function
function arrayBufferToBase64Url(arrayBuffer) {
const bytes = new Uint8Array(arrayBuffer);
let str = "";
for (const charCode of bytes) {
str += String.fromCharCode(charCode);
}
const base64String = btoa(str);
return base64String.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
Note – Platform vs. Roaming authenticator (authenticatorAttachment
)
- Platform – Attached with client device-specific transport (
platform
attachment). Usually not removable from the client device. - Roaming – Attached with cross-platform transports (
cross-platform
attachment). Removable and can “roam” between client devices.
If the keys are created successfully, you can now ask the User to define a passcode and encrypt it.
js
const encryptedPassCode = encryptPasscode(passCode)
// see Passcode encryption section below for how to encrypt the passcode
1
2
2
At this point, you may use the User creation request (/v1/users
) with:
- A
legal
scope, - The
passcode
andwebauthn
attributes to the usual payload.
bash
curl -X POST {baseUrl}/v1/users \
--header 'Authorization: Bearer {accessToken}' \
--header 'Content-Type: application/json' \
-d '{payload}'
1
2
3
4
2
3
4
Here is an example of {payload}
:
json
{
"userTypeId":1,
"specifiedUSPerson":0,
"firstname":"Alex",
"lastname":"Oak",
// [...] other User attributes hidden for brevity
"passcode": "2kdqUTkb3MF[...]My+VIBZB6Xm/tECw+RE9L02/K3x97cvwFXKDTIEZzzwAv1OYbS0w==",
"webauthn":"eyJyZXNwbrRG[...]9CejhlWWNxem9mUSIsInR5cGUiOiJwdWJsaWMta2V5In0="
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
In this case, no webhook is sent upon creating the SCA Wallet.
Existing User enrollment
You may need to create an SCA Wallet for an existing user, either for migrating purposes or for additional devices.
By default, 5 devices may be enrolled with Web Native. If you've reached the limit, you first need to delete an SCA Wallet to enroll a new additional device.
When a new device must be enrolled for an existing User, two options are available to you:
- Using an already enrolled device to ensure that the request is legitimate Preferred
- Validating the User's identity using questions that only they can answer (i.e., birthdate, email OTP, etc.)
Both methods aim at ensuring that the User is who they claim to be before allowing the new device to be enrolled and generate SCA signatures.
Parameters
Below are the parameters to create an SCA Wallet manually.
Attribute | Type | Description |
---|---|---|
userId | string | The unique identifier of the user the SCA Wallet is to be attached to. |
scaWalletTag | string | Custom value for the SCA Wallet. Can be used to name the device for which the new SCA Wallet is created. Max length: 256 characters. |
authMethod | array | The chosen method for the 2-factor authentication when signing the operation with the Web Native solution. Must be two of the following (strings): OTP SMS , OTP EMAIL , ID , OTHER . |
sca | string | For the Web Native solution, when creating the SCA Wallet as an end user (required if no authMethod is provided). |
webauthn | string | For the Web Native solution. The PublicKeyCredential after the WebAuthn Register, encoded in base64. |
passcode | string | Required if the webauthn field is provided and if there is no existing passcode for the user yet. Must be base64-encoded and encrypted. |
Using an already enrolled device
If the User already has a device enrolled and accessible, you may use the cross-device method, requesting that the User approves the new device from their already enrolled device.
The passkeys generation process should be carried out on the non-enrolled device (without carrying out last User creation step).
Once the encodedResponse
from the non-enrolled device available, you can finalize the new device enrollment using the following request.
bash
curl -X POST {baseUrl}/core-connect/sca/scawallets \
--header 'Authorization: Bearer {accessToken}' \
--header 'Content-Type: application/json' \
-d '{payload}'
1
2
3
4
2
3
4
Here is an example of {payload}
.
json
{
"userId":"{userId}",
"webauthn":"{encryptedPublicKey}",
"sca":"{scaProofObtainedAtThePreviousStep}"
}
1
2
3
4
5
2
3
4
5
Using questions
This method can typically be used when the User:
- Has lost or cannot access their already enrolled device
- Was created in the Treezor API before the enforcement of SCA regulations
Security – Verify the User's identity
In this situation, you are sole responsible for verifying the User's identity.
In addition to authenticating the User using your usual authentication mechanism, you should ensure the User is able to provide personal information. Use as much relevant information as necessary, such as:
- Their birthdate, current password, email address, mobile phone number
- A one-time-password that you sent to their email address
- A one-time-password that you sent to their mobile phone
If enough conditions are met, you may contact Treezor endpoint and enroll the new device with the following request.
bash
curl -X POST {baseUrl}/core-connect/sca/scawallets/ \
--header 'Authorization: Bearer {accessToken}' \
--header 'Content-Type: application/json' \
-d '{payload}'
1
2
3
4
2
3
4
Treezor expects the following {payload}
:
json
{
"userId":"",
"authMethod":["OTP SMS", "OTP EMAIL", "ID", "OTHER"], // List methods used to ensure the user's identity
"passcode":"{theUsersCurrentPasscodeInEncryptedForm}",
"webauthn":"{newDevicesPublicKey}"
}
1
2
3
4
5
6
2
3
4
5
6
Answers with a 200
HTTP Status Code and the following response.
json
{
"id": "3532d3d44dd999e8957e07cc7e860b2a",
"status": "CREATED",
"subStatus": "CREATED_READY",
"passcodeStatus": "NOT_SET",
"locked": false,
"lockReasons": [],
"lockMessage": null,
"settingsProfile": "default",
"mobileWallet": null,
"activationCode": "**********************",
"creationDate": "2023-07-12T17:15:25+02:00",
"activationDate":null,
"deletionDate": null,
"activationCodeExpiryDate": "2023-07-12T17:35:25+02:00",
"authenticationMethods": [
{
"userHandle": "dGVzdEB3ZWJhdXRobg",
"publicKeyCredentialId": "-7TzaTMjA3dk-1fLf3fEch_Rwqp3hxB0-yaxQXoqEeo",
"aaguid": "adce0002-35bc-c60a-648b-0b25f1f05503",
"uvInitialized": true,
"attestationType": "self",
"backupEligible": false,
"counter": 0,
"otherUI": null,
"type": "public-key",
"transports": [],
"backupStatus": false,
"credentialPublicKey": "pQECAyYgA[...]qWcprI5yqvTz5P7-frvEHIzI", // Truncated for readability
"trustPath": {
"type": "Webauthn\\TrustPath\\EmptyTrustPath"
}
}
],
"invalidActivationAttempts": null,
"userId": "3400118",
"scaWalletTag": "Iphone 12 mini",
"clientId": "0998765"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
In this case, no webhook is sent upon creating the SCA Wallet.
Passcode encryption
Anytime Treezor requires the User's passcode to be provided, you must use Treezor public key to encrypt the passcode.
Security – Passcode privacy
The User's passcode must only be known to the User. It must never be stored on your infrastructure in any form.
The following code snippet includes all the necessary code for client-side encryption of the passcode.
js
(async () => {
const str2ab = (str) => {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
const pemEncodedKey = `-----BEGIN PUBLIC KEY-----
<your_public_key>
-----END PUBLIC KEY-----`;
const importRsaKey = async (pem) => {
// fetch the part of the PEM string between header and footer
const pemHeader = "-----BEGIN PUBLIC KEY-----";
const pemFooter = "-----END PUBLIC KEY-----";
const pemContents = pem.substring(
pemHeader.length,
pem.length - pemFooter.length - 1,
);
// base64 decode the string to get the binary data
const binaryDerString = window.atob(pemContents);
// convert from a binary string to an ArrayBuffer
const binaryDer = str2ab(binaryDerString);
return await window.crypto.subtle.importKey(
"spki",
binaryDer,
{
name: "RSA-OAEP",
hash: "SHA-256",
},
true,
["encrypt"],
);
}
let key = await importRsaKey(pemEncodedKey)
const encryptMessage = async (publicKey) => {
let message = 'nicolas'
let enc = new TextEncoder();
let encoded = enc.encode(message)
return window.crypto.subtle.encrypt(
{
name: "RSA-OAEP",
},
publicKey,
encoded,
);
}
let result = await encryptMessage(key)
_arrayBufferToBase64 = ( buffer ) => {
var binary = '';
var bytes = new Uint8Array( buffer );
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode( bytes[ i ] );
}
return window.btoa( binary );
}
let enc = new TextDecoder();
// enc.encode(enc.decode(result))
console.log(_arrayBufferToBase64(result))
})()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71