# SDK login

### Overview

Before using any D1 SDK service, the issuer application shall provide a proof of the end user authentication. The D1 SDK and D1 backend rely on this proof to authorize the issuer application to access D1 services.

For example, the issuer application prompts the end user to use the biometric authentication method that has been set up.

<figure><img src="broken-reference" alt=""><figcaption><p>Example: end user biometric authentication.</p></figcaption></figure>

### Prerequisites

During project onboarding, the issuer generates a key pair and share the public part with the Thales delivery team.

### Login flow

Before calling any SDK operation, the issuer application shall login as detailed in the following flow:

**Prerequisite**

* Issuer certificate is exchanged with Thales
* Consumer is registered in D1 backend
* SDK is properly initialized

<figure><img src="broken-reference" alt=""><figcaption><p>SDK login flow.</p></figcaption></figure>

### Proof of authentication

D1 supports [OpenID](https://openid.net/connect/) access token as proof of authentication, so by default, this is compatible with OpenID Identity Provider (IDP).

The issuer access token format is defined [here](https://docs.payments.thalescloud.io/click-to-pay/integrate-the-d1-sdk/getting-started/configuration/5.-authentication/broken-reference).

{% hint style="warning" %}

* The issuer access token must be created with a short expiration period such as a few minutes and must not be reused for multiple login attempts.
* For iOS devices, device passcode must be set. Otherwise, an error message `Key generation failed` will be thrown.
  {% endhint %}

### Login validity

Once the end user is logged into the D1 services, the end user is granted access to D1 services during the full logged-in session, which by default is set to one hour. However, sensitive D1 services have a shorter login validity period (few minutes) and require the end user to re-authenticate if the end user login is older than this short validity period.

The D1 service call would return a `NOT_LOGGED_IN` error in such cases. The issuer application should request the end user to login again when this error is encountered.

### Implementation

Before proceeding with the login flow, you have to convert the issuer [JWT access token](https://docs.payments.thalescloud.io/click-to-pay/integrate-the-d1-sdk/getting-started/configuration/5.-authentication/broken-reference) to a UTF-8 data type.

The following code snippets demonstrate how to login to D1 SDK on the different platforms:

{% tabs %}
{% tab title="Android Java" %}
{% code title="" lineNumbers="true" %}

```java
public void login(@NonNull D1Task d1Task) {
    byte[] issuerToken = "<<IDP Access Token>>".getBytes(); // TODO: replace with your IDP access token value
    D1Task.Callback<Void> callback = new D1Task.Callback<Void>() {
        @Override
        public void onSuccess(Void ignore) {
            // Proceed with subsequent flows
        }

        @Override
        public void onError(D1Exception exception) {
            // Refer to D1 SDK Integration – Error Management section
        }
    };
    d1Task.login(issuerToken, callback);
}
```

{% endcode %}
{% endtab %}

{% tab title="Android Kotlin" %}
{% code lineNumbers="true" %}

```kotlin
   fun login(d1Task: D1Task) {
    val issuerToken : ByteArray = "<<IDP Access Token>>".toByteArray() // TODO: replace with your IDP access token value
    val callback: D1Task.Callback<Void?> = object : D1Task.Callback<Void?> {

        override fun onSuccess(ignore: Void?) {
            // Proceed with subsequent flows
        }

        override fun onError(exception: D1Exception) {
            // Refer to D1 SDK Integration – Error Management section
        }
    }
    d1Task.login(issuerToken, callback)
}
```

{% endcode %}
{% endtab %}

{% tab title="iOS" %}
{% code lineNumbers="true" %}

```swift
var issueToken : Data = Data() // IDP Access Token
d1Task.login(&issueToken) { error in
    if let error = error {
        // Refer to D1 SDK Integration – Error Management section
    } else {
        // Proceed with subsequent flows
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Multiple issuers and consumers

The updated login API introduces support for multiple `D1Task` instances and enables the authentication with multiple issuer tokens, each representing different consumer IDs (issuer aggregators).

The key features are:

* Single Login with Multiple Tokens\
  You can perform a one-time login using any `D1Task` instance by supplying all relevant issuer tokens. Each token can contain its own consumer IDs, allowing you to authenticate across multiple issuers in a single step.
* Cross-instance Access\
  After logging in, you can seamlessly use any `D1Task` instance to perform operations without requiring re-authentication. This offers unified access for multiple consumers and issuer aggregators within the same application.

{% tabs %}
{% tab title="Android" %}
{% code lineNumbers="true" %}

```java
class D1Task {
  public void login(
            @NonNull final List<byte[]> issuerTokens,
            @NonNull final Callback<Void> callback
    );
}
```

{% endcode %}
{% endtab %}

{% tab title="iOS" %}
{% code overflow="wrap" lineNumbers="true" %}

```swift
class D1Task {
    public func login(_ issuerTokens: inout [Data], completion: @escaping (D1Error?) -> Void)
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Multiple `D1Task` instances

You can create multiple instances of `D1Task` for different issuers and consumers as follows:

{% tabs %}
{% tab title="Android Java" %}
{% code title="" lineNumbers="true" %}

```java
// --- Placeholders you already have in your app / DI ---
Activity activity = myActivity;/* your Activity */;
OEMPayType oemPayType = OEMPayType.GOOGLE_PAY;/* e.g., OEMPayType.GOOGLE_PAY or SAMSUNG_PAY */;
String samsungServiceId = "serviceID";/* your Samsung Pay service ID or "" if not used */;
String visaClientAppId = "clientID"; /* your Visa client app ID or "" if not used */;

// --- D1Task declarations ---
D1Task d1Task1 = null; // Uses ISSUER_ID_1 issuerToken with CONSUMER_ID_1
D1Task d1Task2 = null; // Uses ISSUER_ID_1 issuerToken with CONSUMER_ID_2 (multi-consumer)
D1Task d1Task3 = null; // Uses ISSUER_ID_2 issuerToken with CONSUMER_ID_3 (multi-issuer)

// --- Consumer IDs ---
final String consumerID1 = "CONSUMER_ID_1";
final String consumerID2 = "CONSUMER_ID_2";
final String consumerID3 = "CONSUMER_ID_3";

// --- Build common per-task config params ---
D1Params cardCfg1 = ConfigParams.buildConfigCard(activity, oemPayType, samsungServiceId, visaClientAppId);
D1Params cardCfg2 = ConfigParams.buildConfigCard(activity, oemPayType, samsungServiceId, visaClientAppId);
D1Params cardCfg3 = ConfigParams.buildConfigCard(activity, oemPayType, samsungServiceId, visaClientAppId);

// --- Configure d1Task1 ---
D1Params coreCfg1 = ConfigParams.buildConfigCore(consumerID1);
d1Task1.configure(new D1Task.ConfigCallback<Void>() {
    @Override
    public void onSuccess(Void ignore) {
        // configured successfully for consumerID1
    }
    @Override
    public void onError(@NonNull List<D1Exception> exceptions) {
        for (D1Exception ex : exceptions) {
            // Handle errors as per "D1 SDK Integration – Error Management"
            // e.g., log/route by ex.getCode(), ex.getMessage()
        }
    }
}, coreCfg1, cardCfg1);

// --- Configure d1Task2 ---
D1Params coreCfg2 = ConfigParams.buildConfigCore(consumerID2);
d1Task2.configure(new D1Task.ConfigCallback<Void>() {
    @Override
    public void onSuccess(Void ignore) {
        // configured successfully for consumerID2
    }
    @Override
    public void onError(@NonNull List<D1Exception> exceptions) {
        for (D1Exception ex : exceptions) {
            // Handle errors
        }
    }
}, coreCfg2, cardCfg2);

// --- Configure d1Task3 ---
D1Params coreCfg3 = ConfigParams.buildConfigCore(consumerID3);
d1Task3.configure(new D1Task.ConfigCallback<Void>() {
    @Override
    public void onSuccess(Void ignore) {
        // configured successfully for consumerID3
    }
    @Override
    public void onError(@NonNull List<D1Exception> exceptions) {
        for (D1Exception ex : exceptions) {
            // Handle errors
        }
    }
}, coreCfg3, cardCfg3);
```

{% endcode %}
{% endtab %}

{% tab title="Android Kotlin" %}
{% code lineNumbers="true" %}

```kotlin
// --- Placeholders you already have in your app / DI ---
val activity: Activity = myActivity /* your Activity */
val oemPayType: OEMPayType = OEMPayType.GOOGLE_PAY /* e.g., OEMPayType.GOOGLE_PAY or SAMSUNG_PAY */
val samsungServiceId: String = "serviceID" /* your Samsung Pay service ID or "" if not used */
val visaClientAppId: String = "clientAppID" /* your Visa client app ID or "" if not used */

// --- D1Task declarations ---
lateinit var d1Task1: D1Task // Uses ISSUER_ID_1 issuerToken with CONSUMER_ID_1
lateinit var d1Task2: D1Task // Uses ISSUER_ID_1 issuerToken with CONSUMER_ID_2 (multi-consumer)
lateinit var d1Task3: D1Task // Uses ISSUER_ID_2 issuerToken with CONSUMER_ID_3 (multi-issuer)

// --- Consumer IDs ---
val consumerID1 = "CONSUMER_ID_1"
val consumerID2 = "CONSUMER_ID_2"
val consumerID3 = "CONSUMER_ID_3"

// --- Build common per-task config params ---
val cardCfg1: D1Params = ConfigParams.buildConfigCard(activity, oemPayType, samsungServiceId, visaClientAppId)
val cardCfg2: D1Params = ConfigParams.buildConfigCard(activity, oemPayType, samsungServiceId, visaClientAppId)
val cardCfg3: D1Params = ConfigParams.buildConfigCard(activity, oemPayType, samsungServiceId, visaClientAppId)

// (Optional) tiny helper to avoid repeating the callback boilerplate
fun makeConfigCallback(tag: String) = object : D1Task.ConfigCallback<Void?> {
    override fun onSuccess(data: Void?) {
        // configured successfully for $tag
    }
    override fun onError(exceptions: List<D1Exception>) {
        for (ex in exceptions) {
            // Handle errors as per "D1 SDK Integration – Error Management"
            // e.g., log/route by ex.code, ex.message
        }
    }
}

// --- Configure d1Task1 ---
val coreCfg1: D1Params = ConfigParams.buildConfigCore(consumerID1)
d1Task1.configure(
    makeConfigCallback("consumerID1"),
    coreCfg1,
    cardCfg1
)

// --- Configure d1Task2 ---
val coreCfg2: D1Params = ConfigParams.buildConfigCore(consumerID2)
d1Task2.configure(
    makeConfigCallback("consumerID2"),
    coreCfg2,
    cardCfg2
)

// --- Configure d1Task3 ---
val coreCfg3: D1Params = ConfigParams.buildConfigCore(consumerID3)
d1Task3.configure(
    makeConfigCallback("consumerID3"),
    coreCfg3,
    cardCfg3
)
```

{% endcode %}
{% endtab %}

{% tab title="iOS" %}
{% code overflow="wrap" lineNumbers="true" %}

```swift
var d1Task1: D1Task! // Uses ISSUER_ID_1 issuerToken with CONSUMER_ID_1
var d1Task2: D1Task! // Uses ISSUER_ID_1 issuerToken with CONSUMER_ID_2 for multi consumer
var d1Task3: D1Task! // Uses ISSUER_ID_2 issuerToken with CONSUMER_ID_3 for multi issuer

// Setup d1Task1
var comp = D1Task.Components()
comp.issuerID = "ISSUER_ID_1"
// set up other variables, e.g. URL
d1Task1 = comp.task()

// Setup d1Task2 (same issuerID as d1Task1)
comp = D1Task.Components()
comp.issuerID = "ISSUER_ID_1" // Note: Have used the same issuerID as the one used in D1Task1
// set up other variables, e.g. URL
d1Task2 = comp.task()

// Set up d1Task3 (different issuerID)
comp = D1Task.Components()
comp.issuerID = "ISSUER_ID_2" // different issuerID
// set up other variables, e.g. URL
d1Task3 = comp.task()

// Initialize SDK for different D1Task for different consumerIDs
let consumerID1 = "CONSUMER_ID_1"
let consumerID2 = "CONSUMER_ID_2"
let consumerID3 = "CONSUMER_ID_3"

// D1Task1 configure
let coreConfig1 = ConfigParams.coreConfig(consumerID: consumerID1)
let cardConfig1 = ConfigParams.cardConfig()
d1Task1.configure([coreConfig1, cardConfig1]) { errors in
    if let errors = errors {
        for error in errors {
            // Handle errors as per D1 SDK Integration – Error Management documentation
        }
    }
}

// D1Task2 configure
let coreConfig2 = ConfigParams.coreConfig(consumerID: consumerID2)
let cardConfig2 = ConfigParams.cardConfig()
d1Task2.configure([coreConfig2, cardConfig2]) { errors in
    if let errors = errors {
        for error in errors {
            // Handle errors as per D1 SDK Integration – Error Management documentation
        }
    }
}

// D1Task3 configure
let coreConfig3 = ConfigParams.coreConfig(consumerID: consumerID3)
let cardConfig3 = ConfigParams.cardConfig()
d1Task3.configure([coreConfig3, cardConfig3]) { errors in
    if let errors = errors {
        for error in errors {
            // Handle errors as per D1 SDK Integration – Error Management documentation
        }
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Login with a multi-issuer token

The `sub` field of an issuer token may include multiple consumer IDs. When this field is present, the IDs are concatenated into a single space-delimited string. For example:

AccessToken Sample for multi consumerIDs and multi issuerIDs:

{% code overflow="wrap" lineNumbers="true" %}

```java
{
  "jti": "M9JHKtLdfXu782EH3hMf_",
  "sub": "CONSUMER_ID_1 CONSUMER_ID_2",
  "iat": 1626836247,
  "exp": 1627441047,
  "scope": "digibank:mobilebanking digibank:ecommerce",
  "iss": "ISSUER_ID_1",
  "aud": "https://client-api.d1.thalescloud.io/oidc/ISSUER_ID_1"
}

{
  "jti": "M9JHKtLdfXu782EH3hMf_",
  "sub": "CONSUMER_ID_3",
  "iat": 1626836247,
  "exp": 1627441047,
  "scope": "digibank:mobilebanking digibank:ecommerce",
  "iss": "ISSUER_ID_2",
  "aud": "https://client-api.d1.thalescloud.io/oidc/ISSUER_ID_1"
}
```

{% endcode %}

The first issuer token `ISSUER_ID_1` includes a `sub` field containing both `CONSUMER_ID_1` and `CONSUMER_ID_2`. The second issuer token `ISSUER_ID_2` contains the `sub` for `CONSUMER_ID_1` only. The Login API can be invoked using any previously created `D1Task`, and subsequent operations can proceed without requiring an additional login.

{% tabs %}
{% tab title="Android Java" %}
{% code lineNumbers="true" %}

```java
// issuer tokens (byte[]) you already have
byte[] issuerToken1 = new byte[0];
byte[] issuerToken2 = new byte[0];

// Build the list of tokens (use a mutable List)
List<byte[]> tokens = new ArrayList<>();
tokens.add(issuerToken1);
tokens.add(issuerToken2);

// Do a single login using d1Task1 and all issuer tokens
d1Task1.login(tokens, new D1Task.Callback<Void>() {
    @Override
    public void onSuccess(Void ignored) {
        // Login successful — proceed with subsequent operations

        // If your SDK exposes card ID as a getter, use getCardId(); otherwise, use your known IDs.
        final String cardId1 = "CARD_ID_1";
        final String cardId2 = "CARD_ID_2";
        final String cardId3 = "CARD_ID_3";

        // 1) d1Task1 metadata
        d1Task1.getCardMetadata(cardId1, new D1Task.Callback<CardMetadata>() {
            @Override
            public void onSuccess(CardMetadata data) {
                // Use metadata for task1 (data)
            }

            @Override
            public void onError(@NonNull D1Exception exception) {
                // Handle error for task1 metadata
            }
        });

        // 2) d1Task2 metadata
        d1Task2.getCardMetadata(cardId2, new D1Task.Callback<CardMetadata>() {
            @Override
            public void onSuccess(CardMetadata data) {
                // Use metadata for task2 (data)
            }

            @Override
            public void onError(@NonNull D1Exception exception) {
                // Handle error for task2 metadata
            }
        });

        // 3) d1Task3 metadata
        d1Task3.getCardMetadata(cardId3, new D1Task.Callback<CardMetadata>() {
            @Override
            public void onSuccess(CardMetadata data) {
                // Use metadata for task3 (data)
            }

            @Override
            public void onError(@NonNull D1Exception exception) {
                // Handle error for task3 metadata
            }
        });
    }

    @Override
    public void onError(@NonNull D1Exception exception) {
        // Handle login error
    }
});
```

{% endcode %}
{% endtab %}

{% tab title="Android Kotlin" %}
{% code lineNumbers="true" %}

```kotlin
// Your issuer tokens
val issuerToken1: ByteArray = byteArrayOf()
val issuerToken2: ByteArray = byteArrayOf()

// Build the list of tokens
val tokens = mutableListOf<ByteArray>()
tokens += issuerToken1
tokens += issuerToken2

// Single login using d1Task1 with all issuer tokens
d1Task1.login(tokens, object : D1Task.Callback<Void?> {
    override fun onSuccess(data: Void?) {
        // Login successful — proceed with subsequent operations

        // If the SDK exposes a Kotlin property, use d1TaskX.cardId; otherwise use getCardId() or your known IDs.
        val cardId1 = "CARD_ID_1"
        val cardId2 = "CARD_ID_2"
        val cardId3 = "CARD_ID_3"

        // 1) d1Task1 metadata
        d1Task1.getCardMetadata(cardId1, object : D1Task.Callback<CardMetadata> {
            override fun onSuccess(data: CardMetadata) {
                // Use metadata for task1 (data)
            }
            override fun onError(exception: D1Exception) {
                // Handle error for task1 metadata
            }
        })

        // 2) d1Task2 metadata
        d1Task2.getCardMetadata(cardId2, object : D1Task.Callback<CardMetadata> {
            override fun onSuccess(data: CardMetadata) {
                // Use metadata for task2 (data)
            }
            override fun onError(exception: D1Exception) {
                // Handle error for task2 metadata
            }
        })

        // 3) d1Task3 metadata
        d1Task3.getCardMetadata(cardId3, object : D1Task.Callback<CardMetadata> {
            override fun onSuccess(data: CardMetadata) {
                // Use metadata for task3 (data)
            }
            override fun onError(exception: D1Exception) {
                // Handle error for task3 metadata
            }
        })
    }

    override fun onError(exception: D1Exception) {
        // Handle login error
    }
})
```

{% endcode %}
{% endtab %}

{% tab title="iOS" %}
{% code overflow="wrap" lineNumbers="true" %}

```swift
var tokens = [issuerToken1, issuerToken2] // your actual tokens

// Here, we use d1Task1 to perform a login API call.
// You can similarly use other d1Tasks like d1Task2 or d1Task3 as needed.
// But our goal is that to login ONCE by providing all the issuerTokens 
d1Task1.login(&tokens) { error in
    if let error = error {
        // Handle login error
    } else {
        // Login successful
    }
}

// subsequent operation can proceed
d1Task1.cardMetadata(d1Task1.cardId) { metaData, error in
    if let error = error {
        // Handle error
    } else {
        // Use metadata
    }
}

// subsequent operation can proceed
d1Task2.cardMetadata(d1Task2.cardId) { metaData, error in
    if let error = error {
        // Handle error
    } else {
        // Use metadata
    }
}

// subsequent operation can proceed
d1Task3.cardMetadata(d1Task3.cardId) { metaData, error in
    if let error = error {
        // Handle error
    } else {
        // Use metadata
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Logout for all multi-issuer token

New API has been introduced to allow application to invalidate all tokens. Refer to [logoutAll](https://docs.payments.thalescloud.io/click-to-pay/integrate-the-d1-sdk/getting-started/configuration/5.-authentication/broken-reference).

#### Apple Wallet Extension

[Apple Wallet Extension](https://thales-dis-dbp.stoplight.io/docs/d1-caas/650b8d9621fa0-apple-wallet-extension) is able to add multiple cards with multiple issuerIds and multiple consumerIds.

* Application must use new API issuerParamsList to initialize issuer application.
* Application must use new API to authenticate on UI extension.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.payments.thalescloud.io/click-to-pay/integrate-the-d1-sdk/getting-started/configuration/5.-authentication/sdk-login.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
