# App2App

BankID with biometrics supports a direct _app-to-app_ based authentication flow, where the user directly jumps in and out of the BankID App, without the merchant app needing to open an in-app browser.

App2App is supported on both iOS and Android, but the platforms have different
default recommendations:

> **Info**
>
> On **iOS**, App2App is the recommended way to optimize the experience, since iOS users always
> have to enter the BankID app to authenticate.
>
> On **Android**, App2App is usually _not_ needed: Android devices can use their key directly in a
> [Custom Tab](https://docs.stoe.no/bankid-with-biometrics/advanced-topics/native-applications.md#android) launched inside your app, without involving a second
> app. Prefer App2App on Android only when a native app-switch is specifically required.
>
> This page documents both platforms. Where the steps differ, the iOS and Android variants are
> shown side by side.
>
> **If you are an ACS implementing 3DS, you are likely required to implement the App2App flow regardless of the platform.**

## Prerequisites

For now we require merchants to supply both:

1. The user's NNIN
2. Information about the device the merchant's app is installed on.

That information is required so that we can be reasonably sure the flow will start correctly.

This means you cannot currently use this flow for initial user-authentication, and you are required to implement another flow, like [authorization code grant](https://docs.stoe.no/bankid-with-biometrics/advanced-topics/native-applications.md) (optionally with an `app_callback_uri` on iOS), for the cases when BankID with biometrics refuses to initiate due to device mismatch. If App2App starts successfully but the app-switch fails, use the [manual recovery in step 10](https://docs.stoe.no/bankid-with-biometrics/advanced-topics/app2app-app-native.md#10-poll-for-the-result).

## Steps

\[TOC]

## 1. Register a client with BankID OIDC

If you don't have a client registered with [BankID OIDC](https://docs.stoe.no/bankid-oidc-provider/introduction.md)
yet, you'll need to register one. Follow the instructions
[here](https://docs.stoe.no/bankid-oidc-provider/resources/provisioning.md) to register your
app and obtain the necessary [client credentials](https://tools.ietf.org/html/rfc6749#section-1.3.4) (`client_id` and `client_secret`).

> **Info**
>
> Ensure that your client has the `permissions/client` and `permissions/app2app` scopes available.
> If you are implementing 3DS for ACS, also enable `permissions/ciba` and include it in the access token used to create the mixed-intent permission and call the required `/bc-authorize` fallback.

## 2. Get the BankID OIDC Configuration

To ensure seamless integration with BankID OIDC and avoid hardcoding specific endpoints in your app, follow these
steps to dynamically fetch the [OpenID Connect configuration](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig):

1. Determine the appropriate configuration URL based on the environment:

   | Environment                       | Url                                                                                   |
   | --------------------------------- | ------------------------------------------------------------------------------------- |
   | Production                        | <https://auth.bankid.no/auth/realms/prod/.well-known/openid-configuration>            |
   | Current (public test environment) | <https://auth.current.bankid.no/auth/realms/current/.well-known/openid-configuration> |

2. Send an HTTP GET request to the respective configuration URL using your preferred programming language or API tool.

3. Capture the response which will contain a JSON document with the configuration. By dynamically fetching the OIDC configuration, your app remains flexible,
   allowing for future changes or updates to the endpoints without requiring modifications to your code.

> **Example response from Current (relevant fields only)**
>
> ```json
> {
>   ...
>   "token_endpoint": "https://auth.current.bankid.no/auth/realms/current/protocol/openid-connect/token",
>   ...
> }
> ```
>
> _This is a sample response. The actual response may differ._

## 3. Get an access token from BankID OIDC

To obtain an access token from OIDC, follow these steps:

1. Retrieve the token endpoint URL `token_endpoint` dynamically from the OIDC configuration.

2. Call the token endpoint URL with your client credentials.

   You can find more detailed information about acquiring access tokens in the [BankID OIDC documentation](https://docs.stoe.no/bankid-oidc-provider/api/token.md).

3. In the scope field, include the following: `permissions/app2app` and `permissions/client`. This field specifies the scope of access granted to the token.

Here's an example of a valid request in the `current` environment:

```http
POST /auth/realms/current/protocol/openid-connect/token HTTP/1.1
Host: auth.current.bankid.no
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <Base64-encoded client id:client secret>

grant_type=client_credentials&scope=permissions/app2app%20permissions/client
```

Make sure to replace the `<Base64-encoded client id:client secret>` placeholder in the Authorization header with your actual client credentials.

## 4. Get the BankID with biometrics OIDC configuration

> **Note**
>
> The BankID with biometrics OIDC configuration is a separate configuration from the BankID OIDC configuration.

To ensure seamless integration and avoid hardcoding specific endpoints in your app, follow these
steps to dynamically fetch the BankID with biometrics [OpenID Connect configuration](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig):

1. Determine the appropriate configuration URL based on the environment:

   | Environment                       | Url                                                                        |
   | --------------------------------- | -------------------------------------------------------------------------- |
   | Production                        | <https://app.bankid.no/.well-known/openid-configuration>                   |
   | Current (public test environment) | <https://current.aletheia-test.idtech.no/.well-known/openid-configuration> |

2. Send an HTTP GET request to the respective configuration URL using your preferred programming language or API tool.

3. Capture the response which will contain a JSON document with the configuration. By dynamically fetching the OIDC configuration, your app remains flexible,
   allowing for future changes or updates to the endpoints without requiring modifications to your code.

4. To enhance the reliability and performance of your integration, we strongly advise implementing response caching for
   the OIDC configuration.

## 5. Register the permission upfront

To register a permission, follow these steps:

1. Construct a _permission statement_. See the [permissions API](https://docs.stoe.no/bankid-with-biometrics/apis/permissions-api.md) for detailed information of how to construct the different permissions.
   Encode the permission statement using URL safe Base64 encoding. Below is an example of a permission statement for a payment:

   ```javascript
   const permissionStatement = {
       "nonce": "dW5pcXVlIHZhbHVl", // Must be unique
       "id": "YmFza2V0IGlk", // Your reference
       "payments": [
           {
               "paymentId": "cGF5bWVudCBpZA", // Your reference
               "amount": "123.45",
               "currency": "NOK",
               "creditorName": "Scrooge McDuck"
           }
       ]
   }

   // Example implementation of a b64 URL Safe Encoder
   function b64URLSafeEncode(stringData) {
       return btoa(stringData)
           .replace(/\+/g, "-")
           .replace(/\//g, "_")
           .replace(/=/g, "");
   }

   b64URLSafeEncode(
       JSON.stringify(
           permissionStatement
       )
   );
   // -> "eyJub25jZSI6ImRXNXBjWFZsSUhaaGJIVmwiLCJpZCI6IlltRnphMlYwSUdsayIsInBheW1lbnRzIjpbeyJwYXltZW50SWQiOiJjR0Y1YldWdWRDQnBaQSIsImFtb3VudCI6IjEyMy40NSIsImN1cnJlbmN5IjoiTk9LIiwiY3JlZGl0b3JOYW1lIjoiU2Nyb29nZSBNY0R1Y2sifV19"
   ```

   > **Note**
   >
   > - The `nonce` must be a unique value for each permission you register.
   > - Use `id` (basket ID) and `paymentId` as references to the payment(s) in your systems. These will not be shown to the end user.
   > - The `amount` is a decimal amount represented as a string and must use the dot as the decimal separator.

2. Send a POST request to the [permissions endpoint](https://docs.stoe.no/bankid-with-biometrics/apis/permissions-api.md), providing the access token received in the first step as a bearer token.
   Include the permission, which is the URL safe Base64 encoding of the permission statement object.

   Intent specifies how the client wants to complete the process using the permission. For the case of the App2App flow, include `app2app` as the intent.

   You may optionally include the `platform` parameter (`ios` or `android`). It controls whether an `app_callback_uri` is required and how the BankID app hands control back to your app:

   | `platform`         | `app_callback_uri` | Return mechanism                                                           |
   | ------------------ | ------------------ | -------------------------------------------------------------------------- |
   | `ios` (or omitted) | **Required**       | The BankID app switches back via the universal link you supply.            |
   | `android`          | Optional           | The BankID app minimizes itself, bringing your app back to the foreground. |

   In both cases you obtain the result by [polling](https://docs.stoe.no/bankid-with-biometrics/advanced-topics/app2app-app-native.md#10-poll-for-the-result) once the user returns to your app.

   If you omit `app_callback_uri` while `platform` is not `android`, the request is rejected, since the callback URI is required to switch back to your app on iOS. On Android the BankID app currently does not use `app_callback_uri` — it minimizes back to your app instead — so you do not need to supply one. You may still include it for forward-compatibility; a future version of the BankID Android app is expected to honor it.

   **iOS**

   ```http
   POST /permissions/v1/ HTTP/1.1
   Host: api.current.aletheia-test.idtech.no
   Content-Type: application/json
   Authorization: Bearer <access token from step 3>

   {
     "type": "payment.v1",
     "iat": 1617091752,
     "exp": 1617092652,
     "permission": < URL safe b64 encoded permission statement from step 1 >,
     "intents": [
       "app2app"
     ],
     "loginHint": [
       {
         "scheme": "nnin",
         "value": "<nnin_as_string>"
       }
     ],
     "platform": "ios",
     "app_callback_uri": "https://merchant.app/app_callback"
   }
   ```

   **Android**

   ```http
   POST /permissions/v1/ HTTP/1.1
   Host: api.current.aletheia-test.idtech.no
   Content-Type: application/json
   Authorization: Bearer <access token from step 3>

   {
     "type": "payment.v1",
     "iat": 1617091752,
     "exp": 1617092652,
     "permission": < URL safe b64 encoded permission statement from step 1 >,
     "intents": [
       "app2app"
     ],
     "loginHint": [
       {
         "scheme": "nnin",
         "value": "<nnin_as_string>"
       }
     ],
     "platform": "android"
   }
   ```

   > **Note**
   >
   > `iat` and `exp` are timestamps in seconds since the UNIX epoch. `iat` can be at most 1 minute
   > into the past. `exp` sets the maximum time by which the permission must be granted and will
   > be set just a few minutes into the future.

3. On a successful request, you will receive a response body containing a permission ID, a permission token, and a `bindingMessage`:

   ```json
   {
     "id": "1.BY.MEKrA-00GVtmvWOCDYQko_fSg93LO5n2rBnlj-X4MQg.BiDEl_CgfakqahcXjLrFet_GGpEkR_W8D-RK4hORBO0",
     "bindingMessage": "Words to display",
     "permissionToken": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjB5Z3hDX3UzQlpPWXlKZ0Y3eDJ0eWtQaTRCTml2cG9KMDBmYjFzTGliUVUiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjE2MTcwOTU3NDQsImlhdCI6MTYxNzA5MjE0NCwicyI6IjA6LTEjOTIzIiwicCI6IkJpREVsX0NnZmFrcWFoY1hqTHJGZXRfR0dwRWtSX1c4RC1SSzRoT1JCTzAiLCJuYmYiOjE2MTcwOTIxNDh9.39GWvNjNMSKKhKOF4t4HcDnZGNJmLmPT3f612z1VKko"
   }
   ```

   Retain `bindingMessage` (camelCase) for display during [manual recovery](https://docs.stoe.no/bankid-with-biometrics/advanced-topics/app2app-app-native.md#10-poll-for-the-result). The `/app-authorize` response does not return these words.

## 6. Acquiring Device Information

> **Device info for 3DS ACS**
>
> See [3DS app2app ACS integration](https://docs.stoe.no/bankid-with-biometrics/advanced-topics/app2app-app-native/3ds-app2app-acs.md) for how to provide 3DS specific device info.

We require the following information to perform device-matching

1. Device Platform — the literal string `ios` or `android`
2. Device Manufacturer
3. Device model

| Field name   | JSON field name | Value on iOS | Value on Android     |
| ------------ | --------------- | ------------ | -------------------- |
| Platform     | `p`             | `ios`        | `android`            |
| Manufacturer | `ma`            | `Apple`      | `Build.MANUFACTURER` |
| Device Model | `mo`            | See below    | `Build.MODEL`        |

The values will be formatted in a JSON-document, that will further be encoded in a URL-safe Base64 encoding just like in step 5.

**iOS**

Example document for an iPhone XS

```json
{
    "p": "ios",
    "ma": "Apple",
    "mo": "iPhone11,2"
}
```

**Android**

Example document for a Samsung Galaxy S23+

```json
{
    "p": "android",
    "ma": "samsung",
    "mo": "SM-S916B"
}
```

> **Warning**
>
> The values are matched **exactly** (case-sensitive) against the device the user enrolled with.
> Send the manufacturer and model strings verbatim as the platform reports them — for example
> Android's `Build.MANUFACTURER` is typically lowercase (`samsung`). Do not normalize casing,
> translate to a marketing name, or otherwise alter the values. Also make sure your serializer
> actually emits all three fields; sending an empty or partial document will cause device-matching
> to fail.

### Finding the device model on iOS

Your device model value must be equivalent to the result of the following Swift-snippet for an app2app to successfully initiate.

```swift
import Foundation

/// Returns a model identifier for the current device.
/// e.g. iPhone12,3
func modelIdentifier() -> String {
    var systemInfo = utsname()
    uname(&systemInfo)
    let machineMirror = Mirror(reflecting: systemInfo.machine)
    return machineMirror.children.reduce("") { identifier, element in
        guard let value = element.value as? Int8, value != 0 else { return identifier }
        return identifier + String(UnicodeScalar(UInt8(value)))
    }
}
```

### Finding the device model on Android

On Android the values are available directly from [`android.os.Build`](https://developer.android.com/reference/android/os/Build).
Use `Build.MANUFACTURER` and `Build.MODEL` as-is, and the literal string `android` for the platform.

```kotlin
import android.os.Build
import android.util.Base64
import org.json.JSONObject

/**
 * Builds the URL-safe Base64 `device_info` value for the app-authorize request.
 *
 *   p  = "android"
 *   ma = Build.MANUFACTURER   // e.g. "samsung"
 *   mo = Build.MODEL          // e.g. "SM-S916B"
 */
fun deviceInfoParam(): String {
    val json = JSONObject()
        .put("p", "android")
        .put("ma", Build.MANUFACTURER)
        .put("mo", Build.MODEL)
        .toString()

    return Base64.encodeToString(
        json.toByteArray(Charsets.UTF_8),
        Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP,
    )
}
```

## 7. Starting the flow

Make a POST request to the [`app-authorize`](https://docs.stoe.no/bankid-with-biometrics/apis/oidc-api.md) endpoint. You can find the endpoint in the BankID with biometrics' OIDC-configuration under the `app_authentication_endpoint` key.

- Include the `permissionToken` obtained from the permission as the `login_hint_token` in the request to the `app-authorize` endpoint.
- In the scope, include the following scopes:
- Required:
  - `openid`
- Optional:
  - `profile` - to get the user's profile information.
    - Claims: `name`, `given_name`, `family_name`, `birthdate`, `updated_at`
  - `sub_nnin` - to get the user's national identity number (NNIN).
    - Claims: `sub_nnin`
    - Note: This is only available for clients with explicit access!
  - `sub_bankid` - to get the user's BankID PID.
    - Claims: `sub_bankid`
- Provide `device_info` as the URL safe Base64 encoded JSON-document of device information as specified in step 6.

Below is an example of a request to the `app-authorize` endpoint:

```http
POST /oidc/v1/app-authorize HTTP/1.1
Host: oidc.current.aletheia-test.idtech.no
Content-Type: application/x-www-form-urlencoded

client_assertion=<access token from step 3>
&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&login_hint_token=string
&scope=openid
&device_info=<URL safe base64 encoded JSON-document>
&requested_expriy=300
```

Ensure to replace the `string` placeholder values with actual data and provide appropriate values for other fields.

After a successful call to the `app-authorize` endpoint, you will receive the following response:

```json
{
    "authorize_uri": "stringstring", 
    "auth_req_id": "stringstring",
    "expires_in": 300,
    "interval": 3
}
```

- `authorize_uri` is where you send your user from your app.
- `auth_req_id` is used to poll for the final token exchange, similarly to [CIBA](https://docs.stoe.no/bankid-with-biometrics/flows/ciba.md).
- `interval` is the polling interval in seconds to be used between each call to the token endpoint.
- `expires_in` may be different from the requested value.

A `403` response with `error: "access_denied"` and `error_description: "No matching device found"` means App2App can not start from this device. Use another flow, like [authorization code grant](https://docs.stoe.no/bankid-with-biometrics/advanced-topics/native-applications.md). For 3DS integrations, see the [required ACS CIBA fallback](https://docs.stoe.no/bankid-with-biometrics/advanced-topics/3ds-app2app-acs.md#bc-authorize-fallback). See the [OpenAPI-specification](https://docs.stoe.no/bankid-with-biometrics/apis/oidc.external.yaml) for detailed error codes.

## 8. Make the user authenticate

The `authorize_uri` from the previous step must be opened on the user's device so the BankID app can take over. It must only be opened if the BankID app is installed on the same device. If the app-switch fails after App2App has started, use the [manual recovery in step 10](https://docs.stoe.no/bankid-with-biometrics/advanced-topics/app2app-app-native.md#10-poll-for-the-result).

In all cases you find out when authentication has succeeded by [polling](https://docs.stoe.no/bankid-with-biometrics/advanced-topics/app2app-app-native.md#10-poll-for-the-result), typically triggered when the user returns to your app.

**iOS**

The `authorize_uri` is a [universal link](https://developer.apple.com/ios/universal-links/).

Open it using [universalLinksOnly](https://developer.apple.com/documentation/uikit/uiapplicationopenurloptionuniversallinksonly?language=swift), so the link only opens if it resolves to the installed BankID app. If the link is not opened as a universal link, use the [manual recovery in step 10](https://docs.stoe.no/bankid-with-biometrics/advanced-topics/app2app-app-native.md#10-poll-for-the-result).

Once the user has authenticated, the BankID app returns them to your app via the `app_callback_uri` you registered. Trigger a poll when your app is reopened.

**Android**

Open the `authorize_uri` with an intent that resolves only to the BankID app. Two complementary mechanisms work together here: the BankID app claims the link's domain through [Android App Links](https://developer.android.com/training/app-links) (domain verification), and your intent additionally requires a non-browser handler so a browser cannot satisfy it. On API level 30+ the non-browser requirement is expressed with [`FLAG_ACTIVITY_REQUIRE_NON_BROWSER`](https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_REQUIRE_NON_BROWSER) — the closest Android equivalent of iOS' `universalLinksOnly`. Note that the flag alone only excludes browsers; it is App Links verification that guarantees the resolved handler is genuinely the BankID app. If the intent cannot be resolved to a non-browser app, use the [manual recovery in step 10](https://docs.stoe.no/bankid-with-biometrics/advanced-topics/app2app-app-native.md#10-poll-for-the-result).

On API levels below 30 `FLAG_ACTIVITY_REQUIRE_NON_BROWSER` is unavailable, so resolve the handler yourself before launching: query the candidates with `PackageManager` (for example `queryIntentActivities`) and only proceed if the BankID app resolves the link; otherwise treat it as "no non-browser handler" and use the [manual recovery in step 10](https://docs.stoe.no/bankid-with-biometrics/advanced-topics/app2app-app-native.md#10-poll-for-the-result).

When the user finishes authenticating, the BankID app minimizes itself and your app returns to the foreground; no `app_callback_uri` is required. Trigger a poll from your activity's `onResume` (and, if you did supply an `app_callback_uri`, also when it is delivered).

## 9. Verifying callback URI

> **Note**
>
> This section applies when an `app_callback_uri` is used.

Apps using deep links for app switching are susceptible to interception and misuse, potentially enabling phishing attacks. To reduce this risk and make exploitation significantly harder, we recommend the following measures:

1. Include a Secure Identifier in the Callback URL
   - Incorporate a hash derived from a session or app identifier into the callback URL. Using a hash ensures that sensitive identifiers are not transmitted in plaintext, adding an additional layer of security.
2. Verify the Callback on Redirection
   - When your app receives the redirect from the BankID app, validate that the callback matches the expected session or device. This step ensures the redirect originated from the correct source and not a malicious attempt.

## 10. Poll for the result

Even if an `app_callback_uri` was used, you must handle a user manually returning to the app and poll for completion to facilitate manual recovery.

Make a POST request to the [token](https://docs.stoe.no/bankid-with-biometrics/apis/oidc-api.md) endpoint using the `CibaTokenRequest` schema.

```http
POST /oidc/v1/token HTTP/1.1
Host: oidc.current.aletheia-test.idtech.no
Content-Type: application/x-www-form-urlencoded

client_assertion=<access token from step 3>
&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&grant_type=urn%3Aopenid%3Aparams%3Agrant%2Dtype%3Aciba
&auth_req_id=string
```

Note: Make sure to replace the placeholders for `auth_req_id` and the BankID OIDC access token with your actual values.

The token endpoint will respond with a 400 `authorization_pending` error if the user has not finished authenticating.

If the authentication succeeded you will get a 200 response with the `id_token` in the response body.

A successful response will have the following structure:

```json
{
    "token_type": "string",
    "expires_in": 0,
    "id_token": "string"
}
```

We might return other error responses as described in our [OpenAPI-specification](https://docs.stoe.no/bankid-with-biometrics/apis/oidc.external.yaml).

If the user declines the authentication you will get an explicit `access_denied` error.

> **Manual recovery after a failed app-switch**
>
> If the user returns to your app after an attempted app-switch and polling returns `authorization_pending`, you **must** display the saved `bindingMessage` if you haven't already, and instruct the user to open the BankID app manually and verify that the same words are shown there.
>
> This facilitates recovery when the BankID app was opened on the wrong device, or a browser opening because the BankID app was not installed.
>
> Continue polling the **same** `auth_req_id`, respecting `interval` and `expires_in`.

## 11. Validate the ID token

To ensure the authenticity and integrity of the received ID token, you must perform the following verification:

- Verify that the token is signed by a key from our [JWKS (JSON Web Key Set)](https://datatracker.ietf.org/doc/html/rfc7517)
  Document. The URI for the JWKS document can be found under the `jwks_uri` key in the BankID with biometrics OIDC configuration
  document you retrieved in [step 4](https://docs.stoe.no/bankid-with-biometrics/advanced-topics/app2app-app-native.md#4-get-the-bankid-with-biometrics-oidc-configuration). You must also verify that the algorithm
  used is found in the BankID with biometrics OIDC configuration under `id_token_signing_alg_values_supported`.
- We recommend verifying these additional properties:
  - `iss` (issuer) - The issuer of the token must match the issuer in the BankID with biometrics OIDC configuration.
  - `aud` (audience) - The audience of the token must match your client ID.
  - `exp` (expiration time) - The token must not be expired.
  - `iat` (issued at) - The token must not be issued in the future.
  - `acr` (authentication context class reference) is only relevant if you've asked for [forced step up](https://docs.stoe.no/bankid-with-biometrics/advanced-topics/forcing-step-up.md). If relevant must be verified to be the expected value.

Further information on how to validate the ID token can be found in the [OpenID Connect Core specification](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation).

Example flow using `app2app` (the diagram shows iOS; per-platform differences are called out in notes)

```mermaid
sequenceDiagram
    autonumber
    participant MerchantApp as Merchant App
    participant MerchantBackend as Merchant Backend
    participant Backend as BankID Biometrics Backend
    participant BAPP as Authenticating app

    Note left of MerchantBackend: Need to have NNIN of user
    MerchantApp ->> MerchantApp: gatherDeviceInfo 
    MerchantApp ->> MerchantBackend: beginAuth w/ device_info<br/>iOS: {"p": "ios", "ma": "Apple", "mo": "iPhone11,2"}<br/>Android: {"p": "android", "ma": "samsung", "mo": "SM-S916B"}
    MerchantBackend ->> Backend: POST /permissions<br/>{<br/>app_callback_uri: "https://merchant.com/callback",<br/>intents: ["app2app"],<br/>loginHint: [{scheme: "nnin", value: "12345678910"}]<br/>}
    Note over MerchantBackend,Backend: Android: set platform: "android" and<br/>app_callback_uri may be omitted
    Backend ->> MerchantBackend: permissionToken, bindingMessage (retain)
    MerchantBackend ->> Backend: POST /app-authorize<br/>device_info=X&login_hint_token=Y&scope=Z
    break device mismatch
        Backend ->> MerchantBackend: 403 access_denied: No matching device found
        Note left of MerchantBackend: App2App not started<br/>Need to fall back to alternate flow
    end
    Backend ->> MerchantBackend: authorize_uri, auth_req_id=A, interval, expires_in
    MerchantBackend ->> MerchantApp: authorize_uri 
    MerchantApp ->> BAPP: open authorize_uri<br/>iOS: universalLinksOnly<br/>Android: require non-browser (App Links)
    BAPP ->> BAPP: user authenticates
    alt App switch back
        BAPP ->> MerchantApp: iOS: app switch OUT w/ app_callback_uri<br/>Android: BankID app minimizes, merchant app resumes
    else Manual return
        BAPP ->> MerchantApp: User returns manually
        Note right of MerchantApp: Must display bindingMessage
    end
    MerchantApp ->> MerchantBackend: Poll for result
    MerchantBackend ->> Backend: POST /token<br/>auth_req_id=A
    Note right of MerchantBackend: Poll with auth_req_id, interval and expiry
    Backend ->> MerchantBackend: id_token
```
