# Iframes support

While iframes have various security challenges and are not supported for regular [BankID](https://bankid.no/bedrift/iframes), it is part of the 3d secure standard and has become an expectation in ecommerce to preserve context to avoid losing the customer in case of errors.

Providing a dedicated iframes solution is a controlled way of moving user interaction from the iframe to other more secure channels while we balance security and usability concerns along the way.

> **Prefer a top-level redirect where possible**
>
> We strongly recommend using the [Authorization Code Flow](https://docs.stoe.no/bankid-with-biometrics/flows/code.md) (top-level redirect) instead of the iframe flow whenever your integration allows it. WebAuthn — the primary authentication mechanism for BankID with biometrics — is not directly supported inside iframes, so an iframe integration will offer a degraded user experience compared to a top-level redirect.

## 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 [provisioning instructions](https://docs.stoe.no/bankid-oidc-provider/resources/provisioning.md) to register your application 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/iframe` scope available.

## 2. Get the BankID OIDC Configuration

To ensure seamless integration with BankID OIDC and avoid hardcoding specific endpoints in your application, 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 application 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.

> **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/client`, and `permissions/iframe`. This field specifies the scope of access granted to the token.

4. To enhance the reliability and performance of your integration, we strongly advise implementing response caching for the access token. For example based on the expires_in property in the token response.

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=openid%20permissions%2Fclient%20permissions%2Fiframe
```

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 application, 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 application 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.

   Login hint is optional but highly recommended to simplify user interaction in the iframe.

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

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

   > **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 should
   > normally 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 binding message:

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

## 6. Create Code Verifier and Code Challenge for PKCE

The iframe flow requires the use of PKCE (Proof Key for Code Exchange by OAuth Public Clients) to protect against
authorization code interception attacks. You will need to create a code verifier and code challenge to use in the next
steps.

For more detailed information and reasoning, please see [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636).

### Steps for Implementing PKCE

1. **Create a Code Verifier**:
   - Generate a `code_verifier`. The verifier should be a high-entropy cryptographic random string.
   - **Character Set**: Use unreserved characters \[A-Z] / \[a-z] / \[0-9] / "-" / "." / "\_" / "\~".
   - **Length**: The length should be between 43 and 128 characters.
   - The Code Verifier will be included when exchanging the authorization code for an access token.
2. **Create the Code Challenge**:
   - Create a `code_challenge` from the `code_verifier` as follows:
     - `code_challenge` = BASE64URL-ENCODE(SHA256(ASCII(`code_verifier`))).
   - **Note**: The `S256` transformation method is mandatory according to [FAPI 2.0](https://openid.net/specs/fapi-2_0-security-02.html#section-5.3.2.2), thus the `plain` transformation is not supported.
   - The Code Challenge will be included when getting an iframe URL with `iframe-authorize`.

## 7. Get iframe URL

1. Make a POST request to the [iframe-authorize](https://docs.stoe.no/bankid-with-biometrics/apis/oidc-api.md) endpoint.
   - Include the `permissionToken` from step 4 as the `login_hint_token` in the iframe-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 `redirect_uri` as the URL to which the user will be redirected after the authentication is completed.
   - Provide `state` to protect against [cross-site request forgery (CSRF)](https://datatracker.ietf.org/doc/html/rfc6749#section-10.12) attacks.
   - Provide `nonce` to associate the OIDC Client session with an ID Token, and to prevent replay attacks.
   - Provide `code_challenge` generated as described in the PKCE section.
   - Provide `code_challenge_method` to indicate which method was used to derive the challenge. Currently, only `S256`
     is supported.
   - Provide `frame_ancestor` to specify which domain is allowed to embed the iframe. This is a security measure to prevent clickjacking.
   - Optionally provide `ui_locales` to specify the preferred language(s) for the user interface. This is a space-separated list of BCP47 language tags. Supported languages are `nb` (Norsk Bokmål), `nn` (Norsk Nynorsk), and `en` (English). If not provided, the browser's language preference is used.

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

```http
POST /iframe-authorize HTTP/1.1
Host: oidc.current.aletheia-test.idtech.no
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer <access token from step 1>

login_hint_token=string
&scope=openid+permissions%2Fiframe
&state=string
&nonce=string
&code_challenge=string
&code_challenge_method=S256
&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback
&frame_ancestor=https%3A%2F%2Fexample.com
&ui_locales=nn

```

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

After calling the iframe-authorize endpoint, you will receive the following response:

```json
{
    "authorize_uri": "stringstring", 
    "expires_in": 60
}
```

## 8. Open url in iframe

Open the `authorize_uri` from the `/iframe-authorize` response in a new iframe to start the auth code flow.

> **Referrer policies**
>
> As a security measure, we will match the `document.referrer` as reported by
> our client against the `frame_ancestor` you provided in step 7.
> The client will error on origin mismatch.
> This means that you cannot use any [Referrer-Policy header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy) that would prevent us from reading the referrer origin on the page that opens the iframe.
>
> If you must use a Referrer-Policy header on your embedding page, in order for the referrer to be readable by us, the value must be one of:
> `no-referrer-when-downgrade`, `origin`, `origin-when-cross-origin` or `unsafe-url`.
>
> Read more about referrer policies [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy).

## 9. Listen for analytics events (optional)

Events are sent to the embedding page (`window.parent`) using `postMessage`.
The intention is to allow insights into the funnel.
The analytics event is a JSON object with the following shape:

```json
{
    "type": "analytics",
    "event": "string"
}
```

Refer to the below tables for possible `event` values:

### Initialization events

| `event`                  | Description                   |
| ------------------------ | ----------------------------- |
| initialization_pending   | The session is initializing.  |
| initialization_completed | The initialization completed. |
| initialization_errored   | The initialization errored.   |

### Authorization events

| `event`               | Description                                                                                 |
| --------------------- | ------------------------------------------------------------------------------------------- |
| authorization_pending | The authorization is in progress, possibly awaiting user consent or additional credentials. |
| authorization_granted | The user has been successfully authorized.                                                  |
| authorization_denied  | The user or system denied the authorization.                                                |
| authorization_timeout | The authorization timed out.                                                                |
| authorization_errored | The authorization errored.                                                                  |

### Additional events

| `event`                           | Description                                             |
| --------------------------------- | ------------------------------------------------------- |
| awaiting_user_identity_input      | Waiting for the user to provide user identity input.    |
| awaiting_delegated_authentication | Waiting for a delegated authentication to be completed. |

A listener for these events can be implemented using this example code:

```js

window.addEventListener("message", (event) => {
  if (event.origin !== "https://app.bankid.no") return; // Verify sender
  
  try{
    const message = JSON.parse(event.data);

    if(message.type !== "analytics") return;

    console.debug(messageData.event) // "initializing"
  }
  catch(err) {}
})
```

## 10. Handle callback and exchange code for tokens

After the user has authenticated or encountered an error, they will be redirected to the callback URI specified in the `redirect_uri` parameter, with the `state`, `code` and `error` query parameters present according to the outcome of the authentication.

From this callback, you can either:

- Transport the data out to the parent frame, for example using `window.parent.postMessage` and make a backend call from there
- Make a backend call from inside the iframe

> **Do not redirect to the merchant from inside the iframe**
>
> A common approach when handling OIDC callbacks is to exchange the auth code and then immediately redirect to the OIDC Relying Party (RP)/merchant.
> However, following this approach means the redirect will happen _inside_ the iframe, and RPs may have Content Security Policies (CSPs) that block non-allowlisted domains from framing them.
> This may result in the RP blocking the redirected request.
>
> To avoid this potential issue, **do not** redirect to the RP from inside the iframe.
> Instead, follow the above methods for transporting the auth code out of the iframe, and **perform the redirect from outside the iframe**.

### Steps to handle the callback and exchange the code for tokens

1. **Retrieve the `code` parameter** from the callback request.
2. **Build the token request** by including the following parameters:
   - `grant_type=authorization_code`: Indicates the usage of the Authorization Code Flow.
   - `redirect_uri`: The same callback URI used in the authorization request.
   - `code`: The authorization code received in the callback.
   - `code_verifier`: Include the `code_verifier` used to generate the `code_challenge` in step 6.
3. **Make a POST request** to the [token](https://docs.stoe.no/bankid-with-biometrics/apis/oidc-api.md) endpoint. Obtain the `token_endpoint` from the BankID with biometrics OIDC configuration, **not** the BankID OIDC configuration. You will use the same access token you received from BankID OIDC in step 3 to authenticate the token request.
   You only need to request a new token if the old has expired.

Here's an example of a valid request:

```http
POST /oidc/v1/token HTTP/1.1
Host: oidc.current.aletheia-test.idtech.no
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer <access token retrieved from BankID OIDC  in step 3>

grant_type=authorization_code
&redirect_uri=https://localhost:3000/api/auth/callback/bid
&code=AUTHORIZATION_CODE
&code_verifier=code_verifier
```

## 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 algorithm used to sign the ID token is found in the BankID with biometrics OIDC configuration under `id_token_signing_alg_values_supported`.
- 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/iframes-support.md#4-get-the-bankid-with-biometrics-oidc-configuration). We recommend caching the JWKS for 30 minutes, and also fetching a new JWKS if the `kid` (key ID) in the ID token does not match any key in the cached JWKS.

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).

## 12. Get the signed permission (optional)

To get the signed permission you can send in a GET request to the `permissionId/grant` endpoint. The `permissionId` you use is the one that you
got from your [permissions](https://docs.stoe.no/bankid-with-biometrics/apis/permissions-api.md) POST request. Be aware that it can return a 404 error code
(permission not yet granted) but as mentioned in the api specification you can set up a polling request on the `permissionId/grant`
endpoint until your initial permission has been signed.

If your permission has been successfully granted you will receive this response:

```json
{
  "gv": 1,
  "pt": "string",
  "type": "string",
  "rpId": "bankid-app",
  "sv": "2020-10-30@deadbeef",
  "iat": 1800000000,
  "iss": "string",
  "nonce": "string",
  "proofKeyId": "string",
  "sub": "nnin:010112345",
  "permissionId": "string",
  "digest": "string"
}
```

The grant is a JWT token that can be validated as follows:

- In the BankID with biometrics OIDC configuration (as mentioned [here](https://docs.stoe.no/bankid-with-biometrics/advanced-topics/iframes-support.md#4-get-the-bankid-with-biometrics-oidc-configuration)) get the Grant JWK set from the URI pointed to by the `jwks_uri_grants` property in this document.
- Check that the JWT token is signed by one of the keys in the Grant JWK set. You can find a suitable library for performing this verification at <https://jwt.io/libraries>.
- Check that the `nonce` claim inside the token payload is equal to the `nonce` in the permission statement created in step 2.
