Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/470 use new auth properties app and web #477

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
100 changes: 99 additions & 1 deletion docs/articles/bankid.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ The most common scenbario is to use Active Login for BankID auth/login, so most
+ [Event listeners](#event-listeners)
+ [Store data on auth completion](#store-data-on-auth-completion)
+ [Resolve the end user ip](#resolve-the-end-user-ip)
+ [Resolve the end user device data](#resolve-the-end-user-device-data)
+ [Resolve requirements on Auth request](#resolve-requirements-on-auth-request)
+ [Resolve user data on Auth request](#resolve-user-data-on-auth-request)
+ [Custom QR code generation](#custom-qr-code-generation)
Expand Down Expand Up @@ -968,14 +969,111 @@ services.RemoveAll(typeof(IBankIdResultStore));

### Resolve the end user ip

In some scenarios, like running behind a proxy, you might want to resolve the end user IP yourself and override the default implementaion.
In some scenarios, like running behind a proxy, you might want to resolve the end user IP yourself and override the default implementation.

Either register a class implementing `IBankIdEndUserIpResolver`:

```csharp
services.AddTransient<IBankIdEndUserIpResolver, EndUserIpResolver>();
```

---
### Resolve the end user device data

The User Device feature allows you to include detailed information about the end user's device in BankID authentication and signing requests. This feature improves risk evaluation and aligns with BankID's recommended implementation for risk indication.

When using BankID, device information provides valuable metadata that enhances security and ensures a smoother user experience. Including the **device type** (e.g., `APP` or `WEB`) helps BankID:
- **Evaluate risk** for each request.
- **Take automated actions** based on high-risk scenarios (if enabled).
- Provide **better insights** into the context of the request.

#### Configuring User Device

To customize the User Device feature, use the `UseDeviceData` extension in the BankID client builder. This allows you to specify the device type and any relevant metadata using resolvers.

Active Login has a default implementation of the User Device feature that uses a web browser as the device type. You can customize the default implementation or create your own.

The following implementation is necessary to use the User Device feature:
- IBankIdEndUserDeviceDataResolverFactory: A factory that provides the resolvers for the device type.
- IBankIdEndUserDeviceDataResolver: A resolver that provides the device data for the device type.
- IBankIdEndUserDeviceDataConfiguration: A configuration that specifies the device type to use.

The BankIdFlowService will automatically include the device data in the BankID request.

##### Configuration examples with the default implementation

*For requests initiated by av web browser:*
Liteolika marked this conversation as resolved.
Show resolved Hide resolved

```csharp
services
.AddBankId(bankId =>
{
bankId.UseDeviceData(config =>
{
// Set the device type for the request
config.DeviceType = BankIdEndUserDeviceType.Web;

// Use the default resolver factory
config.UseResolverFactory<BankIdDefaultEndUserDeviceDataResolverFactory>();

// Add the default resolver for Web
config.AddDeviceResolver<BankIdDefaultEndUserWebDeviceDataResolver>();
});
});
```
The BankIdDefaultEndUserWebDeviceDataResolver will set a protected cookie with a
unique identifier (DeviceIdentifier) to ensure that the identifier is persistant
across requests.


*For requests initiated by a mobile app:*

```csharp
services
.AddBankId(bankId =>
{
bankId.UseDeviceData(config =>
{
// Set the device type starting the request
config.DeviceType = BankIdEndUserDeviceType.App;

// Use the default resolver factory to find what device data resolvers to use
config.UseResolverFactory<BankIdDefaultEndUserDeviceDataResolverFactory>();

// Use the default data resolver for the device type app
config.AddDeviceResolver(s => new BankIdDefaultEndUserAppDeviceDataResolver()
{
// App ID or package name
AppIdentifier = "com.example.app",

// Device operating system
DeviceOs = "iOS 16.7.7",

// Device model
DeviceModelName = "Apple iPhone14,3",

// Unique hardware ID
DeviceIdentifier = "1234567890"
});

});
});
```
The information for the BankIdDefaultEndUserAppDeviceDataResolver has to be set during startup.

#### What is included in the requests?

| Device Type | Default Resolver Implementation | Metadata Included |
|---------------|-------------------------------------------|--------------------------------------------|
| **Web** | `BankIdDefaultEndUserWebDeviceDataResolver` | IP Address, User-Agent, Cookies, DeviceIdentifier |
| **App** | `BankIdDefaultEndUserAppDeviceDataResolver` | App Identifier, Device OS, Model, DeviceIdentifier |

More information is available at:
- [BankID Risk Indication](https://www.bankid.com/en/foretag/the-service/risk-indication)
- [BankID Api - Auth](https://developers.bankid.com/api-references/auth--sign/auth)
- [BankID Api - Sign](https://developers.bankid.com/api-references/auth--sign/sign)

---
### Resolve requirements on Auth request

If you want to set the requirements on how the authentication order must be performed dynamically for each order instead of statically during startup in `Program.cs`, it can be done by overriding the default implementation of the `IBankIdAuthRequestRequirementsResolver`.
Expand Down
12 changes: 11 additions & 1 deletion samples/Standalone.MvcSample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
using ActiveLogin.Authentication.BankId.AspNetCore;
using ActiveLogin.Authentication.BankId.AspNetCore.Auth;
using ActiveLogin.Authentication.BankId.AspNetCore.Sign;
using ActiveLogin.Authentication.BankId.AspNetCore.UserContext.Device.Resolvers;
using ActiveLogin.Authentication.BankId.AzureKeyVault;
using ActiveLogin.Authentication.BankId.AzureMonitor;
using ActiveLogin.Authentication.BankId.Core;
using ActiveLogin.Authentication.BankId.Core.CertificatePolicies;
using ActiveLogin.Authentication.BankId.Core.UserContext.Device;
using ActiveLogin.Authentication.BankId.Core.UserContext.Device.Configuration;
using ActiveLogin.Authentication.BankId.Core.UserContext.Device.ResolverFactory;
using ActiveLogin.Authentication.BankId.QrCoder;
using ActiveLogin.Authentication.BankId.UaParser;

Expand Down Expand Up @@ -62,6 +65,13 @@
options.LogDeviceUniqueHardwareId = false;
});

bankId.UseDeviceData(config =>
{
config.DeviceType = BankIdEndUserDeviceType.Web;
config.UseResolverFactory<BankIdDefaultEndUserDeviceDataResolverFactory>();
config.UseDeviceResolver<BankIdDefaultEndUserWebDeviceDataResolver>();
});

bankId.UseQrCoderQrCodeGenerator();
bankId.UseUaParserDeviceDetection();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace ActiveLogin.Authentication.BankId.Api;
public interface IBankIdEndUserDeviceData
{ }
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,25 @@ public class AuthRequest : Request
/// </param>
/// <param name="returnUrl">The URL to return to when the authentication order is completed.</param>
/// <param name="returnRisk">If set to true, a risk indication will be included in the collect response.</param>
/// <param name="deviceParameters">Information about the device the end user is using.</param>
public AuthRequest(
string endUserIp,
Requirement? requirement = null,
string? userVisibleData = null,
byte[]? userNonVisibleData = null,
string? userVisibleDataFormat = null,
string? returnUrl = null,
bool? returnRisk = null)
bool? returnRisk = null,
IBankIdEndUserDeviceData? deviceParameters = null)
: base(
endUserIp,
userVisibleData: userVisibleData,
userNonVisibleData: userNonVisibleData,
requirement: requirement,
userVisibleDataFormat: userVisibleDataFormat,
returnUrl: returnUrl,
returnRisk: returnRisk
returnRisk: returnRisk,
deviceParameters: deviceParameters
)
{
}
Expand Down
25 changes: 25 additions & 0 deletions src/ActiveLogin.Authentication.BankId.Api/Models/DeviceData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace ActiveLogin.Authentication.BankId.Api.Models;

/// <summary>
/// Base class for device parameters.
/// </summary>
/// <param name="deviceIdentifier">
/// The identifier of the device your client is running on.
/// This is used to uniquely identify the device and should be a value
/// that is not tied to a single user of the device.
/// Preferably, it should remain the same even if your app is reinstalled.
/// </param>
public abstract class DeviceData(string deviceIdentifier) : IBankIdEndUserDeviceData
{
/// The identifier of the device your client is running on.
/// This is used to uniquely identify the device and should be a value
/// that is not tied to a single user of the device.
/// Preferably, it should remain the same even if your app is reinstalled.
[JsonPropertyName("deviceIdentifier")]
public string? DeviceIdentifier { get; } = deviceIdentifier;

}

50 changes: 50 additions & 0 deletions src/ActiveLogin.Authentication.BankId.Api/Models/DeviceDataApp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Text.Json.Serialization;

namespace ActiveLogin.Authentication.BankId.Api.Models;

/// <summary>
/// Parameters for the device where the BankID app is running.
/// </summary>
/// <param name="appIdentifier">
/// The identifier of your application.
/// This is the package name on Android and the bundle identifier on iOS.
/// It is vital to use the correct value.If your service does not supply the correct value legitimate orders might be blocked.</param>
/// E.g. "com.example.myapp"
/// <param name="deviceOs">
/// The device operating system where your app is running.
/// E.g. "IOS 16.7.7"
/// </param>
/// <param name="deviceModelName">
/// The model of the device your app is running on.
/// E.g. "Apple iPhone14,3"
/// </param>
/// <param name="deviceIdentifier">
/// <inheritdoc cref="DeviceData(string)" path="/param[@name='deviceIdentifier']"/>
/// </param>
public sealed class DeviceDataApp(
string appIdentifier,
string deviceOs,
string deviceModelName,
string deviceIdentifier)
: DeviceData(deviceIdentifier)
{
/// <summary>
/// Application Identifier, e.g. package name on Android and the bundle identifier on iOS.
/// </summary>
[JsonPropertyName("appIdentifier")]
public string? AppIdentifier { get; set; } = appIdentifier;

/// <summary>
/// Device operating system.
/// </summary>
[JsonPropertyName("deviceOS")]
public string? DeviceOs { get; set; } = deviceOs;

/// <summary>
/// Device model
/// </summary>
[JsonPropertyName("deviceModelName")]
public string? DeviceModelName { get; set; } = deviceModelName;

}

39 changes: 39 additions & 0 deletions src/ActiveLogin.Authentication.BankId.Api/Models/DeviceDataWeb.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Text.Json.Serialization;

namespace ActiveLogin.Authentication.BankId.Api.Models;

/// <summary>
/// Parameters for the browser where the BankID app is running.
/// </summary>
/// <param name="referringDomain">
/// The domain that starts the BankID app.
/// This should generally be your domain name followed by the public suffix,
/// which will generally be the top level domain.
/// E.g. "example.com"
/// </param>
/// <param name="userAgent">
/// The user agent of the user interacting with your web page.
/// E.g. "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/114.0"
/// </param>
/// <param name="deviceIdentifier">
/// <inheritdoc cref="DeviceData(string)" path="/param[@name='deviceIdentifier']"/>
/// </param>
public sealed class DeviceDataWeb(
string referringDomain,
string userAgent,
string deviceIdentifier)
: DeviceData(deviceIdentifier)
{
/// <summary>
/// The domain that starts the BankID app.
/// </summary>
[JsonPropertyName("referringDomain")]
public string? ReferringDomain { get; set; } = referringDomain;

/// <summary>
/// The user agent of the user interacting with your web page.
/// </summary>
[JsonPropertyName("userAgent")]
public string? UserAgent { get; set; } = userAgent;

}
34 changes: 32 additions & 2 deletions src/ActiveLogin.Authentication.BankId.Api/Models/Request.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,10 @@ public Request(string endUserIp, string userVisibleData, byte[]? userNonVisibleD
/// </param>
/// <param name="returnUrl">The URL to return to when the authentication order is completed.</param>
/// <param name="returnRisk">If set to true, a risk indication will be included in the collect response.</param>
public Request(string endUserIp, string? userVisibleData, byte[]? userNonVisibleData, Requirement? requirement, string? userVisibleDataFormat, string? returnUrl = null, bool? returnRisk = null)
/// <param name="deviceParameters">Information about the device the end user is using.</param>
public Request(string endUserIp, string? userVisibleData, byte[]? userNonVisibleData, Requirement? requirement, string? userVisibleDataFormat, string? returnUrl = null, bool? returnRisk = null, IBankIdEndUserDeviceData? deviceParameters = null)
{
if(this is SignRequest && userVisibleData == null)
if (this is SignRequest && userVisibleData == null)
{
throw new ArgumentNullException(nameof(userVisibleData));
}
Expand All @@ -124,6 +125,9 @@ public Request(string endUserIp, string? userVisibleData, byte[]? userNonVisible
UserVisibleDataFormat = userVisibleDataFormat;
ReturnUrl = returnUrl;
ReturnRisk = returnRisk;

SetDeviceParameters(deviceParameters);

}

/// <summary>
Expand Down Expand Up @@ -182,6 +186,12 @@ public Request(string endUserIp, string? userVisibleData, byte[]? userNonVisible
[JsonPropertyName("returnRisk"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public bool? ReturnRisk { get; set; }

[JsonPropertyName("app"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public DeviceDataApp? AppDeviceParameters { get; set; }

[JsonPropertyName("web"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public DeviceDataWeb? WebDeviceParameters { get; set; }

private static string? ToBase64EncodedString(string? value)
{
if (value == null)
Expand All @@ -201,4 +211,24 @@ public Request(string endUserIp, string? userVisibleData, byte[]? userNonVisible

return Convert.ToBase64String(value);
}

/// <summary>
/// Determines the type of device parameters and sets the correct DeviceData property.
/// </summary>
/// <param name="deviceParameters"></param>
private void SetDeviceParameters(IBankIdEndUserDeviceData? deviceParameters)
{
switch (deviceParameters)
{
case null:
return;
case DeviceDataApp appDeviceParameters:
AppDeviceParameters = appDeviceParameters;
break;
case DeviceDataWeb webDeviceParameters:
WebDeviceParameters = webDeviceParameters;
break;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ public class SignRequest : Request
/// </param>
/// <param name="returnUrl">The URL to return to when the authentication order is completed.</param>
/// <param name="returnRisk">If set to true, a risk indication will be included in the collect response.</param>
/// <param name="deviceParameters">Information about the device the end user is using.</param>
public SignRequest(
string endUserIp,
string userVisibleData,
byte[]? userNonVisibleData = null,
Requirement? requirement = null,
string? userVisibleDataFormat = null,
string? returnUrl = null,
bool? returnRisk = null)
: base(endUserIp, userVisibleData, userNonVisibleData, requirement, userVisibleDataFormat, returnUrl, returnRisk)
bool? returnRisk = null,
IBankIdEndUserDeviceData? deviceParameters = null)
: base(endUserIp, userVisibleData, userNonVisibleData, requirement, userVisibleDataFormat, returnUrl, returnRisk, deviceParameters)
{
}
}
Loading
Loading