Iframes support¶
While iframes have various security challenges and are not supported for regular BankID, 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.
Steps
- 1. Register a client with BankID OIDC
- 2. Get the BankID OIDC Configuration
- 3. Get an access token from BankID OIDC
- 4. Get the BankID with biometrics OIDC configuration
- 5. Register the permission upfront
- 6. Create Code Verifier and Code Challenge for PKCE
- 7. Get iframe URL
- 8. Open url in iframe
- 9. Listen for analytics events (optional)
- 10. Handle callback and exchange code for tokens
- 11. Verify the token
- 12. Get the signed permission (optional)
1. Register a client with BankID OIDC¶
If you don't have a client registered with BankID OIDC
yet, you'll need to register one. Follow the instructions
here to register your
application and obtain the necessary client credentials (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:
-
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 -
Send an HTTP GET request to the respective configuration URL using your preferred programming language or API tool.
- 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.
Example response from Current (relevant fields only)
{
...
"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:
-
Retrieve the token endpoint URL
token_endpoint
dynamically from the OIDC configuration. -
Call the token endpoint URL with your client credentials.
You can find more detailed information about acquiring access tokens in the BankID OIDC documentation.
-
In the scope field, include the following:
permissions/client
, andpermissions/iframe
. This field specifies the scope of access granted to the token.
Here's an example of a valid request in the current
environment:
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:
-
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 -
Send an HTTP GET request to the respective configuration URL using your preferred programming language or API tool.
- 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.
- 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:
-
Construct a permission statement. See the permissions API 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:
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) andpaymentId
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.
- The
-
Send a POST request to the permissions endpoint, 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.
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", "loa": "sub", "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
andexp
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. -
On a successful request, you will receive a response body containing a permission ID, a permission token and a binding message:
{ "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.
Steps for Implementing PKCE¶
- 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.
- Generate a
- Create the Code Challenge:
- Create a
code_challenge
from thecode_verifier
as follows:code_challenge
= BASE64URL-ENCODE(SHA256(ASCII(code_verifier
))).
- Note: The
S256
transformation method is mandatory according to FAPI 2.0, thus theplain
transformation is not supported. - The Code Challenge will be included when getting an iframe URL with
iframe-authorize
.
- Create a
7. Get iframe URL¶
- Make a POST request to the iframe-authorize endpoint.
- Include the
permissionToken
from step 4 as thelogin_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
- Claims:
sub_nnin
- to get the user's national identity number (NNIN).- Claims:
sub_nnin
- Note: This is only available for clients with explicit access!
- Claims:
sub_bankid
- to get the user's BankID PID.- Claims:
sub_bankid
- Claims:
- 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) 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, onlyS256
is supported. - Provide
frame_ancestor
to specify which domain is allowed to embed the iframe. This is a security measure to prevent clickjacking.
- Include the
Below is an example of a request to the iframe-authorize endpoint:
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
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:
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 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.
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:
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. |
redirecting_to_merchant | The user is being redirected to the callback url. |
A listener for these events can be implemented using this example code:
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 errored, 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:
- Make a backend call from inside the iframe
- Transport the data out to the parent frame, for example using
window.parent.postMessage
and make a backend call from there
The application server handling the callback should exchange the code
for tokens, as described in
4. Handle callback and exchange the auth code for tokens in the
Authorization Code Flow.
- Ensure that the token exchange uses the token endpoint from the BankID with biometrics OIDC configuration, not the BankID OIDC configuration.
- Ensure that the
code_verifier
is included in the request.
11. Verify the token¶
To ensure the authenticity and integrity of the received token, perform the following verifications:
- Verify that the
nonce
claim in the received ID token corresponds to thenonce
value belonging to the same session (i.e. thenonce
associated with the state). - Verify that the
acr
claim includeLOA=3
(Level of Assurance 3). This indicating the desired level of trust in the authentication process. - Verify that the token is signed by JWKS (JSON Web Key Set) document from the endpoint provided in the BankID OIDC configuration.
If any of the above verifications fail, it indicates a failed authentication process.
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 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:
{
"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) 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 thenonce
in the permission statement created in step 2.