From 026f206a96bd1781319630d1e9a6140958c3c6d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Conny=20Sj=C3=B6gren?= Date: Mon, 24 Feb 2025 15:36:27 +0100 Subject: [PATCH 1/2] update docs --- .vscode/settings.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..1f5753d8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "markdownlint.config": { + "MD033": { + "allowed_elements": ["a", "img"] + } + } + } From 786f80bc20741b8c58219af3befc8bbcb7515fcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Conny=20Sj=C3=B6gren?= Date: Thu, 25 Sep 2025 15:58:20 +0200 Subject: [PATCH 2/2] seems to work --- docs/articles/bankid.md | 352 +++++++++------- .../Standalone.MvcSample.csproj | 2 +- ...in.Authentication.BankId.AspNetCore.csproj | 2 +- .../Controllers/BankIdUiApiControllerBase.cs | 70 ++-- .../Controllers/BankIdUiAuthApiController.cs | 26 +- .../Controllers/BankIdUiAuthController.cs | 26 +- .../Controllers/BankIdUiControllerBase.cs | 73 ++-- .../BankIdUiPaymentApiController.cs | 47 +-- .../Controllers/BankIdUiPaymentController.cs | 25 +- .../Controllers/BankIdUiSignApiController.cs | 47 +-- .../Controllers/BankIdUiSignController.cs | 26 +- .../AuthenticationBuilderBankIdExtensions.cs | 5 +- .../Auth/BankIdAuthHandler.cs | 104 ++--- .../Auth/BankIdAuthOptions.cs | 5 +- .../Auth/IBankIdAuthBuilderExtensions.cs | 22 + .../BankIdCommonConfiguration.cs | 19 +- .../BankIdConstants.cs | 8 +- .../CookieStateStorage.cs | 105 +++++ .../Cookies/BankIdUiOptionsCookieManager.cs | 2 +- .../BankIdDataStateProtector.cs | 11 +- .../BankIdDeviceDataProtector.cs | 9 +- .../BankIdQrStartStateProtector.cs | 9 +- .../DataProtection/BankIdUiAuthProtector.cs | 12 + .../BankIdUiOptionsProtector.cs | 9 +- .../BankIdUiOrderRefProtector.cs | 9 +- .../BankIdUiPaymentProtector.cs | 12 + .../DataProtection/BankIdUiResultProtector.cs | 9 +- .../DataProtection/BankIdUiSignProtector.cs | 13 + .../DataProtection/BankIdUiStateProtector.cs | 14 - .../IBankIdDataStateProtector.cs | 2 +- .../IBankIdDeviceDataProtector.cs | 10 - .../IBankIdQrStartStateProtector.cs | 9 - .../IBankIdUiOptionsProtector.cs | 9 - .../IBankIdUiOrderRefProtector.cs | 9 - .../IBankIdUiResultProtector.cs | 9 - .../DataProtection/IBankIdUiStateProtector.cs | 9 - .../BankIdUiAuthStateSerializer.cs | 21 + .../BankIdUiOptionsSerializer.cs | 2 +- .../BankIdUiPaymentStateSerializer.cs | 161 ++++++++ .../BankIdUiSignStateSerializer.cs | 117 ++++++ .../Serialization/BankIdUiStateSerializer.cs | 308 -------------- .../Models/BankIdUiOptions.cs | 4 +- .../Payment/BankIdPaymentOptions.cs | 2 +- .../Payment/BankIdPaymentService.cs | 95 ++--- .../ServiceCollectionBankIdExtensions.cs | 4 +- .../Sign/BankIdSignOptions.cs | 2 +- .../Sign/BankIdSignService.cs | 134 +++--- .../Sign/ServiceCollectionBankIdExtensions.cs | 4 +- .../Device/IServiceCollectionExtensions.cs | 9 +- ...nkIdDefaultEndUserWebDeviceDataResolver.cs | 4 +- .../BankIdEndUserWebDeviceDataResolverBase.cs | 22 +- ....Authentication.BankId.AzureMonitor.csproj | 2 +- .../Flow/BankIdFlowService.cs | 4 +- .../IStateStorage.cs | 34 ++ .../ServiceCollectionBankIdExtensions.cs | 6 +- .../BankId_UiAuth_Tests.cs | 248 +++++------ .../BankId_UiPayment_Tests.cs | 170 ++++---- .../BankId_UiSign_Tests.cs | 386 ++++++++---------- .../BankId_Ui_Tests_Base.cs | 201 ++++++--- ...faultEndUserWebDeviceDataResolver_Tests.cs | 4 +- .../Device/UseDeviceDataExtensionTests.cs | 3 +- 61 files changed, 1600 insertions(+), 1477 deletions(-) create mode 100644 src/ActiveLogin.Authentication.BankId.AspNetCore/CookieStateStorage.cs create mode 100644 src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiAuthProtector.cs create mode 100644 src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiPaymentProtector.cs create mode 100644 src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiSignProtector.cs delete mode 100644 src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiStateProtector.cs delete mode 100644 src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdDeviceDataProtector.cs delete mode 100644 src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdQrStartStateProtector.cs delete mode 100644 src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdUiOptionsProtector.cs delete mode 100644 src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdUiOrderRefProtector.cs delete mode 100644 src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdUiResultProtector.cs delete mode 100644 src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdUiStateProtector.cs create mode 100644 src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiAuthStateSerializer.cs create mode 100644 src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiPaymentStateSerializer.cs create mode 100644 src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiSignStateSerializer.cs delete mode 100644 src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiStateSerializer.cs create mode 100644 src/ActiveLogin.Authentication.BankId.Core/IStateStorage.cs diff --git a/docs/articles/bankid.md b/docs/articles/bankid.md index e8c2c485..ad8b6c39 100644 --- a/docs/articles/bankid.md +++ b/docs/articles/bankid.md @@ -6,59 +6,81 @@ The most common scenario is to use Active Login for BankID auth/login, so most o ## Table of contents -* [Getting started](#getting-started) - + [1. Preparation](#1-preparation) - + [2. Read the documentation](#2-read-the-documentation) - + [3. Install the NuGet package](#3-install-the-nuget-package) - + [3. Prepare your project](#3-prepare-your-project) - + [4. Get started in development](#4-get-started-in-development) - + [5. Use test or production environments](#5-use-test-or-production-environments) - + [6. Monitoring](#6-monitoring) -* [Environments](#environments) - + [Simulated environment](#simulated-environment) - + [Simulated environment with no config](#simulated-environment-with-no-config) - + [Simulated environment with custom person info](#simulated-environment-with-custom-person-info) - + [Test environment](#test-environment) - + [Production environment](#production-environment) - + [Full sample for production](#full-sample-for-production) -* [Sign](#sign) -* [Payment](#payment) -* [Basic configuration samples](#basic-configuration-samples) - + [Using client certificate from Azure KeyVault](#using-client-certificate-from-azure-keyvault) - + [Using client certificate from custom source](#using-client-certificate-from-custom-source) - + [Adding schemas](#adding-schemas) - + [Customizing schemas](#customizing-schemas) - + [Custom schema](#custom-schema) - + [Customizing BankID options](#customizing-bankid-options) -* [Concepts](#concepts) - + [Storing certificates in Azure](#storing-certificates-in-azure) - + [Claims Issuing](#claims-issuing) - + [Return URL for cancellation](#return-url-for-cancellation) - + [Handle missing or invalid state cookie](#handle-missing-or-invalid-state-cookie) - + [Multi tenant scenario](#multi-tenant-scenario) - + [Customize the UI](#customize-the-ui) - + [Simulate BankID API errors](#simulate-bankid-api-errors) - + [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 (app or web)](#resolve-the-end-user-device-data-app-or-web) - + [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) - + [Custom browser detection and launch info](#custom-browser-detection-and-launch-info) - + [Risk indication](#risk-indication) - + [Verify digital ID card](#verify-digital-id-card) - + [Use api wrapper only](#use-api-wrapper-only) - + [Running on Linux](#running-on-linux) - + [Localization](#localization) - + [Names of the person might be capitalized](#names-of-the-person-might-be-capitalized) - + [Cookies issued](#cookies-issued) - + [Browser support](#browser-support) - +- [ActiveLogin.Authentication.BankId](#activeloginauthenticationbankid) + - [Table of contents](#table-of-contents) + - [Getting started](#getting-started) + - [1. Preparation](#1-preparation) + - [Certificates](#certificates) + - [2. Read the documentation](#2-read-the-documentation) + - [3. Install the NuGet package](#3-install-the-nuget-package) + - [3. Prepare your project](#3-prepare-your-project) + - [4. Get started in development](#4-get-started-in-development) + - [5. Use test or production environments](#5-use-test-or-production-environments) + - [6. Monitoring](#6-monitoring) + - [Environments](#environments) + - [Simulated environment](#simulated-environment) + - [Simulated environment with no config](#simulated-environment-with-no-config) + - [Simulated environment with custom person info](#simulated-environment-with-custom-person-info) + - [Test environment](#test-environment) + - [Production environment](#production-environment) + - [Full sample for production](#full-sample-for-production) +- [Sign](#sign) +- [Payment](#payment) + - [Basic configuration samples](#basic-configuration-samples) + - [Using client certificate from Azure KeyVault](#using-client-certificate-from-azure-keyvault) + - [Using client certificate from custom source](#using-client-certificate-from-custom-source) + - [Using client certificate from custom certificate service](#using-client-certificate-from-custom-certificate-service) + - [Adding schemas](#adding-schemas) + - [Customizing schemas](#customizing-schemas) + - [Customizing BankID options](#customizing-bankid-options) + - [Concepts](#concepts) + - [Storing certificates in Azure](#storing-certificates-in-azure) + - [Certificates are secrets](#certificates-are-secrets) + - [KeyVault credentials](#keyvault-credentials) + - [Claims Issuing](#claims-issuing) + - [Implementing IBankIdClaimsTransformer](#implementing-ibankidclaimstransformer) + - [Example: Add orderref as txn claim](#example-add-orderref-as-txn-claim) + - [Example: Add birthdate and gender claims](#example-add-birthdate-and-gender-claims) + - [Return URL for cancellation](#return-url-for-cancellation) + - [Handle missing or invalid state cookie](#handle-missing-or-invalid-state-cookie) + - [Multi tenant scenario](#multi-tenant-scenario) + - [Customizing StateStorage](#customizing-statestorage) + - [Customize the UI](#customize-the-ui) + - [Simulate BankID API errors](#simulate-bankid-api-errors) + - [Simulated API error usage](#simulated-api-error-usage) + - [Event listeners](#event-listeners) + - [Event types](#event-types) + - [Sample implementation](#sample-implementation) + - [Built in event listeners](#built-in-event-listeners) + - [BankIdDebugEventListener](#bankiddebugeventlistener) + - [BankIdApplicationInsightsEventListener](#bankidapplicationinsightseventlistener) + - [BankIdLoggerEventListener](#bankidloggereventlistener) + - [Default registered event listeners](#default-registered-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 (app or web)](#resolve-the-end-user-device-data-app-or-web) + - [Configuring Device Data](#configuring-device-data) + - [What is included in the requests?](#what-is-included-in-the-requests) + - [Customizing the Device Data feature](#customizing-the-device-data-feature) + - [Configuration examples](#configuration-examples) + - [More information available at](#more-information-available-at) + - [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) + - [Custom browser detection and launch info](#custom-browser-detection-and-launch-info) + - [Use UAParserDeviceDetector for device and browser detection](#use-uaparserdevicedetector-for-device-and-browser-detection) + - [Shorthand for only overriding config for custom browsers](#shorthand-for-only-overriding-config-for-custom-browsers) + - [Risk indication](#risk-indication) + - [More information available at](#more-information-available-at-1) + - [Verify digital ID card](#verify-digital-id-card) + - [Use api wrapper only](#use-api-wrapper-only) + - [Localization](#localization) + - [Names of the person might be capitalized](#names-of-the-person-might-be-capitalized) + - [Cookies issued](#cookies-issued) + - [Browser support](#browser-support) --- - ## Getting started ### 1. Preparation @@ -78,7 +100,6 @@ The root CA-certificates specified in _BankID Relying Party Guidelines_ (#7 for It is expected that you have a basic understanding of how [ASP.NET](https://docs.microsoft.com/en-us/aspnet/core/), [ASP.NET MVC](https://docs.microsoft.com/en-us/aspnet/core/mvc/overview) and [ASP.NET Authentication](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity) works before getting started. - Active Login is designed to make it very easy to get started with BankID, but in the end you are responsible for making sure that you are compliant with the technical guidelines and/or legal agreements. Therefore, before you start using Active Login, please read the documentation relevant to your needs. This will also make sure you understand the concepts better. @@ -86,7 +107,6 @@ Therefore, before you start using Active Login, please read the documentation re - [BankID (Swedish)](https://www.bankid.com/utvecklare/guider) - [BankID (English)](https://www.bankid.com/en/utvecklare/guider) - ### 3. Install the NuGet package ActiveLogin.Authentication is distributed as [packages on NuGet](https://www.nuget.org/profiles/ActiveLogin), install using the tool of your choice, for example _dotnet cli_. @@ -95,7 +115,6 @@ ActiveLogin.Authentication is distributed as [packages on NuGet](https://www.nug dotnet add package ActiveLogin.Authentication.BankId.AspNetCore ``` - ### 3. Prepare your project The authentication modules for BankID is registered in your `Program.cs`. Depending on your setup, you will probably have to configure challenge and callbacks in `AccountController.cs` or similar. @@ -128,7 +147,6 @@ services }); ``` - ### 5. Use test or production environments To authenticate using a real BankID you need to receive the certificate. See details under Preperation above. @@ -167,7 +185,6 @@ ___Note:___ `.UseQrCoderQrCodeGenerator()` requires the [ActiveLogin.Authenticat ___Note:___ `.UseUaParserDeviceDetection()` requires the [ActiveLogin.Authentication.BankId.UAParser](https://www.nuget.org/packages/ActiveLogin.Authentication.BankId.UAParser/) package. - ### 6. Monitoring Active Login provides a structured way of generating and logging events. These coould be monitored to get statistics and health status of your BankID login method. @@ -176,18 +193,14 @@ Read more on the topic in [Active Login Monitor](monitor.md). ![Active Login Monitor](https://alresourcesprod.blob.core.windows.net/docsassets/active-login-monitor-screenshot_1.png) - --- - ## Environments - ### Simulated environment For trying out quickly (without the need of certificates) you can use an in-memory implementation of the API by using `.UseSimulatedEnvironment()`. This could also be good when writing tests. - ### Simulated environment with no config ```csharp @@ -198,7 +211,6 @@ services }); ``` - ### Simulated environment with custom person info The faked name and personal identity number can also be customized like this. @@ -211,14 +223,13 @@ services }); ``` - ### Test environment This will use the real REST API for BankID, connecting to the Test environment. It will automatically register both the root and client certificate, even though this behaviour can be disabled. A scenario might be that you want to use the same flow for both test and prod and therefore make sure that fetching the certificate from KeyVault works by trying that out for test. It could also be useful if you are running an older version of Active Login which contains an expired version of the test certificate. You can then disable using the embedded, expired certificate and provide the valid test certificate yourself. -BankId provides the client certificate for the test environment in three different versions FPTestcert5_20240610.p12, FPTestcert5_20240610.pem and FPTestcert5_20240610-legacy.pfx. Use `FPTestcert5_20240610.p12` for newer applications and environments that support modern encryption methods. Use `FPTestcert5_20240610.pem` if your application requires PEM format. Use `FPTestcert5_20240610-legacy.pfx ` for older applications requiring older algorithms such as Windows Server earlier versions than 2022. The format of the client certificate can be configured. By default `FPTestcert5_20240610-legacy.pfx `is used. +BankId provides the client certificate for the test environment in three different versions FPTestcert5_20240610.p12, FPTestcert5_20240610.pem and FPTestcert5_20240610-legacy.pfx. Use `FPTestcert5_20240610.p12` for newer applications and environments that support modern encryption methods. Use `FPTestcert5_20240610.pem` if your application requires PEM format. Use `FPTestcert5_20240610-legacy.pfx` for older applications requiring older algorithms such as Windows Server earlier versions than 2022. The format of the client certificate can be configured. By default `FPTestcert5_20240610-legacy.pfx` is used. ```csharp services @@ -324,8 +335,8 @@ services Once that is done you will be able to use these services in your application, for example in your controller: -* `IBankIdSignConfigurationProvider` : List the registered configuraitons (SameDevice / Other Device) -* `IBankIdSignService` : Initiate and resolve the result of sign flow +- `IBankIdSignConfigurationProvider` : List the registered configuraitons (SameDevice / Other Device) +- `IBankIdSignService` : Initiate and resolve the result of sign flow Here is a minimal sample. See `Standalone.MvcSample` for more details. @@ -418,8 +429,8 @@ services.AddBankIdPayment(bankId => Once that is done you will be able to use these services in your application, for example in your controller: -* `IBankIdPaymentConfigurationProvider` : List the registered configuraitons (Same Device / Other Device) -* `IBankIdPaymentService` : Initiate and resolve the result of payment flow +- `IBankIdPaymentConfigurationProvider` : List the registered configuraitons (Same Device / Other Device) +- `IBankIdPaymentService` : Initiate and resolve the result of payment flow Here is a minimal sample. See `Standalone.MvcSample` for more details. @@ -515,7 +526,6 @@ services.AddBankId(bankId => }); ``` - ### Using client certificate from custom source ```csharp @@ -542,8 +552,8 @@ services.AddBankId(bankId => ### Adding schemas -* *Same device*: Launches the BankID app on the same device, no need to enter any personal identity number. -* *Other device*: The user manually launches the app the smartphone and scans the QR code. +- _Same device_: Launches the BankID app on the same device, no need to enter any personal identity number. +- _Other device_: The user manually launches the app the smartphone and scans the QR code. ```csharp services @@ -556,7 +566,6 @@ services }); ``` - ### Customizing schemas By default, `Add*Device` will use predefined schemas and display names, but they can be changed. @@ -610,7 +619,6 @@ To use dynamic requirements with payments provide the requirements as part the ` --- - ## Concepts ### Storing certificates in Azure @@ -685,7 +693,6 @@ They will be evaluated in the order: 2. `DefaultAzureCredential` with `AzureManagedIdentityClientId` (if specified) 3. `DefaultAzureCredential` - ### Claims Issuing Active Login aims to issue the most relevant claims that can be extracted from the information provided by BankID. There are scenarios where you might like to change issued claims or add new ones yourself. @@ -711,7 +718,6 @@ services The claims beeing issued by default have the names/keys specified in the public class `BankIdClaimTypes` so you can refer to them by these constants. - #### Example: Add orderref as txn claim If the application that uses ActiveLogin BankId needs to keep an audit trail of the sign-in, the _txn_ claim could preferably be used for this. @@ -733,7 +739,6 @@ public class BankIdTxnClaimsTransformer : IBankIdClaimsTransformer __Note:__ If the _txn_ claim is issued, you are responsible for making sure to keep relevant audit informaiton given that session. See the OpenId Connect spec linked above for more information. - #### Example: Add birthdate and gender claims It is possible to extract some information from the swedish personal identity number. In previous versions of Active Login this was a built in feature, but is now removed from the default set of claims beeing issued. @@ -793,8 +798,8 @@ If a user cancels the login, the user will be redirected to the `cancelReturnUrl The defaults for cancellation are as follows: -* Same Device Scheme returns to scheme selection -* Other Device Scheme returns to scheme selection when using QR codes +- Same Device Scheme returns to scheme selection +- Other Device Scheme returns to scheme selection when using QR codes It is possible to override the default navigation when cancelling an authentication request. The URL used for navigation is set through the `cancelReturnUrl` item in the `AuthenticationProperties` passed in the authentication challenge. @@ -813,10 +818,9 @@ var props = new AuthenticationProperties return Challenge(props, provider); ``` - ### Handle missing or invalid state cookie -If the user navigates directly to the BankdID status page (*/ActiveLogin/BankId/Auth*) the state cookie (*__ActiveLogin.BankIdState*) will be missing. If that happens, the flow will fail. By default, the user will be redirected back to the `cancelReturnUrl`, see [Setting the return URL for cancellation](#return-url-for-cancellation). +If the user navigates directly to the BankdID status page (_/ActiveLogin/BankId/Auth_) the state cookie (___ActiveLogin.BankIdState_) will be missing. If that happens, the flow will fail. By default, the user will be redirected back to the `cancelReturnUrl`, see [Setting the return URL for cancellation](#return-url-for-cancellation). This behaviour can be overriden by implementing `IBankIdInvalidStateHandler` and adding that to the IOC-container. @@ -834,7 +838,6 @@ public class SampleInvalidStateHandler : IBankIdInvalidStateHandler } ``` - ### Multi tenant scenario With the current architecture of Active Login all services are registered "globally" and you can't call `.AddBankId()` more than once. @@ -884,6 +887,50 @@ public class Startup } ``` +### Customizing StateStorage + +ActiveLogin.Authentication uses an implementation of IStateStorage to persist temporary state during the BankID authentication flow. By default an in-memory implementation is used, which works great for development and testing. However, for production you might want to persist state using a different storage mechanism (for example, in a database or through a distributed cache). + +To do so, implement the IStateStorage interface in your own class: + +```csharp +using ActiveLogin.Authentication.BankId.Core; + +public class CustomStateStorage : IStateStorage +{ + public async Task WriteAsync(object value) + { + // Implement your custom logic to store the state, + // for example, saving data into a database. + var key = Guid.NewGuid().ToString(); + // ...custom persistence logic here... + return await Task.FromResult(new StateKey(key)); + } + + public async Task ReadAsync(StateKey key) + { + // Retrieve the state associated with the key. + return await Task.FromResult(null); + } + + public async Task RemoveAsync(StateKey key) + { + // Remove the stored state. + return await Task.FromResult(null); + } +} +``` + +Register your custom implementation during startup: + +```csharp +services + .AddBankId(bankId => + { + bankId.AddStateStorage(); + // ...other configuration... + }); +``` ### Customize the UI @@ -906,9 +953,9 @@ In this folder, you can then create any of the partials and MVC will then discov If you want, you can override the UI for Auth, Sign and Payment with different templates. Do so by placing the files in one of these folders: -* `Areas/ActiveLogin/Views/BankIdUiAuth` -* `Areas/ActiveLogin/Views/BankIdUiSign` -* `Areas/ActiveLogin/Views/BankIdUiPayment` +- `Areas/ActiveLogin/Views/BankIdUiAuth` +- `Areas/ActiveLogin/Views/BankIdUiSign` +- `Areas/ActiveLogin/Views/BankIdUiPayment` See [the MVC sample](https://github.com/ActiveLogin/ActiveLogin.Authentication/tree/main/samples/Standalone.MvcSample) to see this in action, as demonstrated [here](https://github.com/ActiveLogin/ActiveLogin.Authentication/tree/main/samples/Standalone.MvcSample/Areas/ActiveLogin/Views/BankIdUiAuth/_Wrapper.cshtml). @@ -918,6 +965,7 @@ When developing and testing your application, it can be useful to simulate vario The BankIdBuilder has an extension method `AddSimulatedBankIdApiError` that can be used to simulate errors. The method takes the parameters: + - `errorRate`: The rate of errors to simulate, a value between 0 and 1. For example, 0.5 will simulate an error in 50% of the requests. - `errors`: The errors that will be used to simulate. The errors are defined in a Dictionary with the key being an `ErrorCod e` Enum and the value being the ErrorDescription. - `varyErrorTypes`: If true, the error type will be varied between the errors in the list. If false, the same random error type will be used for all API calls. @@ -926,6 +974,7 @@ The method takes the parameters: The example below will fail 20% of the API calls to BankId with either a RequestTimeout or InternalError. The error type will be varied between the errors. + ```csharp services .AddBankId(bankId => @@ -953,26 +1002,26 @@ During the login flow, quite a lot of things are happening and using our event l At the moment, we trigger the events listed below. They all have unique event properties relevant to the event type. - AspNet - - `BankIdAspNetChallengeSuccessEvent` - - `BankIdAspNetAuthenticateSuccessEvent` - - `BankIdAspNetAuthenticateFailureEvent` + - `BankIdAspNetChallengeSuccessEvent` + - `BankIdAspNetAuthenticateSuccessEvent` + - `BankIdAspNetAuthenticateFailureEvent` - Initialize - - `BankIdInitializeSuccessEvent` - - `BankIdInitializeErrorEvent` + - `BankIdInitializeSuccessEvent` + - `BankIdInitializeErrorEvent` - Sign - - `BankIdSignSuccessEvent` - - `BankIdSignFailureEvent` + - `BankIdSignSuccessEvent` + - `BankIdSignFailureEvent` - Payment - - `BankIdPaymentSuccessEvent` - - `BankIdPaymentFailureEvent` + - `BankIdPaymentSuccessEvent` + - `BankIdPaymentFailureEvent` - Collect - - `BankIdCollectPendingEvent` - - `BankIdCollectCompletedEvent` - - `BankIdCollectFailureEvent` - - `BankIdCollectErrorEvent` + - `BankIdCollectPendingEvent` + - `BankIdCollectCompletedEvent` + - `BankIdCollectFailureEvent` + - `BankIdCollectErrorEvent` - Cancel - - `BankIdCancelSuccessEvent` - - `BankIdCancelErrorEvent` + - `BankIdCancelSuccessEvent` + - `BankIdCancelErrorEvent` #### Sample implementation @@ -1038,7 +1087,6 @@ services You can also customize what kind of data should be logged together with the Application Insight events. For example: - ```csharp services .AddBankId(bankId => @@ -1059,16 +1107,15 @@ services }); ``` - ##### BankIdLoggerEventListener `BankIdLoggerEventListener` will listen for all events and write them with a descriptive text to the log using `ILogger.Log(...)`. This listener is registered by default on startup, se info below if you want to clear the default listeners. - #### Default registered event listeners By default, two event listeners will be enabled: + - `BankIdLoggerEventListener` (Log all events to `ILogger`) - `BankIdResultStoreEventListener` (Map the completion event for `IBankIdResultStore`, see info below under __Store data on auth completion__.) @@ -1078,7 +1125,6 @@ If you want to remove those implementations, remove any class implementing `IBan services.RemoveAll(typeof(IBankIdEventListener)); ``` - ### Store data on auth completion When the login flow is completed and the collect request to BankID returns data, any class implementing `IBankIdResultStore` registered in the DI will be called. @@ -1086,7 +1132,7 @@ There is a shorthand method (`AddResultStore`) on the BankIdBuilder to register ___Note:___ `IBankIdResultStore` is just a shorthand for the `BankIdCollectCompletedEvent` as described above. -*Sample implementation:* +_Sample implementation:_ ```csharp public class BankIdResultSampleLoggerStore : IBankIdResultStore @@ -1120,7 +1166,6 @@ The default implementation will log all data to the tracelog. If you want to rem 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 implementation. @@ -1132,19 +1177,21 @@ services.AddTransient(); ``` --- + ### Resolve the end user device data (app or web) -When initiating a flow with BankID, you can include either the **`web`** parameter (for **web applications**) or the **`app`** parameter (for **mobile apps**) in the request. In Active Login, these parameters are collectively referred to as **device data**. +When initiating a flow with BankID, you can include either the __`web`__ parameter (for __web applications__) or the __`app`__ parameter (for __mobile apps__) in the request. In Active Login, these parameters are collectively referred to as __device data__. The metadata included with these parameters differs depending on the device type, but providing either one allows BankID to deliver a more accurate [Risk indication](#risk-indication). -**Risk Indication** provides an estimated risk level for a BankID transaction. It is a way to enhance the security of your application by, for example, requiring additional controls such as ID card validation for transactions that are assessed as high risk. +__Risk Indication__ provides an estimated risk level for a BankID transaction. It is a way to enhance the security of your application by, for example, requiring additional controls such as ID card validation for transactions that are assessed as high risk. #### Configuring Device Data -Active Login provides a default implementation of the Device Data feature that assumes it is running from a **web application**. +Active Login provides a default implementation of the Device Data feature that assumes it is running from a __web application__. You can either customize this default implementation or create your own for other device types. The following service interface must be implemented to use the Device Data feature: + - `IBankIdEndUserDeviceDataResolverFactory`: Factory that provides the resolvers for the device type. - `IBankIdEndUserDeviceDataResolver`: Resolver that provides the device data for a given device type. - `IBankIdEndUserDeviceDataConfiguration`: Configuration that specifies the device type to use. @@ -1153,23 +1200,22 @@ The following service interface must be implemented to use the Device Data featu | Device Type | Default Resolver Implementation | Metadata Included | |---------------|-------------------------------------------|--------------------------------------------| -| **Web** | `BankIdDefaultEndUserWebDeviceDataResolver` | Referring Domain, User-Agent, Device Identifier | -| **App** | No resolver is configured by default for mobile apps. | App Identifier, Device OS, Model, Device Identifier | +| __Web__ | `BankIdDefaultEndUserWebDeviceDataResolver` | Referring Domain, User-Agent, Device Identifier | +| __App__ | No resolver is configured by default for mobile apps. | App Identifier, Device OS, Model, Device Identifier | -The **Device Identifier**, included in both the **`web`** and **`app`** parameters, must remain identical across requests. +The __Device Identifier__, included in both the __`web`__ and __`app`__ parameters, must remain identical across requests. -For **web applications**, the **Device Identifier** should be unique to the user's browser and must not rely on a session cookie, it can be stored in a separate cookie or as a hash of one. -The `BankIdDefaultEndUserWebDeviceDataResolver` sets a protected cookie named `__ActiveLogin.BankIdDeviceData` containing a unique **Device Identifier**. This ensures that the identifier persists across sessions and requests. - -For **mobile apps**, the **Device Identifier** uniquely identifies the device your client is running on. It should not be tied to a single user of the device and ideally should remain the same even if the app is reinstalled. +For __web applications__, the __Device Identifier__ should be unique to the user's browser and must not rely on a session cookie, it can be stored in a separate cookie or as a hash of one. +The `BankIdDefaultEndUserWebDeviceDataResolver` sets a protected cookie named `__ActiveLogin.BankIdDeviceData` containing a unique __Device Identifier__. This ensures that the identifier persists across sessions and requests. +For __mobile apps__, the __Device Identifier__ uniquely identifies the device your client is running on. It should not be tied to a single user of the device and ideally should remain the same even if the app is reinstalled. ___Note:___ Cookies are protected using ASP.NET Core Data Protection. For more information about the cookies used by the package, including how they are protected and considerations for persistent key storage, see the [Cookies issued](#cookies-issued) section above. This is important, especially in distributed environments. - #### Customizing the Device Data feature + To customize the Device Data feature, use the `UseDeviceData` extension in the BankID client builder. This allows you to specify the device type and any relevant metadata using resolvers. @@ -1183,7 +1229,8 @@ This implementation will set the device type to `BankIdEndUserDeviceType.Web` an the `BankIdDefaultEndUserDeviceDataResolverFactory` to find the correct resolver for the device type. The `BankIdDefaultEndUserWebDeviceDataResolver` is used to fetch the web browser information. -*If no custom implementation is made the device data defaults to this:* +_If no custom implementation is made the device data defaults to this:_ + ```csharp services .AddBankId(bankId => @@ -1202,7 +1249,8 @@ services }); ``` -*Custom implementation requests initiated by av web browser:* +_Custom implementation requests initiated by av web browser:_ + ```csharp services .AddBankId(bankId => @@ -1223,8 +1271,7 @@ services }); ``` - -*Custom implementation requests initiated by a mobile app:* +_Custom implementation requests initiated by a mobile app:_ ```csharp services @@ -1262,14 +1309,15 @@ services The information for the `BankIdAppDeviceDataResolver` must be configured during application startup. You can also provide your own custom resolver implementation for mobile apps. There is no default resolver that automatically fetches device data for mobile apps because this data must be retrieved from the device hardware, which can vary between devices (iOS, Android, etc.). +#### More information available at -#### More information 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) - - [BankID Api - Payment](https://developers.bankid.com/api-references/auth--sign/payment) +- [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) +- [BankID Api - Payment](https://developers.bankid.com/api-references/auth--sign/payment) --- + ### 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`. @@ -1299,9 +1347,9 @@ BankID allows you to display a text during authentication to describe the intent User visible data -* `UserVisibleData` -* `UserNonVisibleData` -* `UserVisibleDataFormat` +- `UserVisibleData` +- `UserNonVisibleData` +- `UserVisibleDataFormat` These can either be set as static data during startup in `Program.cs` or dynamically by overiding the interface `IBankIdAuthRequestUserDataResolver`. @@ -1351,7 +1399,6 @@ services.AddTransient(); ``` - #### Use UAParserDeviceDetector for device and browser detection In Active Login device and browser detection is required for example to determine which URL to use to launch the BankID app, according to the BankID Relaying party Guidelines. This logic is primarily encapsulated into `IBankIdSupportedDeviceDetector`. @@ -1501,9 +1547,9 @@ public class BankIdTxnClaimsTransformer : IBankIdClaimsTransformer } ``` -#### More information available at: - - [BankID Risk Indication](https://www.bankid.com/en/foretag/the-service/risk-indication) +#### More information available at +- [BankID Risk Indication](https://www.bankid.com/en/foretag/the-service/risk-indication) ### Verify digital ID card @@ -1563,8 +1609,6 @@ The constructor for these ApiClients takes an `HttpClient` and you need to confi For easy use the APIs you register the BankID services, select an environment etc. and then the APIs are ready to be injected using IoC. - - ```csharp services .AddBankId(bankId => @@ -1577,7 +1621,8 @@ services ___Note:___ The `BankIdApiClient` class below is available in the `ActiveLogin.Authentication.BankId.Api` package. -*App API:* +_App API:_ + ```csharp public class BankIdAppApiClient : IBankIdAppApiClient { @@ -1591,7 +1636,8 @@ public class BankIdAppApiClient : IBankIdAppApiClient } ``` -*Verify API:* +_Verify API:_ + ```csharp public class BankIdVerifyApiClient : IBankIdVerifyApiClient { @@ -1599,7 +1645,6 @@ public class BankIdVerifyApiClient : IBankIdVerifyApiClient } ``` - ### Localization The messages are already localized to English and Swedish using the official recommended texts. To select what language that is used you can for example use the [localization middleware in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization#localization-middleware). @@ -1610,60 +1655,57 @@ The user messages that will be displayed are provided through the implementation services.AddTransient(); ``` - ### Names of the person might be capitalized The names comes from the bank that the end user has, and some banks (due to legacy) stores all of the names in all caps (like `ALICE SMITH`). We have choosen not to normalize the capitalization of the names as it´s hard or impossible to do so in a general way. - ### Cookies issued The `*.AspNetCore` package will issue a cookie to make the auth flow work - Cookie: `__ActiveLogin.BankIdUiState` - This cookie is there to store state during the auth process, as the user will/might be redirected during the flow. The cookie is session based only and will be deleted once the auth process is finished and/or when the user closes the browser. - + - Because it is strictly related to temp storage during auth, you should not have to inform the user about these specific cookies (according to the [EU "cookie law"](https://www.cookielaw.org/the-cookie-law/)). - + - With the current implementation (following the convention from Microsoft ASP.NET) the usage of cookies is not optional. - + - A more technical deep dive of this cookie can be found in [this issue](https://github.com/ActiveLogin/ActiveLogin.Authentication/issues/156). - Cookie: `__ActiveLogin.BankIdDeviceData` - This cookie is used to store the device data for the user, in the default implementation, it is used to ensure that the device data is persistent across requests. - ___Note:___ -All cookies issued by this package are **protected using ASP.NET Core Data Protection**. This means their contents are encrypted and tamper-proof. +All cookies issued by this package are __protected using ASP.NET Core Data Protection__. This means their contents are encrypted and tamper-proof. -In certain environments (such as multi-instance deployments or containers) you may need to **configure Data Protection to use a persistent key store** (e.g., a shared file system, Azure Blob Storage, Redis, or SQL Server) so that cookies can be unprotected across app restarts or multiple instances. +In certain environments (such as multi-instance deployments or containers) you may need to __configure Data Protection to use a persistent key store__ (e.g., a shared file system, Azure Blob Storage, Redis, or SQL Server) so that cookies can be unprotected across app restarts or multiple instances. For guidance on configuring a persistent key store, see the official documentation: [Data Protection configuration overview](https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-8.0). - ### Browser support We aim at supporting the latest version of all major browsers both on desktop and on mobile. All browsers on mobile are supported to show the UI, but the redirect flow have been tested and verified on these: + - iOS - - Safari - - Chrome - - Edge - - Firefox - - Opera Touch + - Safari + - Chrome + - Edge + - Firefox + - Opera Touch - Android - - Chrome - - Firefox - - Edge - - Samsung Internet - - Opera Mini + - Chrome + - Firefox + - Edge + - Samsung Internet + - Opera Mini ___Note:___ Brave on iOS/Android identifies as Safari or Chrome for privacy reasons and will get wrong configuration, so the redirect flow will fail. ___Note:___ If you aim to support IE11 a polyfill for some JavaScript features we are using is needed. -* [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API): https://github.com/github/fetch +- [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API): diff --git a/samples/Standalone.MvcSample/Standalone.MvcSample.csproj b/samples/Standalone.MvcSample/Standalone.MvcSample.csproj index 82a91222..af64722b 100644 --- a/samples/Standalone.MvcSample/Standalone.MvcSample.csproj +++ b/samples/Standalone.MvcSample/Standalone.MvcSample.csproj @@ -31,6 +31,6 @@ - + diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/ActiveLogin.Authentication.BankId.AspNetCore.csproj b/src/ActiveLogin.Authentication.BankId.AspNetCore/ActiveLogin.Authentication.BankId.AspNetCore.csproj index c1faedbc..2eecfe42 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/ActiveLogin.Authentication.BankId.AspNetCore.csproj +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/ActiveLogin.Authentication.BankId.AspNetCore.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiApiControllerBase.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiApiControllerBase.cs index 219ad1e1..d28fdf76 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiApiControllerBase.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiApiControllerBase.cs @@ -9,7 +9,9 @@ using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; using ActiveLogin.Authentication.BankId.AspNetCore.Helpers; using ActiveLogin.Authentication.BankId.AspNetCore.Models; +using ActiveLogin.Authentication.BankId.Core; using ActiveLogin.Authentication.BankId.Core.Flow; +using ActiveLogin.Authentication.BankId.Core.Models; using ActiveLogin.Authentication.BankId.Core.UserMessage; using Microsoft.AspNetCore.Http; @@ -18,43 +20,29 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.Areas.ActiveLogin.Controllers; [NonController] -public abstract class BankIdUiApiControllerBase : ControllerBase +public abstract class BankIdUiApiControllerBase( + IBankIdFlowService bankIdFlowService, + IBankIdDataStateProtector orderRefProtector, + IBankIdDataStateProtector qrStartStateProtector, + IBankIdUiOptionsCookieManager uiOptionsCookieManager, + + IBankIdUserMessage bankIdUserMessage, + IBankIdUserMessageLocalizer bankIdUserMessageLocalizer, + IBankIdDataStateProtector uiAuthResultProtector, + IStateStorage stateStorage +) : ControllerBase { private static JsonSerializerOptions JsonSerializerOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - protected readonly IBankIdFlowService BankIdFlowService; - protected readonly IBankIdUiOrderRefProtector OrderRefProtector; - protected readonly IBankIdQrStartStateProtector QrStartStateProtector; - protected readonly IBankIdUiOptionsProtector UiOptionsProtector; - protected readonly IBankIdUiOptionsCookieManager UiOptionsCookieManager; + protected readonly IBankIdFlowService BankIdFlowService = bankIdFlowService; + protected readonly IBankIdDataStateProtector OrderRefProtector = orderRefProtector; + protected readonly IBankIdDataStateProtector QrStartStateProtector = qrStartStateProtector; + protected readonly IStateStorage _stateStorage = stateStorage; + protected readonly IBankIdUiOptionsCookieManager UiOptionsCookieManager = uiOptionsCookieManager; - private readonly IBankIdUserMessage _bankIdUserMessage; - private readonly IBankIdUserMessageLocalizer _bankIdUserMessageLocalizer; - private readonly IBankIdUiResultProtector _uiAuthResultProtector; - - protected BankIdUiApiControllerBase( - IBankIdFlowService bankIdFlowService, - IBankIdUiOrderRefProtector orderRefProtector, - IBankIdQrStartStateProtector qrStartStateProtector, - IBankIdUiOptionsProtector uiOptionsProtector, - IBankIdUiOptionsCookieManager uiOptionsCookieManager, - - IBankIdUserMessage bankIdUserMessage, - IBankIdUserMessageLocalizer bankIdUserMessageLocalizer, - IBankIdUiResultProtector uiAuthResultProtector - - ) - { - BankIdFlowService = bankIdFlowService; - OrderRefProtector = orderRefProtector; - QrStartStateProtector = qrStartStateProtector; - UiOptionsProtector = uiOptionsProtector; - UiOptionsCookieManager = uiOptionsCookieManager; - - _bankIdUserMessage = bankIdUserMessage; - _bankIdUserMessageLocalizer = bankIdUserMessageLocalizer; - _uiAuthResultProtector = uiAuthResultProtector; - } + private readonly IBankIdUserMessage _bankIdUserMessage = bankIdUserMessage; + private readonly IBankIdUserMessageLocalizer _bankIdUserMessageLocalizer = bankIdUserMessageLocalizer; + private readonly IBankIdDataStateProtector _uiAuthResultProtector = uiAuthResultProtector; [ValidateAntiForgeryToken] [HttpPost(BankIdConstants.Routes.BankIdApiStatusActionName)] @@ -83,7 +71,7 @@ public async Task Status(BankIdUiApiStatusRequest request) return BadRequestJsonResult(new BankIdUiApiErrorResponse(errorStatusMessage)); } - switch(result) + switch (result) { case BankIdFlowCollectResultPending pending: { @@ -196,4 +184,18 @@ protected BankIdUiOptions ResolveProtectedUiOptions(string protectedUiOptionsOrG return UiOptionsCookieManager.Retrieve(protectedUiOptionsOrGuid) ?? throw new InvalidOperationException(BankIdConstants.ErrorMessages.InvalidUiOptions); } + + protected async ValueTask GetState(BankIdUiOptions uiOptions) + where T : BankIdUiState + { + var cookie = Request.Cookies[uiOptions.StateKeyCookieName]; + if (cookie == null) + { + return null; + } + + var stateKey = new StateKey(cookie); + return await _stateStorage.GetAsync(stateKey); + } + } diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiAuthApiController.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiAuthApiController.cs index 60434bc9..d0406ba6 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiAuthApiController.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiAuthApiController.cs @@ -7,7 +7,7 @@ using ActiveLogin.Authentication.BankId.AspNetCore.Models; using ActiveLogin.Authentication.BankId.Core.Flow; using ActiveLogin.Authentication.BankId.Core.UserMessage; - +using ActiveLogin.Authentication.BankId.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -18,21 +18,17 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.Areas.ActiveLogin.Control [ApiController] [AllowAnonymous] [NonController] -public class BankIdUiAuthApiController : BankIdUiApiControllerBase +public class BankIdUiAuthApiController( + IBankIdFlowService bankIdFlowService, + IBankIdDataStateProtector orderRefProtector, + IBankIdDataStateProtector qrStartStateProtector, + IBankIdUserMessage bankIdUserMessage, + IBankIdUserMessageLocalizer bankIdUserMessageLocalizer, + IBankIdDataStateProtector uiAuthResultProtector, + IBankIdUiOptionsCookieManager uiOptionsCookieManager, + IStateStorage stateStorage +) : BankIdUiApiControllerBase(bankIdFlowService, orderRefProtector, qrStartStateProtector, uiOptionsCookieManager, bankIdUserMessage, bankIdUserMessageLocalizer, uiAuthResultProtector, stateStorage) { - public BankIdUiAuthApiController( - IBankIdFlowService bankIdFlowService, - IBankIdUiOrderRefProtector orderRefProtector, - IBankIdQrStartStateProtector qrStartStateProtector, - IBankIdUiOptionsProtector uiOptionsProtector, - IBankIdUiOptionsCookieManager uiOptionsCookieManager, - IBankIdUserMessage bankIdUserMessage, - IBankIdUserMessageLocalizer bankIdUserMessageLocalizer, - IBankIdUiResultProtector uiAuthResultProtector) - : base(bankIdFlowService, orderRefProtector, qrStartStateProtector, uiOptionsProtector, uiOptionsCookieManager, bankIdUserMessage, bankIdUserMessageLocalizer, uiAuthResultProtector) - { - } - [ValidateAntiForgeryToken] [HttpPost(BankIdConstants.Routes.BankIdApiInitializeActionName)] public async Task> Initialize(BankIdUiApiInitializeRequest request) diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiAuthController.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiAuthController.cs index 95ff0446..91f8a9d9 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiAuthController.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiAuthController.cs @@ -1,6 +1,7 @@ +using ActiveLogin.Authentication.BankId.AspNetCore.Auth; using ActiveLogin.Authentication.BankId.AspNetCore.Cookies; -using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; using ActiveLogin.Authentication.BankId.AspNetCore.StateHandling; +using ActiveLogin.Authentication.BankId.Core; using ActiveLogin.Authentication.BankId.Core.UserMessage; using Microsoft.AspNetCore.Antiforgery; @@ -13,22 +14,15 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.Areas.ActiveLogin.Control [Area(BankIdConstants.Routes.ActiveLoginAreaName)] [AllowAnonymous] [NonController] -public class BankIdUiAuthController : BankIdUiControllerBase +public class BankIdUiAuthController( + IAntiforgery antiforgery, + IStringLocalizer localizer, + IBankIdUserMessageLocalizer bankIdUserMessageLocalizer, + IBankIdInvalidStateHandler bankIdInvalidStateHandler, + IBankIdUiOptionsCookieManager uiOptionsCookieManager, + IStateStorage stateStorage +) : BankIdUiControllerBase(antiforgery, localizer, bankIdUserMessageLocalizer, bankIdInvalidStateHandler, uiOptionsCookieManager, stateStorage) { - public BankIdUiAuthController( - IAntiforgery antiforgery, - IStringLocalizer localizer, - IBankIdUserMessageLocalizer bankIdUserMessageLocalizer, - IBankIdUiOptionsProtector uiOptionsProtector, - IBankIdInvalidStateHandler bankIdInvalidStateHandler, - IBankIdUiStateProtector bankIdUiStateProtector, - IBankIdUiOptionsCookieManager uiOptionsCookieManager - ) - : base(antiforgery, localizer, bankIdUserMessageLocalizer, uiOptionsProtector, bankIdInvalidStateHandler, bankIdUiStateProtector, uiOptionsCookieManager) - { - - } - [HttpGet] [Route($"/[area]/{BankIdConstants.Routes.BankIdPathName}/{BankIdConstants.Routes.BankIdAuthControllerPath}")] public Task Init(string returnUrl, [FromQuery(Name = BankIdConstants.QueryStringParameters.UiOptions)] string uiOptionsGuid) diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiControllerBase.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiControllerBase.cs index 5c26b83c..f45c6b76 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiControllerBase.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiControllerBase.cs @@ -7,6 +7,7 @@ using ActiveLogin.Authentication.BankId.AspNetCore.Payment; using ActiveLogin.Authentication.BankId.AspNetCore.Sign; using ActiveLogin.Authentication.BankId.AspNetCore.StateHandling; +using ActiveLogin.Authentication.BankId.Core; using ActiveLogin.Authentication.BankId.Core.UserMessage; using Microsoft.AspNetCore.Antiforgery; @@ -16,32 +17,32 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.Areas.ActiveLogin.Controllers; [NonController] -public abstract class BankIdUiControllerBase : Controller +public abstract class BankIdUiControllerBase( + IAntiforgery antiforgery, + IStringLocalizer localizer, + IBankIdUserMessageLocalizer bankIdUserMessageLocalizer, + IBankIdInvalidStateHandler bankIdInvalidStateHandler, + IBankIdUiOptionsCookieManager uiOptionsCookieManager, + IStateStorage stateStorage +) : Controller + where T : BankIdUiState { - private readonly IAntiforgery _antiforgery; - private readonly IStringLocalizer _localizer; - private readonly IBankIdUserMessageLocalizer _bankIdUserMessageLocalizer; - private readonly IBankIdUiOptionsProtector _uiOptionsProtector; - private readonly IBankIdInvalidStateHandler _bankIdInvalidStateHandler; - private readonly IBankIdUiStateProtector _bankIdUiStateProtector; - private readonly IBankIdUiOptionsCookieManager _uiOptionsCookieManager; - - protected BankIdUiControllerBase( - IAntiforgery antiforgery, - IStringLocalizer localizer, - IBankIdUserMessageLocalizer bankIdUserMessageLocalizer, - IBankIdUiOptionsProtector uiOptionsProtector, - IBankIdInvalidStateHandler bankIdInvalidStateHandler, - IBankIdUiStateProtector bankIdUiStateProtector, - IBankIdUiOptionsCookieManager uiOptionsCookieManager) + private readonly IAntiforgery _antiforgery = antiforgery; + private readonly IStringLocalizer _localizer = localizer; + private readonly IBankIdUserMessageLocalizer _bankIdUserMessageLocalizer = bankIdUserMessageLocalizer; + private readonly IBankIdInvalidStateHandler _bankIdInvalidStateHandler = bankIdInvalidStateHandler; + private readonly IBankIdUiOptionsCookieManager _uiOptionsCookieManager = uiOptionsCookieManager; + private readonly IStateStorage stateStorage = stateStorage; + + protected Task GetUIState(BankIdUiOptions uiOptions) { - _antiforgery = antiforgery; - _localizer = localizer; - _bankIdUserMessageLocalizer = bankIdUserMessageLocalizer; - _uiOptionsProtector = uiOptionsProtector; - _bankIdInvalidStateHandler = bankIdInvalidStateHandler; - _bankIdUiStateProtector = bankIdUiStateProtector; - _uiOptionsCookieManager = uiOptionsCookieManager; + var cookie = HttpContext.Request.Cookies[uiOptions.StateKeyCookieName]; + if (cookie is null) + { + return Task.FromResult(default); + } + var stateKey = new StateKey(cookie); + return stateStorage.GetAsync(stateKey); } protected async Task Initialize(string returnUrl, string apiControllerName, string uiOptionsGuid, string viewName) @@ -63,18 +64,17 @@ protected async Task Initialize(string returnUrl, string apiContro return new EmptyResult(); } - var antiforgeryTokens = _antiforgery.GetAndStoreTokens(HttpContext); + var state = await GetUIState(uiOptions); - var protectedState = Request.Cookies[uiOptions.StateCookieName]; - if(protectedState == null) + if (state == null) { var invalidStateContext = new BankIdInvalidStateContext(uiOptions.CancelReturnUrl); await _bankIdInvalidStateHandler.HandleAsync(invalidStateContext); return new EmptyResult(); } - var state = _bankIdUiStateProtector.Unprotect(protectedState); + var antiforgeryTokens = _antiforgery.GetAndStoreTokens(HttpContext); var viewModel = GetUiViewModel(returnUrl, apiControllerName, uiOptionsGuid, uiOptions, state, antiforgeryTokens); return View(viewName, viewModel); @@ -82,13 +82,22 @@ protected async Task Initialize(string returnUrl, string apiContro private bool HasStateCookie(BankIdUiOptions uiOptions) { - if (string.IsNullOrEmpty(uiOptions.StateCookieName) - || !HttpContext.Request.Cookies.ContainsKey(uiOptions.StateCookieName)) + if (string.IsNullOrEmpty(uiOptions.StateKeyCookieName)) + { + return false; + } + + if (!HttpContext.Request.Cookies.ContainsKey(uiOptions.StateKeyCookieName)) + { + return false; + } + + if (string.IsNullOrEmpty(HttpContext.Request.Cookies[uiOptions.StateKeyCookieName])) { return false; } - return !string.IsNullOrEmpty(HttpContext.Request.Cookies[uiOptions.StateCookieName]); + return true; } private BankIdUiViewModel GetUiViewModel(string returnUrl, string apiControllerName, string uiOptionsGuid, BankIdUiOptions unprotectedUiOptions, BankIdUiState uiState, AntiforgeryTokenSet antiforgeryTokens) @@ -127,7 +136,7 @@ private BankIdUiViewModel GetUiViewModel(string returnUrl, string apiControllerN var localizedCancelButtonText = _localizer["Cancel_Button"]; var localizedQrCodeImageAltText = _localizer["Qr_Code_Image"]; - if(uiState is BankIdUiSignState signState) + if (uiState is BankIdUiSignState signState) { var uiSignData = new BankIdUiSignData { diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiPaymentApiController.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiPaymentApiController.cs index 19d38a6f..2857b6ea 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiPaymentApiController.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiPaymentApiController.cs @@ -6,6 +6,7 @@ using ActiveLogin.Authentication.BankId.AspNetCore.Helpers; using ActiveLogin.Authentication.BankId.AspNetCore.Models; using ActiveLogin.Authentication.BankId.AspNetCore.Payment; +using ActiveLogin.Authentication.BankId.Core; using ActiveLogin.Authentication.BankId.Core.Flow; using ActiveLogin.Authentication.BankId.Core.Models; using ActiveLogin.Authentication.BankId.Core.UserMessage; @@ -20,25 +21,17 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.Areas.ActiveLogin.Control [ApiController] [AllowAnonymous] [NonController] -public class BankIdUiPaymentApiController : BankIdUiApiControllerBase +public class BankIdUiPaymentApiController( + IBankIdFlowService bankIdFlowService, + IBankIdDataStateProtector orderRefProtector, + IBankIdDataStateProtector qrStartStateProtector, + IBankIdUserMessage bankIdUserMessage, + IBankIdUserMessageLocalizer bankIdUserMessageLocalizer, + IBankIdDataStateProtector uiAuthResultProtector, + IBankIdUiOptionsCookieManager uiOptionsCookieManager, + IStateStorage stateStorage +) : BankIdUiApiControllerBase(bankIdFlowService, orderRefProtector, qrStartStateProtector, uiOptionsCookieManager, bankIdUserMessage, bankIdUserMessageLocalizer, uiAuthResultProtector, stateStorage) { - private readonly IBankIdUiStateProtector _bankIdUiStateProtector; - - public BankIdUiPaymentApiController( - IBankIdFlowService bankIdFlowService, - IBankIdUiOrderRefProtector orderRefProtector, - IBankIdQrStartStateProtector qrStartStateProtector, - IBankIdUiOptionsProtector uiOptionsProtector, - IBankIdUiOptionsCookieManager uiOptionsCookieManager, - IBankIdUserMessage bankIdUserMessage, - IBankIdUserMessageLocalizer bankIdUserMessageLocalizer, - IBankIdUiResultProtector uiAuthResultProtector, - IBankIdUiStateProtector bankIdUiStateProtector) - : base(bankIdFlowService, orderRefProtector, qrStartStateProtector, uiOptionsProtector, uiOptionsCookieManager, bankIdUserMessage, bankIdUserMessageLocalizer, uiAuthResultProtector) - { - _bankIdUiStateProtector = bankIdUiStateProtector; - } - [ValidateAntiForgeryToken] [HttpPost(BankIdConstants.Routes.BankIdApiInitializeActionName)] public async Task> Initialize(BankIdUiApiInitializeRequest request) @@ -48,12 +41,7 @@ public async Task> Initialize(BankId var uiOptions = ResolveProtectedUiOptions(request.UiOptions); - var state = GetStateFromCookie(uiOptions); - if(state == null) - { - throw new InvalidOperationException(BankIdConstants.ErrorMessages.InvalidStateCookie); - } - + var state = await GetState(uiOptions) ?? throw new InvalidOperationException(BankIdConstants.ErrorMessages.InvalidStateCookie); BankIdFlowInitializeResult bankIdFlowInitializeResult; try { @@ -110,15 +98,4 @@ public async Task> Initialize(BankId } } } - - private BankIdUiPaymentState? GetStateFromCookie(BankIdUiOptions uiOptions) - { - var protectedState = Request.Cookies[uiOptions.StateCookieName]; - if (protectedState == null) - { - throw new InvalidOperationException(BankIdConstants.ErrorMessages.InvalidStateCookie); - } - - return _bankIdUiStateProtector.Unprotect(protectedState) as BankIdUiPaymentState; - } } diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiPaymentController.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiPaymentController.cs index e5db8842..07c5d4d0 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiPaymentController.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiPaymentController.cs @@ -1,6 +1,8 @@ using ActiveLogin.Authentication.BankId.AspNetCore.Cookies; using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; +using ActiveLogin.Authentication.BankId.AspNetCore.Payment; using ActiveLogin.Authentication.BankId.AspNetCore.StateHandling; +using ActiveLogin.Authentication.BankId.Core; using ActiveLogin.Authentication.BankId.Core.UserMessage; using Microsoft.AspNetCore.Antiforgery; @@ -13,22 +15,15 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.Areas.ActiveLogin.Control [Area(BankIdConstants.Routes.ActiveLoginAreaName)] [AllowAnonymous] [NonController] -public class BankIdUiPaymentController : BankIdUiControllerBase +public class BankIdUiPaymentController( + IAntiforgery antiforgery, + IStringLocalizer localizer, + IBankIdUserMessageLocalizer bankIdUserMessageLocalizer, + IBankIdUiOptionsCookieManager uiOptionsCookieManager, + IBankIdInvalidStateHandler bankIdInvalidStateHandler, + IStateStorage stateStorage +) : BankIdUiControllerBase(antiforgery, localizer, bankIdUserMessageLocalizer, bankIdInvalidStateHandler, uiOptionsCookieManager, stateStorage) { - public BankIdUiPaymentController( - IAntiforgery antiforgery, - IStringLocalizer localizer, - IBankIdUserMessageLocalizer bankIdUserMessageLocalizer, - IBankIdUiOptionsProtector uiOptionsProtector, - IBankIdInvalidStateHandler bankIdInvalidStateHandler, - IBankIdUiStateProtector bankIdUiStateProtector, - IBankIdUiOptionsCookieManager uiOptionsCookieManager - ) - : base(antiforgery, localizer, bankIdUserMessageLocalizer, uiOptionsProtector, bankIdInvalidStateHandler, bankIdUiStateProtector, uiOptionsCookieManager) - { - - } - [HttpGet] [AllowAnonymous] [Route($"/[area]/{BankIdConstants.Routes.BankIdPathName}/{BankIdConstants.Routes.BankIdPaymentControllerPath}")] diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiSignApiController.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiSignApiController.cs index d5000097..a524757e 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiSignApiController.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiSignApiController.cs @@ -6,6 +6,7 @@ using ActiveLogin.Authentication.BankId.AspNetCore.Helpers; using ActiveLogin.Authentication.BankId.AspNetCore.Models; using ActiveLogin.Authentication.BankId.AspNetCore.Sign; +using ActiveLogin.Authentication.BankId.Core; using ActiveLogin.Authentication.BankId.Core.Flow; using ActiveLogin.Authentication.BankId.Core.Models; using ActiveLogin.Authentication.BankId.Core.UserMessage; @@ -20,25 +21,17 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.Areas.ActiveLogin.Control [ApiController] [AllowAnonymous] [NonController] -public class BankIdUiSignApiController : BankIdUiApiControllerBase +public class BankIdUiSignApiController( + IBankIdFlowService bankIdFlowService, + IBankIdDataStateProtector orderRefProtector, + IBankIdDataStateProtector qrStartStateProtector, + IBankIdUserMessage bankIdUserMessage, + IBankIdUserMessageLocalizer bankIdUserMessageLocalizer, + IBankIdDataStateProtector uiAuthResultProtector, + IBankIdUiOptionsCookieManager uiOptionsCookieManager, + IStateStorage stateStorage +) : BankIdUiApiControllerBase(bankIdFlowService, orderRefProtector, qrStartStateProtector, uiOptionsCookieManager, bankIdUserMessage, bankIdUserMessageLocalizer, uiAuthResultProtector, stateStorage) { - private readonly IBankIdUiStateProtector _bankIdUiStateProtector; - - public BankIdUiSignApiController( - IBankIdFlowService bankIdFlowService, - IBankIdUiOrderRefProtector orderRefProtector, - IBankIdQrStartStateProtector qrStartStateProtector, - IBankIdUiOptionsProtector uiOptionsProtector, - IBankIdUiOptionsCookieManager uiOptionsCookieManager, - IBankIdUserMessage bankIdUserMessage, - IBankIdUserMessageLocalizer bankIdUserMessageLocalizer, - IBankIdUiResultProtector uiAuthResultProtector, - IBankIdUiStateProtector bankIdUiStateProtector) - : base(bankIdFlowService, orderRefProtector, qrStartStateProtector, uiOptionsProtector, uiOptionsCookieManager, bankIdUserMessage, bankIdUserMessageLocalizer, uiAuthResultProtector) - { - _bankIdUiStateProtector = bankIdUiStateProtector; - } - [ValidateAntiForgeryToken] [HttpPost(BankIdConstants.Routes.BankIdApiInitializeActionName)] public async Task> Initialize(BankIdUiApiInitializeRequest request) @@ -48,12 +41,7 @@ public async Task> Initialize(BankId var uiOptions = ResolveProtectedUiOptions(request.UiOptions); - var state = GetStateFromCookie(uiOptions); - if(state == null) - { - throw new InvalidOperationException(BankIdConstants.ErrorMessages.InvalidStateCookie); - } - + var state = await GetState(uiOptions) ?? throw new InvalidOperationException(BankIdConstants.ErrorMessages.InvalidStateCookie); BankIdFlowInitializeResult bankIdFlowInitializeResult; try { @@ -106,15 +94,4 @@ public async Task> Initialize(BankId } } } - - private BankIdUiSignState? GetStateFromCookie(BankIdUiOptions uiOptions) - { - var protectedState = Request.Cookies[uiOptions.StateCookieName]; - if (protectedState == null) - { - throw new InvalidOperationException(BankIdConstants.ErrorMessages.InvalidStateCookie); - } - - return _bankIdUiStateProtector.Unprotect(protectedState) as BankIdUiSignState; - } } diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiSignController.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiSignController.cs index 74df05d6..2dce43b8 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiSignController.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Areas/ActiveLogin/Controllers/BankIdUiSignController.cs @@ -1,6 +1,9 @@ using ActiveLogin.Authentication.BankId.AspNetCore.Cookies; using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; +using ActiveLogin.Authentication.BankId.AspNetCore.Models; +using ActiveLogin.Authentication.BankId.AspNetCore.Sign; using ActiveLogin.Authentication.BankId.AspNetCore.StateHandling; +using ActiveLogin.Authentication.BankId.Core; using ActiveLogin.Authentication.BankId.Core.UserMessage; using Microsoft.AspNetCore.Antiforgery; @@ -13,22 +16,15 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.Areas.ActiveLogin.Control [Area(BankIdConstants.Routes.ActiveLoginAreaName)] [AllowAnonymous] [NonController] -public class BankIdUiSignController : BankIdUiControllerBase +public class BankIdUiSignController( + IAntiforgery antiforgery, + IStringLocalizer localizer, + IBankIdUserMessageLocalizer bankIdUserMessageLocalizer, + IBankIdUiOptionsCookieManager uiOptionsCookieManager, + IBankIdInvalidStateHandler bankIdInvalidStateHandler, + IStateStorage stateStorage +) : BankIdUiControllerBase(antiforgery, localizer, bankIdUserMessageLocalizer, bankIdInvalidStateHandler, uiOptionsCookieManager, stateStorage) { - public BankIdUiSignController( - IAntiforgery antiforgery, - IStringLocalizer localizer, - IBankIdUserMessageLocalizer bankIdUserMessageLocalizer, - IBankIdUiOptionsProtector uiOptionsProtector, - IBankIdInvalidStateHandler bankIdInvalidStateHandler, - IBankIdUiStateProtector bankIdUiStateProtector, - IBankIdUiOptionsCookieManager uiOptionsCookieManager - ) - : base(antiforgery, localizer, bankIdUserMessageLocalizer, uiOptionsProtector, bankIdInvalidStateHandler, bankIdUiStateProtector, uiOptionsCookieManager) - { - - } - [HttpGet] [AllowAnonymous] [Route($"/[area]/{BankIdConstants.Routes.BankIdPathName}/{BankIdConstants.Routes.BankIdSignControllerPath}")] diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Auth/AuthenticationBuilderBankIdExtensions.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Auth/AuthenticationBuilderBankIdExtensions.cs index f9179300..c49b1c9e 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Auth/AuthenticationBuilderBankIdExtensions.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Auth/AuthenticationBuilderBankIdExtensions.cs @@ -1,6 +1,5 @@ using ActiveLogin.Authentication.BankId.AspNetCore.ApplicationFeatureProviders; using ActiveLogin.Authentication.BankId.AspNetCore.ClaimsTransformation; -using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; using ActiveLogin.Authentication.BankId.Core; using ActiveLogin.Authentication.BankId.Core.Requirements; using ActiveLogin.Authentication.BankId.Core.UserData; @@ -8,6 +7,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.DependencyInjection; + namespace ActiveLogin.Authentication.BankId.AspNetCore.Auth; public static class AuthenticationBuilderBankIdAuthExtensions @@ -72,9 +72,6 @@ private static void AddBankIdAuthDefaultServices(IBankIdAuthBuilder builder) BankIdCommonConfiguration.AddDefaultServices(services); - services.AddTransient(); - services.AddTransient(); - builder.AddClaimsTransformer(); builder.UseAuthRequestUserDataResolver(); diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Auth/BankIdAuthHandler.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Auth/BankIdAuthHandler.cs index aea06b00..5896f1da 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Auth/BankIdAuthHandler.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Auth/BankIdAuthHandler.cs @@ -1,12 +1,12 @@ using System.Security.Claims; using System.Text.Encodings.Web; -using ActiveLogin.Authentication.BankId.Api.Models; using ActiveLogin.Authentication.BankId.AspNetCore.ClaimsTransformation; using ActiveLogin.Authentication.BankId.AspNetCore.Cookies; using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; using ActiveLogin.Authentication.BankId.AspNetCore.Helpers; using ActiveLogin.Authentication.BankId.AspNetCore.Models; +using ActiveLogin.Authentication.BankId.Core; using ActiveLogin.Authentication.BankId.Core.Events; using ActiveLogin.Authentication.BankId.Core.Events.Infrastructure; using ActiveLogin.Authentication.BankId.Core.SupportedDevice; @@ -22,58 +22,47 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.Auth; -public class BankIdAuthHandler : RemoteAuthenticationHandler +public class BankIdAuthHandler( + IHttpContextAccessor httpContextAccessor, + IAntiforgery antiforgery, + IOptionsMonitor options, + ILoggerFactory loggerFactory, + UrlEncoder encoder, + IBankIdDataStateProtector uiResultProtector, + IBankIdEventTrigger bankIdEventTrigger, + IBankIdSupportedDeviceDetector bankIdSupportedDeviceDetector, + IEnumerable bankIdClaimsTransformers, + IBankIdUiOptionsCookieManager uiOptionsCookieManager, + IStateStorage stateStorage +) : RemoteAuthenticationHandler(options, loggerFactory, encoder) { - private const string StateCookieNameParameterName = "StateCookie.Name"; private readonly PathString _authPath = new($"/{BankIdConstants.Routes.ActiveLoginAreaName}/{BankIdConstants.Routes.BankIdPathName}/{BankIdConstants.Routes.BankIdAuthControllerPath}"); - - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IAntiforgery _antiforgery; - private readonly IBankIdUiStateProtector _uiStateProtector; - private readonly IBankIdUiOptionsProtector _uiOptionsProtector; - private readonly IBankIdUiResultProtector _uiResultProtector; - private readonly IBankIdEventTrigger _bankIdEventTrigger; - private readonly IBankIdSupportedDeviceDetector _bankIdSupportedDeviceDetector; - private readonly IBankIdUiOptionsCookieManager _uiOptionsCookieManager; - private readonly List _bankIdClaimsTransformers; - - public BankIdAuthHandler( - IHttpContextAccessor httpContextAccessor, - IAntiforgery antiforgery, - IOptionsMonitor options, - ILoggerFactory loggerFactory, - UrlEncoder encoder, - IBankIdUiStateProtector uiStateProtector, - IBankIdUiOptionsProtector uiOptionsProtector, - IBankIdUiResultProtector uiResultProtector, - IBankIdEventTrigger bankIdEventTrigger, - IBankIdSupportedDeviceDetector bankIdSupportedDeviceDetector, - IBankIdUiOptionsCookieManager uiOptionsCookieManager, - IEnumerable bankIdClaimsTransformers) - : base(options, loggerFactory, encoder) - { - _httpContextAccessor = httpContextAccessor; - _antiforgery = antiforgery; - _uiStateProtector = uiStateProtector; - _uiOptionsProtector = uiOptionsProtector; - _uiResultProtector = uiResultProtector; - _bankIdEventTrigger = bankIdEventTrigger; - _bankIdSupportedDeviceDetector = bankIdSupportedDeviceDetector; - _uiOptionsCookieManager = uiOptionsCookieManager; - _bankIdClaimsTransformers = bankIdClaimsTransformers.ToList(); - } + private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor; + private readonly IAntiforgery _antiforgery = antiforgery; + private readonly IStateStorage _stateStorage = stateStorage; + private readonly IBankIdDataStateProtector _uiResultProtector = uiResultProtector; + private readonly IBankIdEventTrigger _bankIdEventTrigger = bankIdEventTrigger; + private readonly IBankIdSupportedDeviceDetector _bankIdSupportedDeviceDetector = bankIdSupportedDeviceDetector; + private readonly IBankIdUiOptionsCookieManager _uiOptionsCookieManager = uiOptionsCookieManager; + private readonly List _bankIdClaimsTransformers = bankIdClaimsTransformers.ToList(); protected override async Task HandleRemoteAuthenticateAsync() { var detectedDevice = _bankIdSupportedDeviceDetector.Detect(); - var state = GetStateFromCookie(); + if (!Request.Cookies.TryGetValue(BankIdConstants.StateKeyCookieName, out var stateKeyString)) + { + return await HandleRemoteAuthenticateFail(BankIdConstants.ErrorMessages.InvalidStateCookie, detectedDevice); + } + + var stateKey = new StateKey(stateKeyString); + var state = await GetStateFromCookie(stateKey); if (state == null) { return await HandleRemoteAuthenticateFail(BankIdConstants.ErrorMessages.InvalidStateCookie, detectedDevice); } - DeleteStateCookie(); + await DeleteCookies(stateKey); if (!Request.HasFormContentType) { @@ -159,7 +148,7 @@ private async Task> GetClaims(BankIdUiResult uiAuthResult) protected override async Task HandleChallengeAsync(AuthenticationProperties properties) { - AppendStateCookie(properties); + await AppendStateCookie(properties); var uiOptions = new BankIdUiOptions( Options.BankIdCertificatePolicies, @@ -197,9 +186,9 @@ private string GetInitUiUrl(BankIdUiOptions uiOptions) return $"{authUrl}{queryBuilder.ToQueryString()}"; } - private void AppendStateCookie(AuthenticationProperties properties) + private async Task AppendStateCookie(AuthenticationProperties properties) { - Validators.ThrowIfNullOrWhitespace(Options.StateCookie.Name, StateCookieNameParameterName); + Validators.ThrowIfNullOrWhitespace(Options.StateCookie.Name, BankIdConstants.StateKeyCookieName); if (Options.TimeProvider == null) { @@ -207,35 +196,28 @@ private void AppendStateCookie(AuthenticationProperties properties) } var state = new BankIdUiAuthState(properties); - var cookieOptions = Options.StateCookie.Build(Context, Options.TimeProvider.GetUtcNow()); - var cookieValue = _uiStateProtector.Protect(state); + var stateKey = await _stateStorage.SetAsync(state); - Response.Cookies.Append(Options.StateCookie.Name, cookieValue, cookieOptions); + var cookieOptions = Options.StateCookie.Build(Context, Options.TimeProvider.GetUtcNow()); + Response.Cookies.Append(Options.StateCookie.Name, stateKey, cookieOptions); } - private BankIdUiAuthState? GetStateFromCookie() + private Task GetStateFromCookie(StateKey stateKey) { - Validators.ThrowIfNullOrWhitespace(Options.StateCookie.Name, StateCookieNameParameterName); - - var protectedState = Request.Cookies[Options.StateCookie.Name]; - if (string.IsNullOrEmpty(protectedState)) - { - return null; - } - - return _uiStateProtector.Unprotect(protectedState) as BankIdUiAuthState; + return string.IsNullOrEmpty(stateKey) + ? Task.FromResult(null) + : _stateStorage.GetAsync(new(stateKey)); } - private void DeleteStateCookie() + private async Task DeleteCookies(StateKey stateKey) { - Validators.ThrowIfNullOrWhitespace(Options.StateCookie.Name, StateCookieNameParameterName); + Validators.ThrowIfNullOrWhitespace(Options.StateCookie.Name, BankIdConstants.StateKeyCookieName); if (Options.TimeProvider == null) { throw new InvalidOperationException(BankIdConstants.ErrorMessages.TimeProviderNotSet); } - var cookieOptions = Options.StateCookie.Build(Context, Options.TimeProvider.GetUtcNow()); - Response.Cookies.Delete(Options.StateCookie.Name, cookieOptions); + _ = await _stateStorage.RemoveAsync(stateKey); } } diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Auth/BankIdAuthOptions.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Auth/BankIdAuthOptions.cs index 9221561c..16bbb511 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Auth/BankIdAuthOptions.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Auth/BankIdAuthOptions.cs @@ -45,11 +45,12 @@ public class BankIdAuthOptions : RemoteAuthenticationOptions private CookieBuilder _stateCookieBuilder = new() { - Name = BankIdConstants.DefaultStateCookieName, + Name = BankIdConstants.StateKeyCookieName, SecurePolicy = CookieSecurePolicy.SameAsRequest, HttpOnly = true, SameSite = SameSiteMode.Lax, - IsEssential = true + IsEssential = true, + MaxAge = TimeSpan.FromMinutes(5) }; public CookieBuilder StateCookie diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Auth/IBankIdAuthBuilderExtensions.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Auth/IBankIdAuthBuilderExtensions.cs index c89bc017..84a2251b 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Auth/IBankIdAuthBuilderExtensions.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Auth/IBankIdAuthBuilderExtensions.cs @@ -1,10 +1,12 @@ using ActiveLogin.Authentication.BankId.AspNetCore.ClaimsTransformation; +using ActiveLogin.Authentication.BankId.Core; using ActiveLogin.Authentication.BankId.Core.Requirements; using ActiveLogin.Authentication.BankId.Core.UserData; using Microsoft.Extensions.DependencyInjection; namespace ActiveLogin.Authentication.BankId.AspNetCore.Auth; + public static class IBankIdAuthBuilderExtensions { /// @@ -182,4 +184,24 @@ internal static IBankIdAuthBuilder AddScheme(this IBankIdAuthBuilder builder, st return builder; } + + public static IBankIdAuthBuilder AddStateStorage(this IBankIdAuthBuilder builder, T storage) + where T : class, IStateStorage + { + builder.Services.AddSingleton(storage); + return builder; + } + + public static IBankIdAuthBuilder AddStateStorage(this IBankIdAuthBuilder builder) + where T : class, IStateStorage + { + builder.Services.AddSingleton(); + return builder; + } + + public static IBankIdAuthBuilder AddStateStorage(this IBankIdAuthBuilder builder, Func factory) + { + builder.Services.AddSingleton(factory); + return builder; + } } diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/BankIdCommonConfiguration.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/BankIdCommonConfiguration.cs index 036039e4..3715743f 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/BankIdCommonConfiguration.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/BankIdCommonConfiguration.cs @@ -20,21 +20,24 @@ internal static class BankIdCommonConfiguration public static void AddDefaultServices(IServiceCollection services) { - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); - services.AddTransient(); + services.AddSingleton(); - services.AddTransient(); - - services.AddTransient(); + services.AddSingleton, BankIdUiAuthProtector>(); + services.AddSingleton, BankIdUiSignProtector>(); + services.AddSingleton, BankIdUiPaymentProtector>(); + services.AddSingleton, BankIdUiOptionsProtector>(); + services.AddSingleton, BankIdUiResultProtector>(); + services.AddSingleton, BankIdQrStartStateProtector>(); + services.AddSingleton, BankIdUiOrderRefProtector>(); services.AddHttpContextAccessor(); services.AddTransient(); services.AddDefaultDeviceData(); - } public static (string name, string version) GetActiveLoginInfo() diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/BankIdConstants.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/BankIdConstants.cs index 561448de..5ace1524 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/BankIdConstants.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/BankIdConstants.cs @@ -1,3 +1,4 @@ +using System.Runtime.Serialization; using System.Text.Json; namespace ActiveLogin.Authentication.BankId.AspNetCore; @@ -13,9 +14,10 @@ internal static class BankIdConstants public static readonly TimeSpan UiOptionsCookieLifeTime = TimeSpan.FromMinutes(10); public const string DefaultCancelUrl = "/"; - public const string DefaultStateCookieName = "__ActiveLogin.BankIdUiState"; + public const string StateKeyCookieName = "__ActiveLogin.BankIdStateKey"; public const string DefaultDeviceDataCookieName = "__ActiveLogin.BankIdDeviceData"; public const string DefaultUiOptionsCookieNamePrefix = "__ActiveLogin.BankId.UiOptions_"; + public const string StateKeyPrefix = "__ActiveLogin.BankIdState_"; public const string LocalizationResourcesPath = "Resources"; @@ -58,6 +60,10 @@ public static class ErrorMessages private const string CouldNotGetUrlForPrefix = "Could not get URL for"; public static string CouldNotGetUrlFor(string controller, string action) => $"{CouldNotGetUrlForPrefix} {controller}.{action}"; + + public const string InvalidState = "Invalid state"; + + public const string StateNotFound = "State not found"; } public static class LocalizationKeys diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/CookieStateStorage.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/CookieStateStorage.cs new file mode 100644 index 00000000..1fa12cda --- /dev/null +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/CookieStateStorage.cs @@ -0,0 +1,105 @@ +using System.Diagnostics.CodeAnalysis; + +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; + +using ActiveLogin.Authentication.BankId.Core; +using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; + +namespace ActiveLogin.Authentication.BankId.AspNetCore; + +/// +/// State storage using cookies. +/// Requires IServiceProvider to dynamically resolve BankIdDataStateProtector. +/// +/// +internal class CookieStateStorage( + IServiceProvider serviceProvider +) : IStateStorage +{ + internal static string StateCookieName(StateKey stateKey) => $"{BankIdConstants.StateKeyPrefix}{stateKey}"; + + private readonly IHttpContextAccessor httpContextAccessor = serviceProvider.GetRequiredService(); + private readonly ILogger logger = serviceProvider.GetRequiredService>(); + + private const int MaxCookieSize = 4096; // 4KB + + public Task GetAsync(StateKey key) + { + var httpContext = httpContextAccessor.HttpContext ?? throw new InvalidOperationException(BankIdConstants.ErrorMessages.CouldNotAccessHttpContext); + if (httpContext.Request.Cookies.TryGetValue(StateCookieName(key), out var value) && !string.IsNullOrEmpty(value)) + { + var deserializedValue = Deserialize(value); + return Task.FromResult(deserializedValue); + } + + return Task.FromResult(default); + } + + public Task SetAsync(T value) + { + var stateKey = StateKey.New(); + + var key = Set(stateKey, value); + return Task.FromResult(key); + } + + public async Task RemoveAsync(StateKey key) + { + var httpContext = httpContextAccessor.HttpContext ?? throw new InvalidOperationException(BankIdConstants.ErrorMessages.CouldNotAccessHttpContext); + var cookieName = StateCookieName(key); + httpContext.Response.Cookies.Delete(BankIdConstants.StateKeyCookieName); + if (httpContext.Request.Cookies.TryGetValue(cookieName, out var value) && !string.IsNullOrEmpty(value)) + { + var deserializedValue = Deserialize(value); + httpContext.Response.Cookies.Delete(cookieName); + return await Task.FromResult(deserializedValue); + } + + throw new InvalidOperationException("Could not find state in cookies to remove."); + } + + private StateKey Set(StateKey key, T value) + { + var httpContext = httpContextAccessor.HttpContext ?? throw new InvalidOperationException(BankIdConstants.ErrorMessages.CouldNotAccessHttpContext); + var cookie = Serialize(value); + + if (cookie.Length > MaxCookieSize) + { + logger.LogWarning("Cookie size exceeds 4096 bytes. Consider using a different storage method."); + } + + var cookieOptions = new CookieOptions + { + HttpOnly = true, + Secure = httpContext.Request.IsHttps, + SameSite = SameSiteMode.Strict, + Path = "/", + IsEssential = true, + MaxAge = TimeSpan.FromMinutes(5) + }; + + httpContext.Response.Cookies.Append(StateCookieName(key), cookie, cookieOptions); + + return key; + } + + private string Serialize(T value) + { + ArgumentNullException.ThrowIfNull(value); + + return serviceProvider + .GetRequiredService>() + .Protect(value); + } + + private T Deserialize(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + + return serviceProvider + .GetRequiredService>() + .Unprotect(value); + } +} diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Cookies/BankIdUiOptionsCookieManager.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Cookies/BankIdUiOptionsCookieManager.cs index 1b293a29..3f0111dd 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Cookies/BankIdUiOptionsCookieManager.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Cookies/BankIdUiOptionsCookieManager.cs @@ -6,7 +6,7 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.Cookies; internal class BankIdUiOptionsCookieManager( IHttpContextAccessor httpContextAccessor, - IBankIdUiOptionsProtector uiOptionsProtector + IBankIdDataStateProtector uiOptionsProtector ) : IBankIdUiOptionsCookieManager { diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdDataStateProtector.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdDataStateProtector.cs index 507b084c..e624550a 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdDataStateProtector.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdDataStateProtector.cs @@ -7,7 +7,7 @@ internal abstract class BankIdDataStateProtector { private const string ProtectorVersion = "v2"; - private readonly ISecureDataFormat _secureDataFormat; + private readonly SecureDataFormat _secureDataFormat; protected BankIdDataStateProtector( IDataProtectionProvider dataProtectionProvider, @@ -32,13 +32,6 @@ public virtual string Protect(TModel model) public virtual TModel Unprotect(string protectedModel) { - var unprotected = _secureDataFormat.Unprotect(protectedModel); - - if (unprotected == null) - { - throw new Exception(BankIdConstants.ErrorMessages.CouldNotUnprotect(typeof(TModel).Name)); - } - - return unprotected; + return _secureDataFormat.Unprotect(protectedModel) ?? throw new Exception(BankIdConstants.ErrorMessages.CouldNotUnprotect(typeof(TModel).Name)); } } diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdDeviceDataProtector.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdDeviceDataProtector.cs index e13172d7..61ab7c32 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdDeviceDataProtector.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdDeviceDataProtector.cs @@ -5,10 +5,9 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; -internal class BankIdDeviceDataProtector : BankIdDataStateProtector, IBankIdDeviceDataProtector +internal class BankIdDeviceDataProtector( + IDataProtectionProvider dataProtectionProvider +) : BankIdDataStateProtector(dataProtectionProvider, new BankIdDeviceDataStateSerializer()), + IBankIdDataStateProtector { - public BankIdDeviceDataProtector(IDataProtectionProvider dataProtectionProvider) - : base(dataProtectionProvider, new BankIdDeviceDataStateSerializer()) - { - } } diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdQrStartStateProtector.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdQrStartStateProtector.cs index a76ac755..de30bb45 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdQrStartStateProtector.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdQrStartStateProtector.cs @@ -5,10 +5,9 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; -internal class BankIdQrStartStateProtector : BankIdDataStateProtector, IBankIdQrStartStateProtector +internal class BankIdQrStartStateProtector( + IDataProtectionProvider dataProtectionProvider +) : BankIdDataStateProtector(dataProtectionProvider, new BankIdQrStartStateSerializer()), + IBankIdDataStateProtector { - public BankIdQrStartStateProtector(IDataProtectionProvider dataProtectionProvider) - : base(dataProtectionProvider, new BankIdQrStartStateSerializer()) - { - } } diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiAuthProtector.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiAuthProtector.cs new file mode 100644 index 00000000..235eaa4f --- /dev/null +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiAuthProtector.cs @@ -0,0 +1,12 @@ +using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection.Serialization; + +using Microsoft.AspNetCore.DataProtection; + +namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; + +internal class BankIdUiAuthProtector( + IDataProtectionProvider dataProtectionProvider +) : BankIdDataStateProtector(dataProtectionProvider, new BankIdUiAuthStateSerializer()), + IBankIdDataStateProtector +{ +} diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiOptionsProtector.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiOptionsProtector.cs index 16926327..8eef1e3f 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiOptionsProtector.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiOptionsProtector.cs @@ -5,10 +5,9 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; -internal class BankIdUiOptionsProtector : BankIdDataStateProtector, IBankIdUiOptionsProtector +internal class BankIdUiOptionsProtector( + IDataProtectionProvider dataProtectionProvider +) : BankIdDataStateProtector(dataProtectionProvider, new BankIdUiOptionsSerializer()), + IBankIdDataStateProtector { - public BankIdUiOptionsProtector(IDataProtectionProvider dataProtectionProvider) - : base(dataProtectionProvider, new BankIdUiOptionsSerializer()) - { - } } diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiOrderRefProtector.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiOrderRefProtector.cs index a5cb79a0..256fc511 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiOrderRefProtector.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiOrderRefProtector.cs @@ -5,10 +5,9 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; -internal class BankIdUiOrderRefProtector : BankIdDataStateProtector, IBankIdUiOrderRefProtector +internal class BankIdUiOrderRefProtector( + IDataProtectionProvider dataProtectionProvider +) : BankIdDataStateProtector(dataProtectionProvider, new BankIdUiOrderRefSerializer()), + IBankIdDataStateProtector { - public BankIdUiOrderRefProtector(IDataProtectionProvider dataProtectionProvider) - : base(dataProtectionProvider, new BankIdUiOrderRefSerializer()) - { - } } diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiPaymentProtector.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiPaymentProtector.cs new file mode 100644 index 00000000..cb05334d --- /dev/null +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiPaymentProtector.cs @@ -0,0 +1,12 @@ +using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection.Serialization; + +using Microsoft.AspNetCore.DataProtection; + +namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; + +internal class BankIdUiPaymentProtector( + IDataProtectionProvider dataProtectionProvider +) : BankIdDataStateProtector(dataProtectionProvider, new BankIdUiPaymentStateSerializer()), + IBankIdDataStateProtector +{ +} diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiResultProtector.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiResultProtector.cs index cd331fd0..6d0f3c87 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiResultProtector.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiResultProtector.cs @@ -5,10 +5,9 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; -internal class BankIdUiResultProtector : BankIdDataStateProtector, IBankIdUiResultProtector +internal class BankIdUiResultProtector( + IDataProtectionProvider dataProtectionProvider +) : BankIdDataStateProtector(dataProtectionProvider, new BankIdUiResultSerializer()), + IBankIdDataStateProtector { - public BankIdUiResultProtector(IDataProtectionProvider dataProtectionProvider) - : base(dataProtectionProvider, new BankIdUiResultSerializer()) - { - } } diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiSignProtector.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiSignProtector.cs new file mode 100644 index 00000000..24254de7 --- /dev/null +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiSignProtector.cs @@ -0,0 +1,13 @@ +using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection.Serialization; +using ActiveLogin.Authentication.BankId.AspNetCore.Sign; + +using Microsoft.AspNetCore.DataProtection; + +namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; + +internal class BankIdUiSignProtector( + IDataProtectionProvider dataProtectionProvider +) : BankIdDataStateProtector(dataProtectionProvider, new BankIdUiSignStateSerializer()), + IBankIdDataStateProtector +{ +} diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiStateProtector.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiStateProtector.cs deleted file mode 100644 index b4fa547f..00000000 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/BankIdUiStateProtector.cs +++ /dev/null @@ -1,14 +0,0 @@ -using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection.Serialization; -using ActiveLogin.Authentication.BankId.AspNetCore.Models; - -using Microsoft.AspNetCore.DataProtection; - -namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; - -internal class BankIdUiStateProtector : BankIdDataStateProtector, IBankIdUiStateProtector -{ - public BankIdUiStateProtector(IDataProtectionProvider dataProtectionProvider) - : base(dataProtectionProvider, new BankIdUiStateSerializer()) - { - } -} diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdDataStateProtector.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdDataStateProtector.cs index 3760e7db..e30b1a87 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdDataStateProtector.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdDataStateProtector.cs @@ -1,6 +1,6 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; -internal interface IBankIdDataStateProtector +public interface IBankIdDataStateProtector { string Protect(TModel model); TModel Unprotect(string protectedModel); diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdDeviceDataProtector.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdDeviceDataProtector.cs deleted file mode 100644 index f3c7f039..00000000 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdDeviceDataProtector.cs +++ /dev/null @@ -1,10 +0,0 @@ -using ActiveLogin.Authentication.BankId.AspNetCore.Models; -using ActiveLogin.Authentication.BankId.AspNetCore.UserContext.Device.State; - -namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; - -public interface IBankIdDeviceDataProtector -{ - string Protect(DeviceDataState deviceDataState); - DeviceDataState Unprotect(string protectedDeviceDataState); -} diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdQrStartStateProtector.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdQrStartStateProtector.cs deleted file mode 100644 index ab37cf1c..00000000 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdQrStartStateProtector.cs +++ /dev/null @@ -1,9 +0,0 @@ -using ActiveLogin.Authentication.BankId.Core.Models; - -namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; - -public interface IBankIdQrStartStateProtector -{ - string Protect(BankIdQrStartState qrStartState); - BankIdQrStartState Unprotect(string protectedQrStartState); -} diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdUiOptionsProtector.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdUiOptionsProtector.cs deleted file mode 100644 index c404562e..00000000 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdUiOptionsProtector.cs +++ /dev/null @@ -1,9 +0,0 @@ -using ActiveLogin.Authentication.BankId.AspNetCore.Models; - -namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; - -public interface IBankIdUiOptionsProtector -{ - string Protect(BankIdUiOptions uiOptions); - BankIdUiOptions Unprotect(string protectedUiOptions); -} diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdUiOrderRefProtector.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdUiOrderRefProtector.cs deleted file mode 100644 index c2f5a4f2..00000000 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdUiOrderRefProtector.cs +++ /dev/null @@ -1,9 +0,0 @@ -using ActiveLogin.Authentication.BankId.AspNetCore.Areas.ActiveLogin.Models; - -namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; - -public interface IBankIdUiOrderRefProtector -{ - string Protect(BankIdUiOrderRef orderRef); - BankIdUiOrderRef Unprotect(string protectedOrderRef); -} diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdUiResultProtector.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdUiResultProtector.cs deleted file mode 100644 index 1a8cc657..00000000 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdUiResultProtector.cs +++ /dev/null @@ -1,9 +0,0 @@ -using ActiveLogin.Authentication.BankId.AspNetCore.Models; - -namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; - -public interface IBankIdUiResultProtector -{ - string Protect(BankIdUiResult uiAuthResult); - BankIdUiResult Unprotect(string protectedUiAuthResult); -} diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdUiStateProtector.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdUiStateProtector.cs deleted file mode 100644 index fdcad259..00000000 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/IBankIdUiStateProtector.cs +++ /dev/null @@ -1,9 +0,0 @@ -using ActiveLogin.Authentication.BankId.AspNetCore.Models; - -namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; - -public interface IBankIdUiStateProtector -{ - string Protect(BankIdUiState state); - BankIdUiState Unprotect(string protectedState); -} diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiAuthStateSerializer.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiAuthStateSerializer.cs new file mode 100644 index 00000000..26ad9db1 --- /dev/null +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiAuthStateSerializer.cs @@ -0,0 +1,21 @@ +using ActiveLogin.Authentication.BankId.AspNetCore.Auth; + +using Microsoft.AspNetCore.Authentication; + +namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection.Serialization; + +internal class BankIdUiAuthStateSerializer : BankIdDataSerializer +{ + protected override void Write(BinaryWriter writer, BankIdUiAuthState model) + { + PropertiesSerializer.Default.Write(writer, model.AuthenticationProperties); + } + + protected override BankIdUiAuthState Read(BinaryReader reader) + { + var authenticationProperties = PropertiesSerializer.Default.Read(reader); + return authenticationProperties == null + ? throw new Exception(BankIdConstants.ErrorMessages.CouldNotDeserialize(nameof(AuthenticationProperties))) + : new BankIdUiAuthState(authenticationProperties); + } +} diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiOptionsSerializer.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiOptionsSerializer.cs index b1948368..4daf2d33 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiOptionsSerializer.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiOptionsSerializer.cs @@ -16,7 +16,7 @@ protected override void Write(BinaryWriter writer, BankIdUiOptions model) writer.Write(model.RequireMrtd); writer.Write(model.ReturnRisk); writer.Write(model.CancelReturnUrl); - writer.Write(model.StateCookieName); + writer.Write(model.StateKeyCookieName); writer.Write(model.CardReader?.ToString() ?? string.Empty); } diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiPaymentStateSerializer.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiPaymentStateSerializer.cs new file mode 100644 index 00000000..7a40d8e5 --- /dev/null +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiPaymentStateSerializer.cs @@ -0,0 +1,161 @@ +using ActiveLogin.Authentication.BankId.Api.Models; +using ActiveLogin.Authentication.BankId.AspNetCore.Payment; +using ActiveLogin.Authentication.BankId.Core.CertificatePolicies; +using ActiveLogin.Authentication.BankId.Core.Payment; +using ActiveLogin.Identity.Swedish; + +namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection.Serialization; + +internal class BankIdUiPaymentStateSerializer : BankIdDataSerializer +{ + protected override void Write(BinaryWriter writer, BankIdUiPaymentState paymentState) + { + writer.Write(paymentState.ConfigKey); + + writer.Write(paymentState.BankIdPaymentProperties.TransactionType.ToString()); + writer.Write(paymentState.BankIdPaymentProperties.RecipientName.ToString()); + + writer.Write(paymentState.BankIdPaymentProperties.Money == null); + writer.Write(paymentState.BankIdPaymentProperties.Money?.Amount ?? string.Empty); + writer.Write(paymentState.BankIdPaymentProperties.Money?.Currency ?? string.Empty); + + writer.Write(paymentState.BankIdPaymentProperties.RiskWarning == null); + writer.Write(paymentState.BankIdPaymentProperties.RiskWarning ?? string.Empty); + + writer.Write(paymentState.BankIdPaymentProperties.RiskFlags == null); + var riskFlagsStr = paymentState.BankIdPaymentProperties.RiskFlags != null ? string.Join(",", paymentState.BankIdPaymentProperties.RiskFlags.Select(r => r.ToString())) : string.Empty; + writer.Write(riskFlagsStr); + + writer.Write(paymentState.BankIdPaymentProperties.UserVisibleData == null); + writer.Write(paymentState.BankIdPaymentProperties.UserVisibleData ?? string.Empty); + + writer.Write(paymentState.BankIdPaymentProperties.UserVisibleDataFormat == null); + writer.Write(paymentState.BankIdPaymentProperties.UserVisibleDataFormat ?? string.Empty); + + writer.Write(paymentState.BankIdPaymentProperties.UserNonVisibleData?.Length ?? -1); + writer.Write(paymentState.BankIdPaymentProperties.UserNonVisibleData ?? Array.Empty()); + + writer.Write(paymentState.BankIdPaymentProperties.RequiredPersonalIdentityNumber == null); + writer.Write(paymentState.BankIdPaymentProperties.RequiredPersonalIdentityNumber?.To12DigitString() ?? string.Empty); + + writer.Write(paymentState.BankIdPaymentProperties.RequireMrtd == null); + writer.Write(paymentState.BankIdPaymentProperties.RequireMrtd.GetValueOrDefault()); + + writer.Write(paymentState.BankIdPaymentProperties.RequirePinCode == null); + writer.Write(paymentState.BankIdPaymentProperties.RequirePinCode.GetValueOrDefault()); + + writer.Write(paymentState.BankIdPaymentProperties.BankIdCertificatePolicies?.Count ?? 0); + if (paymentState.BankIdPaymentProperties.BankIdCertificatePolicies?.Count > 0) + { + foreach (var policy in paymentState.BankIdPaymentProperties.BankIdCertificatePolicies) + { + writer.Write(Convert.ToInt32(policy)); + } + } + + writer.Write(paymentState.BankIdPaymentProperties.CardReader == null); + writer.Write(paymentState.BankIdPaymentProperties.CardReader.HasValue ? Convert.ToInt32(paymentState.BankIdPaymentProperties.CardReader) : -1); + + writer.Write(paymentState.BankIdPaymentProperties.Items.Count); + foreach (var item in paymentState.BankIdPaymentProperties.Items) + { + writer.Write(item.Key ?? string.Empty); + writer.Write(item.Value ?? string.Empty); + } + } + + protected override BankIdUiPaymentState Read(BinaryReader reader) + { + var configKey = reader.ReadString(); + + var transactionTypeStr = reader.ReadString(); + var transactionType = (TransactionType) Enum.Parse(typeof(TransactionType), transactionTypeStr); + var recipientName = reader.ReadString(); + + var moneyIsNull = reader.ReadBoolean(); + var moneyAmount = reader.ReadString(); + var moneyCurrency = reader.ReadString(); + var money = moneyIsNull ? null : new Money(moneyAmount, moneyCurrency); + + var riskFlagsIsNull = reader.ReadBoolean(); + var riskFlagsStr = reader.ReadString(); + var riskFlags = riskFlagsIsNull ? null : riskFlagsStr.Split(",").Select(f => (RiskFlags) Enum.Parse(typeof(RiskFlags), f)); + + var riskWarningIsNull = reader.ReadBoolean(); + var riskWarning = reader.ReadString(); + if (riskWarningIsNull) + { + riskWarning = null; + } + + var userVisibleDataIsNull = reader.ReadBoolean(); + var userVisibleData = reader.ReadString(); + if (userVisibleDataIsNull) { + userVisibleData = null; + } + + var userVisibleDataFormatIsNull = reader.ReadBoolean(); + var userVisibleDataFormat = reader.ReadString(); + if (userVisibleDataFormatIsNull) + { + userVisibleDataFormat = null; + } + + var userNonVisibleDataLength = reader.ReadInt32(); + var userNonVisibleData = reader.ReadBytes(userNonVisibleDataLength == -1 ? 0 : userNonVisibleDataLength); + if (userNonVisibleDataLength == -1) + { + userNonVisibleData = null; + } + + var requiredPersonalIdentityNumberIsNull = reader.ReadBoolean(); + var requiredPersonalIdentityNumber = reader.ReadString(); + if (requiredPersonalIdentityNumberIsNull) + { + requiredPersonalIdentityNumber = null; + } + + var requireMrtdIsNull = reader.ReadBoolean(); + var requireMrtd = reader.ReadBoolean(); + + var requirePinCodeIsNull = reader.ReadBoolean(); + var requirePinCode = reader.ReadBoolean(); + + var bankIdCertificatePoliciesCount = reader.ReadInt32(); + var bankIdCertificatePolicies = new List(); + for (var index = 0; index < bankIdCertificatePoliciesCount; index++) + { + bankIdCertificatePolicies.Add((BankIdCertificatePolicy)reader.ReadInt32()); + } + + var cardReaderIsNull = reader.ReadBoolean(); + var cardReaderValue = reader.ReadInt32(); + CardReader? cardReader = cardReaderIsNull ? null : (CardReader)cardReaderValue; + + var count = reader.ReadInt32(); + var items = new Dictionary(count); + + for (var index = 0; index != count; ++index) + { + var key = reader.ReadString(); + var value = reader.ReadString(); + items.Add(key, value); + } + + var bankIdPaymentProperties = new BankIdPaymentProperties(transactionType, recipientName) + { + Money = money, + RiskFlags = riskFlags, + UserVisibleData = userVisibleData, + UserVisibleDataFormat = userVisibleDataFormat, + UserNonVisibleData = userNonVisibleData, + RequiredPersonalIdentityNumber = requiredPersonalIdentityNumber != null ? PersonalIdentityNumber.Parse(requiredPersonalIdentityNumber) : null, + RequireMrtd = requireMrtdIsNull ? null : requireMrtd, + RequirePinCode = requirePinCodeIsNull ? null : requirePinCode, + BankIdCertificatePolicies = bankIdCertificatePolicies, + CardReader = cardReader, + Items = items + }; + return new BankIdUiPaymentState(configKey, bankIdPaymentProperties); + } +} diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiSignStateSerializer.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiSignStateSerializer.cs new file mode 100644 index 00000000..20e8d4c5 --- /dev/null +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiSignStateSerializer.cs @@ -0,0 +1,117 @@ +using ActiveLogin.Authentication.BankId.Api.Models; +using ActiveLogin.Authentication.BankId.AspNetCore.Sign; +using ActiveLogin.Authentication.BankId.Core.CertificatePolicies; +using ActiveLogin.Identity.Swedish; + +namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection.Serialization; + +internal class BankIdUiSignStateSerializer : BankIdDataSerializer +{ + protected override void Write(BinaryWriter writer, BankIdUiSignState signState) + { + writer.Write(signState.ConfigKey); + + writer.Write(signState.BankIdSignProperties.UserVisibleData); + + writer.Write(signState.BankIdSignProperties.UserVisibleDataFormat == null); + writer.Write(signState.BankIdSignProperties.UserVisibleDataFormat ?? string.Empty); + + writer.Write(signState.BankIdSignProperties.UserNonVisibleData?.Length ?? -1); + writer.Write(signState.BankIdSignProperties.UserNonVisibleData ?? Array.Empty()); + + writer.Write(signState.BankIdSignProperties.RequiredPersonalIdentityNumber == null); + writer.Write(signState.BankIdSignProperties.RequiredPersonalIdentityNumber?.To12DigitString() ?? string.Empty); + + writer.Write(signState.BankIdSignProperties.RequireMrtd == null); + writer.Write(signState.BankIdSignProperties.RequireMrtd.GetValueOrDefault()); + + writer.Write(signState.BankIdSignProperties.RequirePinCode == null); + writer.Write(signState.BankIdSignProperties.RequirePinCode.GetValueOrDefault()); + + writer.Write(signState.BankIdSignProperties.BankIdCertificatePolicies?.Count ?? 0); + if (signState.BankIdSignProperties.BankIdCertificatePolicies?.Count > 0) + { + foreach (var policy in signState.BankIdSignProperties.BankIdCertificatePolicies) + { + writer.Write(Convert.ToInt32(policy)); + } + } + + writer.Write(signState.BankIdSignProperties.CardReader == null); + writer.Write(signState.BankIdSignProperties.CardReader.HasValue ? Convert.ToInt32(signState.BankIdSignProperties.CardReader) : -1); + + writer.Write(signState.BankIdSignProperties.Items.Count); + foreach (var item in signState.BankIdSignProperties.Items) + { + writer.Write(item.Key ?? string.Empty); + writer.Write(item.Value ?? string.Empty); + } + } + + protected override BankIdUiSignState Read(BinaryReader reader) + { + var configKey = reader.ReadString(); + var userVisibleData = reader.ReadString(); + + var userVisibleDataFormatIsNull = reader.ReadBoolean(); + var userVisibleDataFormat = reader.ReadString(); + if (userVisibleDataFormatIsNull) + { + userVisibleDataFormat = null; + } + + var userNonVisibleDataLength = reader.ReadInt32(); + var userNonVisibleData = reader.ReadBytes(userNonVisibleDataLength == -1 ? 0 : userNonVisibleDataLength); + if (userNonVisibleDataLength == -1) + { + userNonVisibleData = null; + } + + var requiredPersonalIdentityNumberIsNull = reader.ReadBoolean(); + var requiredPersonalIdentityNumber = reader.ReadString(); + if (requiredPersonalIdentityNumberIsNull) + { + requiredPersonalIdentityNumber = null; + } + + var requireMrtdIsNull = reader.ReadBoolean(); + var requireMrtd = reader.ReadBoolean(); + + var requirePinCodeIsNull = reader.ReadBoolean(); + var requirePinCode = reader.ReadBoolean(); + + var bankIdCertificatePoliciesCount = reader.ReadInt32(); + var bankIdCertificatePolicies = new List(); + for(var index = 0; index < bankIdCertificatePoliciesCount; index++) + { + bankIdCertificatePolicies.Add((BankIdCertificatePolicy)reader.ReadInt32()); + } + + var cardReaderIsNull = reader.ReadBoolean(); + var cardReaderValue = reader.ReadInt32(); + CardReader? cardReader = cardReaderIsNull ? null : (CardReader)cardReaderValue; + + var count = reader.ReadInt32(); + var items = new Dictionary(count); + + for (var index = 0; index != count; ++index) + { + var key = reader.ReadString(); + var value = reader.ReadString(); + items.Add(key, value); + } + + var bankIdSignProperties = new BankIdSignProperties(userVisibleData) + { + UserVisibleDataFormat = userVisibleDataFormat, + UserNonVisibleData = userNonVisibleData, + RequiredPersonalIdentityNumber = requiredPersonalIdentityNumber != null ? PersonalIdentityNumber.Parse(requiredPersonalIdentityNumber) : null, + RequireMrtd = requireMrtdIsNull ? null : requireMrtd, + RequirePinCode = requirePinCodeIsNull ? null : requirePinCode, + BankIdCertificatePolicies = bankIdCertificatePolicies, + CardReader = cardReader, + Items = items + }; + return new BankIdUiSignState(configKey, bankIdSignProperties); + } +} diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiStateSerializer.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiStateSerializer.cs deleted file mode 100644 index 85ddc13e..00000000 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/DataProtection/Serialization/BankIdUiStateSerializer.cs +++ /dev/null @@ -1,308 +0,0 @@ -using System.Linq; - -using ActiveLogin.Authentication.BankId.Api.Models; -using ActiveLogin.Authentication.BankId.AspNetCore.Auth; -using ActiveLogin.Authentication.BankId.AspNetCore.Models; -using ActiveLogin.Authentication.BankId.AspNetCore.Payment; -using ActiveLogin.Authentication.BankId.AspNetCore.Sign; -using ActiveLogin.Authentication.BankId.Core.CertificatePolicies; -using ActiveLogin.Authentication.BankId.Core.Payment; -using ActiveLogin.Identity.Swedish; - -using Microsoft.AspNetCore.Authentication; - -namespace ActiveLogin.Authentication.BankId.AspNetCore.DataProtection.Serialization; - -internal class BankIdUiStateSerializer : BankIdDataSerializer -{ - public BankIdUiStateSerializer() - { - } - - protected override void Write(BinaryWriter writer, BankIdUiState model) - { - writer.Write((int)model.Type); - if (model is BankIdUiAuthState authState) - { - PropertiesSerializer.Default.Write(writer, authState.AuthenticationProperties); - } - else if (model is BankIdUiSignState signState) - { - writer.Write(signState.ConfigKey); - - writer.Write(signState.BankIdSignProperties.UserVisibleData); - - writer.Write(signState.BankIdSignProperties.UserVisibleDataFormat == null); - writer.Write(signState.BankIdSignProperties.UserVisibleDataFormat ?? string.Empty); - - writer.Write(signState.BankIdSignProperties.UserNonVisibleData?.Length ?? -1); - writer.Write(signState.BankIdSignProperties.UserNonVisibleData ?? Array.Empty()); - - writer.Write(signState.BankIdSignProperties.RequiredPersonalIdentityNumber == null); - writer.Write(signState.BankIdSignProperties.RequiredPersonalIdentityNumber?.To12DigitString() ?? string.Empty); - - writer.Write(signState.BankIdSignProperties.RequireMrtd == null); - writer.Write(signState.BankIdSignProperties.RequireMrtd.GetValueOrDefault()); - - writer.Write(signState.BankIdSignProperties.RequirePinCode == null); - writer.Write(signState.BankIdSignProperties.RequirePinCode.GetValueOrDefault()); - - writer.Write(signState.BankIdSignProperties.BankIdCertificatePolicies?.Count ?? 0); - if (signState.BankIdSignProperties.BankIdCertificatePolicies?.Count > 0) - { - foreach (var policy in signState.BankIdSignProperties.BankIdCertificatePolicies) - { - writer.Write(Convert.ToInt32(policy)); - } - } - - writer.Write(signState.BankIdSignProperties.CardReader == null); - writer.Write(signState.BankIdSignProperties.CardReader.HasValue ? Convert.ToInt32(signState.BankIdSignProperties.CardReader) : -1); - - writer.Write(signState.BankIdSignProperties.Items.Count); - foreach (var item in signState.BankIdSignProperties.Items) - { - writer.Write(item.Key ?? string.Empty); - writer.Write(item.Value ?? string.Empty); - } - } - else if (model is BankIdUiPaymentState paymentState) - { - writer.Write(paymentState.ConfigKey); - - writer.Write(paymentState.BankIdPaymentProperties.TransactionType.ToString()); - writer.Write(paymentState.BankIdPaymentProperties.RecipientName.ToString()); - - writer.Write(paymentState.BankIdPaymentProperties.Money == null); - writer.Write(paymentState.BankIdPaymentProperties.Money?.Amount ?? string.Empty); - writer.Write(paymentState.BankIdPaymentProperties.Money?.Currency ?? string.Empty); - - writer.Write(paymentState.BankIdPaymentProperties.RiskWarning == null); - writer.Write(paymentState.BankIdPaymentProperties.RiskWarning ?? string.Empty); - - writer.Write(paymentState.BankIdPaymentProperties.RiskFlags == null); - var riskFlagsStr = paymentState.BankIdPaymentProperties.RiskFlags != null ? string.Join(",", paymentState.BankIdPaymentProperties.RiskFlags.Select(r => r.ToString())) : string.Empty; - writer.Write(riskFlagsStr); - - writer.Write(paymentState.BankIdPaymentProperties.UserVisibleData == null); - writer.Write(paymentState.BankIdPaymentProperties.UserVisibleData ?? string.Empty); - - writer.Write(paymentState.BankIdPaymentProperties.UserVisibleDataFormat == null); - writer.Write(paymentState.BankIdPaymentProperties.UserVisibleDataFormat ?? string.Empty); - - writer.Write(paymentState.BankIdPaymentProperties.UserNonVisibleData?.Length ?? -1); - writer.Write(paymentState.BankIdPaymentProperties.UserNonVisibleData ?? Array.Empty()); - - writer.Write(paymentState.BankIdPaymentProperties.RequiredPersonalIdentityNumber == null); - writer.Write(paymentState.BankIdPaymentProperties.RequiredPersonalIdentityNumber?.To12DigitString() ?? string.Empty); - - writer.Write(paymentState.BankIdPaymentProperties.RequireMrtd == null); - writer.Write(paymentState.BankIdPaymentProperties.RequireMrtd.GetValueOrDefault()); - - writer.Write(paymentState.BankIdPaymentProperties.RequirePinCode == null); - writer.Write(paymentState.BankIdPaymentProperties.RequirePinCode.GetValueOrDefault()); - - writer.Write(paymentState.BankIdPaymentProperties.BankIdCertificatePolicies?.Count ?? 0); - if (paymentState.BankIdPaymentProperties.BankIdCertificatePolicies?.Count > 0) - { - foreach (var policy in paymentState.BankIdPaymentProperties.BankIdCertificatePolicies) - { - writer.Write(Convert.ToInt32(policy)); - } - } - - writer.Write(paymentState.BankIdPaymentProperties.CardReader == null); - writer.Write(paymentState.BankIdPaymentProperties.CardReader.HasValue ? Convert.ToInt32(paymentState.BankIdPaymentProperties.CardReader) : -1); - - writer.Write(paymentState.BankIdPaymentProperties.Items.Count); - foreach (var item in paymentState.BankIdPaymentProperties.Items) - { - writer.Write(item.Key ?? string.Empty); - writer.Write(item.Value ?? string.Empty); - } - } - else - { - throw new ArgumentException("Invalid enum value for BankIdStateType", nameof(model.Type)); - } - } - - protected override BankIdUiState Read(BinaryReader reader) - { - var type = (BankIdStateType)reader.ReadInt32(); - if (type == BankIdStateType.Auth) - { - var authenticationProperties = PropertiesSerializer.Default.Read(reader); - if (authenticationProperties == null) - { - throw new Exception(BankIdConstants.ErrorMessages.CouldNotDeserialize(nameof(AuthenticationProperties))); - } - - return new BankIdUiAuthState(authenticationProperties); - } - else if (type == BankIdStateType.Sign) - { - var configKey = reader.ReadString(); - var userVisibleData = reader.ReadString(); - - var userVisibleDataFormatIsNull = reader.ReadBoolean(); - var userVisibleDataFormat = reader.ReadString(); - if (userVisibleDataFormatIsNull) - { - userVisibleDataFormat = null; - } - - var userNonVisibleDataLength = reader.ReadInt32(); - var userNonVisibleData = reader.ReadBytes(userNonVisibleDataLength == -1 ? 0 : userNonVisibleDataLength); - if (userNonVisibleDataLength == -1) - { - userNonVisibleData = null; - } - - var requiredPersonalIdentityNumberIsNull = reader.ReadBoolean(); - var requiredPersonalIdentityNumber = reader.ReadString(); - if (requiredPersonalIdentityNumberIsNull) - { - requiredPersonalIdentityNumber = null; - } - - var requireMrtdIsNull = reader.ReadBoolean(); - var requireMrtd = reader.ReadBoolean(); - - var requirePinCodeIsNull = reader.ReadBoolean(); - var requirePinCode = reader.ReadBoolean(); - - var bankIdCertificatePoliciesCount = reader.ReadInt32(); - var bankIdCertificatePolicies = new List(); - for(var index = 0; index < bankIdCertificatePoliciesCount; index++) - { - bankIdCertificatePolicies.Add((BankIdCertificatePolicy)reader.ReadInt32()); - } - - var cardReaderIsNull = reader.ReadBoolean(); - var cardReaderValue = reader.ReadInt32(); - CardReader? cardReader = cardReaderIsNull ? null : (CardReader)cardReaderValue; - - var count = reader.ReadInt32(); - var items = new Dictionary(count); - - for (var index = 0; index != count; ++index) - { - var key = reader.ReadString(); - var value = reader.ReadString(); - items.Add(key, value); - } - - var bankIdSignProperties = new BankIdSignProperties(userVisibleData) - { - UserVisibleDataFormat = userVisibleDataFormat, - UserNonVisibleData = userNonVisibleData, - RequiredPersonalIdentityNumber = requiredPersonalIdentityNumber != null ? PersonalIdentityNumber.Parse(requiredPersonalIdentityNumber) : null, - RequireMrtd = requireMrtdIsNull ? null : requireMrtd, - RequirePinCode = requirePinCodeIsNull ? null : requirePinCode, - BankIdCertificatePolicies = bankIdCertificatePolicies, - CardReader = cardReader, - Items = items - }; - return new BankIdUiSignState(configKey, bankIdSignProperties); - } - else if (type == BankIdStateType.Payment) - { - var configKey = reader.ReadString(); - - var transactionTypeStr = reader.ReadString(); - var transactionType = (TransactionType) Enum.Parse(typeof(TransactionType), transactionTypeStr); - var recipientName = reader.ReadString(); - - var moneyIsNull = reader.ReadBoolean(); - var moneyAmount = reader.ReadString(); - var moneyCurrency = reader.ReadString(); - var money = moneyIsNull ? null : new Money(moneyAmount, moneyCurrency); - - var riskFlagsIsNull = reader.ReadBoolean(); - var riskFlagsStr = reader.ReadString(); - var riskFlags = riskFlagsIsNull ? null : riskFlagsStr.Split(",").Select(f => (RiskFlags) Enum.Parse(typeof(RiskFlags), f)); - - var riskWarningIsNull = reader.ReadBoolean(); - var riskWarning = reader.ReadString(); - if (riskWarningIsNull) - { - riskWarning = null; - } - - var userVisibleDataIsNull = reader.ReadBoolean(); - var userVisibleData = reader.ReadString(); - if (userVisibleDataIsNull) { - userVisibleData = null; - } - - var userVisibleDataFormatIsNull = reader.ReadBoolean(); - var userVisibleDataFormat = reader.ReadString(); - if (userVisibleDataFormatIsNull) - { - userVisibleDataFormat = null; - } - - var userNonVisibleDataLength = reader.ReadInt32(); - var userNonVisibleData = reader.ReadBytes(userNonVisibleDataLength == -1 ? 0 : userNonVisibleDataLength); - if (userNonVisibleDataLength == -1) - { - userNonVisibleData = null; - } - - var requiredPersonalIdentityNumberIsNull = reader.ReadBoolean(); - var requiredPersonalIdentityNumber = reader.ReadString(); - if (requiredPersonalIdentityNumberIsNull) - { - requiredPersonalIdentityNumber = null; - } - - var requireMrtdIsNull = reader.ReadBoolean(); - var requireMrtd = reader.ReadBoolean(); - - var requirePinCodeIsNull = reader.ReadBoolean(); - var requirePinCode = reader.ReadBoolean(); - - var bankIdCertificatePoliciesCount = reader.ReadInt32(); - var bankIdCertificatePolicies = new List(); - for (var index = 0; index < bankIdCertificatePoliciesCount; index++) - { - bankIdCertificatePolicies.Add((BankIdCertificatePolicy)reader.ReadInt32()); - } - - var cardReaderIsNull = reader.ReadBoolean(); - var cardReaderValue = reader.ReadInt32(); - CardReader? cardReader = cardReaderIsNull ? null : (CardReader)cardReaderValue; - - var count = reader.ReadInt32(); - var items = new Dictionary(count); - - for (var index = 0; index != count; ++index) - { - var key = reader.ReadString(); - var value = reader.ReadString(); - items.Add(key, value); - } - - var bankIdPaymentProperties = new BankIdPaymentProperties(transactionType, recipientName) - { - Money = money, - RiskFlags = riskFlags, - UserVisibleData = userVisibleData, - UserVisibleDataFormat = userVisibleDataFormat, - UserNonVisibleData = userNonVisibleData, - RequiredPersonalIdentityNumber = requiredPersonalIdentityNumber != null ? PersonalIdentityNumber.Parse(requiredPersonalIdentityNumber) : null, - RequireMrtd = requireMrtdIsNull ? null : requireMrtd, - RequirePinCode = requirePinCodeIsNull ? null : requirePinCode, - BankIdCertificatePolicies = bankIdCertificatePolicies, - CardReader = cardReader, - Items = items - }; - return new BankIdUiPaymentState(configKey, bankIdPaymentProperties); - } - else - { - throw new ArgumentException("Invalid enum value for BankIdStateType", nameof(type)); - } - } -} diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Models/BankIdUiOptions.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Models/BankIdUiOptions.cs index 057d928c..75760539 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Models/BankIdUiOptions.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Models/BankIdUiOptions.cs @@ -22,7 +22,7 @@ public BankIdUiOptions( RequireMrtd = requireMrtd; ReturnRisk = returnRisk; CancelReturnUrl = cancelReturnUrl; - StateCookieName = stateCookieName; + StateKeyCookieName = stateCookieName; CardReader = cardReader; } @@ -38,7 +38,7 @@ public BankIdUiOptions( public string CancelReturnUrl { get; } - public string StateCookieName { get; } + public string StateKeyCookieName { get; } public CardReader? CardReader { get; } } diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Payment/BankIdPaymentOptions.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Payment/BankIdPaymentOptions.cs index 66deef73..782b3afc 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Payment/BankIdPaymentOptions.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Payment/BankIdPaymentOptions.cs @@ -50,7 +50,7 @@ public class BankIdPaymentOptions private CookieBuilder _stateCookieBuilder = new() { - Name = BankIdConstants.DefaultStateCookieName, + Name = BankIdConstants.StateKeyCookieName, SecurePolicy = CookieSecurePolicy.SameAsRequest, HttpOnly = true, SameSite = SameSiteMode.Lax, diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Payment/BankIdPaymentService.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Payment/BankIdPaymentService.cs index 03ac6fce..015bf107 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Payment/BankIdPaymentService.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Payment/BankIdPaymentService.cs @@ -2,6 +2,7 @@ using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; using ActiveLogin.Authentication.BankId.AspNetCore.Helpers; using ActiveLogin.Authentication.BankId.AspNetCore.Models; +using ActiveLogin.Authentication.BankId.Core; using ActiveLogin.Authentication.BankId.Core.Events; using ActiveLogin.Authentication.BankId.Core.Events.Infrastructure; using ActiveLogin.Authentication.BankId.Core.Flow; @@ -16,50 +17,39 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.Payment; -public class BankIdPaymentService : IBankIdPaymentService +public class BankIdPaymentService( + IHttpContextAccessor httpContextAccessor, + IAntiforgery antiforgery, + IOptionsSnapshot optionsSnapshot, + IBankIdFlowSystemClock systemClock, + IStateStorage bankIdStateStorage, + IBankIdDataStateProtector uiOptionsProtector, + IBankIdSupportedDeviceDetector bankIdSupportedDeviceDetector, + IBankIdEventTrigger bankIdEventTrigger, + IBankIdDataStateProtector uiResultProtector +) : IBankIdPaymentService { private const string StateCookieNameParameterName = "StateCookie.Name"; private readonly PathString _paymentInitPath = new($"/{BankIdConstants.Routes.ActiveLoginAreaName}/{BankIdConstants.Routes.BankIdPathName}/{BankIdConstants.Routes.BankIdPaymentControllerPath}"); - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IAntiforgery _antiforgery; - - private readonly IOptionsSnapshot _optionsSnapshot; - private readonly IBankIdFlowSystemClock _systemClock; - private readonly IBankIdUiStateProtector _bankIdUiStateProtector; - private readonly IBankIdUiResultProtector _uiResultProtector; - private readonly IBankIdUiOptionsProtector _uiOptionsProtector; - private readonly IBankIdSupportedDeviceDetector _bankIdSupportedDeviceDetector; - private readonly IBankIdEventTrigger _bankIdEventTrigger; - - public BankIdPaymentService( - IHttpContextAccessor httpContextAccessor, - IAntiforgery antiforgery, - IOptionsSnapshot optionsSnapshot, - IBankIdFlowSystemClock systemClock, - IBankIdUiStateProtector bankIdUiStateProtector, - IBankIdUiOptionsProtector uiOptionsProtector, - IBankIdSupportedDeviceDetector bankIdSupportedDeviceDetector, - IBankIdEventTrigger bankIdEventTrigger, - IBankIdUiResultProtector uiResultProtector) - { - _httpContextAccessor = httpContextAccessor; - _antiforgery = antiforgery; - _optionsSnapshot = optionsSnapshot; - _systemClock = systemClock; - _bankIdUiStateProtector = bankIdUiStateProtector; - _uiOptionsProtector = uiOptionsProtector; - _bankIdSupportedDeviceDetector = bankIdSupportedDeviceDetector; - _bankIdEventTrigger = bankIdEventTrigger; - _uiResultProtector = uiResultProtector; - } + private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor; + private readonly IAntiforgery _antiforgery = antiforgery; + + private readonly IOptionsSnapshot _optionsSnapshot = optionsSnapshot; + private readonly IBankIdFlowSystemClock _systemClock = systemClock; + private readonly IStateStorage _bankIdStateStorage = bankIdStateStorage; + private readonly IBankIdDataStateProtector _uiResultProtector = uiResultProtector; + private readonly IBankIdDataStateProtector _uiOptionsProtector = uiOptionsProtector; + private readonly IBankIdSupportedDeviceDetector _bankIdSupportedDeviceDetector = bankIdSupportedDeviceDetector; + private readonly IBankIdEventTrigger _bankIdEventTrigger = bankIdEventTrigger; - public Task InitiatePaymentAsync(BankIdPaymentProperties properties, string callbackPath, string configKey) + public async Task InitiatePaymentAsync(BankIdPaymentProperties properties, string callbackPath, string configKey) { var httpContext = _httpContextAccessor.HttpContext ?? throw new InvalidOperationException(BankIdConstants.ErrorMessages.CouldNotAccessHttpContext); var options = _optionsSnapshot.Get(configKey); - AppendStateCookie(httpContext, properties, options, configKey); + var stateKey = await SaveState(properties, configKey); + AppendCookie(httpContext, stateKey, options); var uiOptions = new BankIdUiOptions( options.BankIdCertificatePolicies, @@ -75,19 +65,27 @@ public Task InitiatePaymentAsync(BankIdPaymentProperties properties, string call var paymentUrl = GetUiInitUrl(httpContext, callbackPath, uiOptions); httpContext.Response.Redirect(paymentUrl); + } + + private void AppendCookie(HttpContext httpContext, StateKey key, BankIdPaymentOptions options) + { + Validators.ThrowIfNullOrWhitespace(options.StateCookie.Name, StateCookieNameParameterName); - return Task.CompletedTask; + var cookieOptions = options.StateCookie.Build(httpContext, _systemClock.UtcNow); + httpContext.Response.Cookies.Append(options.StateCookie.Name, key, cookieOptions); } public async Task GetPaymentResultAsync(string configKey) { Validators.ThrowIfNullOrWhitespace(configKey, nameof(configKey)); + var options = _optionsSnapshot.Get(configKey); var detectedDevice = _bankIdSupportedDeviceDetector.Detect(); - var options = _optionsSnapshot.Get(configKey); + Validators.ThrowIfNullOrWhitespace(options.StateCookie.Name, StateCookieNameParameterName); + var httpContext = _httpContextAccessor.HttpContext ?? throw new InvalidOperationException(BankIdConstants.ErrorMessages.CouldNotAccessHttpContext); + var state = await GetState(httpContext, options.StateCookie.Name); - var state = GetStateCookie(httpContext, options); if (state == null) { await _bankIdEventTrigger.TriggerAsync(new BankIdPaymentFailureEvent(BankIdConstants.ErrorMessages.InvalidStateCookie, detectedDevice)); @@ -134,27 +132,22 @@ await _bankIdEventTrigger.TriggerAsync(new BankIdPaymentSuccessEvent( ); } - private BankIdUiPaymentState? GetStateCookie(HttpContext httpContext, BankIdPaymentOptions options) + private async Task GetState(HttpContext httpContext, string stateCookieName) { - Validators.ThrowIfNullOrWhitespace(options.StateCookie.Name, StateCookieNameParameterName); - - var protectedState = httpContext.Request.Cookies[options.StateCookie.Name]; - if (string.IsNullOrEmpty(protectedState)) + var cookie = httpContext.Request.Cookies[stateCookieName]; + if (cookie is null) { return null; } - return _bankIdUiStateProtector.Unprotect(protectedState) as BankIdUiPaymentState; + + var stateKey = new StateKey(cookie); + return await _bankIdStateStorage.GetAsync(stateKey); } - private void AppendStateCookie(HttpContext httpContext, BankIdPaymentProperties properties, BankIdPaymentOptions options, string configKey) + private Task SaveState(BankIdPaymentProperties properties, string configKey) { - Validators.ThrowIfNullOrWhitespace(options.StateCookie.Name, StateCookieNameParameterName); - var state = new BankIdUiPaymentState(configKey, properties); - var cookieOptions = options.StateCookie.Build(httpContext, _systemClock.UtcNow); - var cookieValue = _bankIdUiStateProtector.Protect(state); - - httpContext.Response.Cookies.Append(options.StateCookie.Name, cookieValue, cookieOptions); + return _bankIdStateStorage.SetAsync(state); } private string GetUiInitUrl(HttpContext httpContext, string callbackPath, BankIdUiOptions uiOptions) diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Payment/ServiceCollectionBankIdExtensions.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Payment/ServiceCollectionBankIdExtensions.cs index 78501f57..80494ed2 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Payment/ServiceCollectionBankIdExtensions.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Payment/ServiceCollectionBankIdExtensions.cs @@ -69,8 +69,8 @@ private static void AddBankIdAuthDefaultServices(IBankIdPaymentBuilder builder) BankIdCommonConfiguration.AddDefaultServices(services); - services.AddTransient(); - services.AddTransient(); + services.AddSingleton(); + services.AddSingleton, BankIdUiResultProtector>(); services.AddTransient(); } diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Sign/BankIdSignOptions.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Sign/BankIdSignOptions.cs index 7ab7a4ea..49022818 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Sign/BankIdSignOptions.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Sign/BankIdSignOptions.cs @@ -39,7 +39,7 @@ public class BankIdSignOptions private CookieBuilder _stateCookieBuilder = new() { - Name = BankIdConstants.DefaultStateCookieName, + Name = BankIdConstants.StateKeyCookieName, SecurePolicy = CookieSecurePolicy.SameAsRequest, HttpOnly = true, SameSite = SameSiteMode.Lax, diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Sign/BankIdSignService.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Sign/BankIdSignService.cs index 1e2c2017..dff4f6be 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Sign/BankIdSignService.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Sign/BankIdSignService.cs @@ -1,7 +1,7 @@ -using ActiveLogin.Authentication.BankId.Api.Models; using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; using ActiveLogin.Authentication.BankId.AspNetCore.Helpers; using ActiveLogin.Authentication.BankId.AspNetCore.Models; +using ActiveLogin.Authentication.BankId.Core; using ActiveLogin.Authentication.BankId.Core.Events; using ActiveLogin.Authentication.BankId.Core.Events.Infrastructure; using ActiveLogin.Authentication.BankId.Core.Flow; @@ -16,50 +16,39 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.Sign; -public class BankIdSignService : IBankIdSignService +public class BankIdSignService( + IHttpContextAccessor httpContextAccessor, + IAntiforgery antiforgery, + IOptionsSnapshot optionsSnapshot, + IBankIdFlowSystemClock systemClock, + IStateStorage bankIdStateStorage, + IBankIdDataStateProtector uiOptionsProtector, + IBankIdSupportedDeviceDetector bankIdSupportedDeviceDetector, + IBankIdEventTrigger bankIdEventTrigger, + IBankIdDataStateProtector uiResultProtector +) : IBankIdSignService { private const string StateCookieNameParameterName = "StateCookie.Name"; private readonly PathString _signInitPath = new($"/{BankIdConstants.Routes.ActiveLoginAreaName}/{BankIdConstants.Routes.BankIdPathName}/{BankIdConstants.Routes.BankIdSignControllerPath}"); - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IAntiforgery _antiforgery; - - private readonly IOptionsSnapshot _optionsSnapshot; - private readonly IBankIdFlowSystemClock _systemClock; - private readonly IBankIdUiStateProtector _bankIdUiStateProtector; - private readonly IBankIdUiResultProtector _uiResultProtector; - private readonly IBankIdUiOptionsProtector _uiOptionsProtector; - private readonly IBankIdSupportedDeviceDetector _bankIdSupportedDeviceDetector; - private readonly IBankIdEventTrigger _bankIdEventTrigger; - - public BankIdSignService( - IHttpContextAccessor httpContextAccessor, - IAntiforgery antiforgery, - IOptionsSnapshot optionsSnapshot, - IBankIdFlowSystemClock systemClock, - IBankIdUiStateProtector bankIdUiStateProtector, - IBankIdUiOptionsProtector uiOptionsProtector, - IBankIdSupportedDeviceDetector bankIdSupportedDeviceDetector, - IBankIdEventTrigger bankIdEventTrigger, - IBankIdUiResultProtector uiResultProtector) - { - _httpContextAccessor = httpContextAccessor; - _antiforgery = antiforgery; - _optionsSnapshot = optionsSnapshot; - _systemClock = systemClock; - _bankIdUiStateProtector = bankIdUiStateProtector; - _uiOptionsProtector = uiOptionsProtector; - _bankIdSupportedDeviceDetector = bankIdSupportedDeviceDetector; - _bankIdEventTrigger = bankIdEventTrigger; - _uiResultProtector = uiResultProtector; - } + private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor; + private readonly IAntiforgery _antiforgery = antiforgery; + + private readonly IOptionsSnapshot _optionsSnapshot = optionsSnapshot; + private readonly IBankIdFlowSystemClock _systemClock = systemClock; + private readonly IStateStorage _bankIdStateStorage = bankIdStateStorage; + private readonly IBankIdDataStateProtector _uiResultProtector = uiResultProtector; + private readonly IBankIdDataStateProtector _uiOptionsProtector = uiOptionsProtector; + private readonly IBankIdSupportedDeviceDetector _bankIdSupportedDeviceDetector = bankIdSupportedDeviceDetector; + private readonly IBankIdEventTrigger _bankIdEventTrigger = bankIdEventTrigger; - public Task InitiateSignAsync(BankIdSignProperties properties, string callbackPath, string configKey) + public async Task InitiateSignAsync(BankIdSignProperties properties, string callbackPath, string configKey) { var httpContext = _httpContextAccessor.HttpContext ?? throw new InvalidOperationException(BankIdConstants.ErrorMessages.CouldNotAccessHttpContext); var options = _optionsSnapshot.Get(configKey); - AppendStateCookie(httpContext, properties, options, configKey); + var stateKey = await SaveState(properties, configKey); + AppendCookie(httpContext, stateKey, options); var uiOptions = new BankIdUiOptions( options.BankIdCertificatePolicies, @@ -75,31 +64,39 @@ public Task InitiateSignAsync(BankIdSignProperties properties, string callbackPa var signUrl = GetUiInitUrl(httpContext, callbackPath, uiOptions); httpContext.Response.Redirect(signUrl); + } + + private void AppendCookie(HttpContext httpContext, StateKey key, BankIdSignOptions options) + { + Validators.ThrowIfNullOrWhitespace(options.StateCookie.Name, StateCookieNameParameterName); - return Task.CompletedTask; + var cookieOptions = options.StateCookie.Build(httpContext, _systemClock.UtcNow); + httpContext.Response.Cookies.Append(options.StateCookie.Name, key, cookieOptions); } public async Task GetSignResultAsync(string configKey) { Validators.ThrowIfNullOrWhitespace(configKey, nameof(configKey)); + var options = _optionsSnapshot.Get(configKey); var detectedDevice = _bankIdSupportedDeviceDetector.Detect(); - var options = _optionsSnapshot.Get(configKey); + Validators.ThrowIfNullOrWhitespace(options.StateCookie.Name, StateCookieNameParameterName); + var httpContext = _httpContextAccessor.HttpContext ?? throw new InvalidOperationException(BankIdConstants.ErrorMessages.CouldNotAccessHttpContext); + var signState = await GetState(httpContext, options.StateCookie.Name); - var state = GetStateCookie(httpContext, options); - if (state == null) + if (signState == null) { await _bankIdEventTrigger.TriggerAsync(new BankIdSignFailureEvent(BankIdConstants.ErrorMessages.InvalidStateCookie, detectedDevice)); - throw new ArgumentException(BankIdConstants.ErrorMessages.InvalidStateCookie); + throw new ArgumentException(BankIdConstants.ErrorMessages.StateNotFound); } - if (!state.ConfigKey.Equals(configKey)) + if (!signState.ConfigKey.Equals(configKey)) { - throw new ArgumentException(BankIdConstants.ErrorMessages.InvalidStateCookie); + throw new ArgumentException(BankIdConstants.ErrorMessages.InvalidState); } - DeleteStateCookie(httpContext, options); + DeleteCookie(httpContext, options); if (!httpContext.Request.HasFormContentType) { @@ -113,14 +110,14 @@ public Task InitiateSignAsync(BankIdSignProperties properties, string callbackPa if (StringValues.IsNullOrEmpty(protectedUiResult)) { await _bankIdEventTrigger.TriggerAsync(new BankIdSignFailureEvent(BankIdConstants.ErrorMessages.InvalidUiResult, detectedDevice)); - return new BankIdSignResult(false, state.BankIdSignProperties); + return new BankIdSignResult(false, signState.BankIdSignProperties); } var uiResult = _uiResultProtector.Unprotect(protectedUiResult.ToString()); if (!uiResult.IsSuccessful) { await _bankIdEventTrigger.TriggerAsync(new BankIdSignFailureEvent(BankIdConstants.ErrorMessages.InvalidUiResult, detectedDevice)); - return new BankIdSignResult(false, state.BankIdSignProperties); + return new BankIdSignResult(false, signState.BankIdSignProperties); } await _bankIdEventTrigger.TriggerAsync(new BankIdSignSuccessEvent( @@ -129,34 +126,11 @@ await _bankIdEventTrigger.TriggerAsync(new BankIdSignSuccessEvent( )); return BankIdSignResult.Success( - state.BankIdSignProperties, + signState.BankIdSignProperties, uiResult.GetCompletionData() ); } - private BankIdUiSignState? GetStateCookie(HttpContext httpContext, BankIdSignOptions options) - { - Validators.ThrowIfNullOrWhitespace(options.StateCookie.Name, StateCookieNameParameterName); - - var protectedState = httpContext.Request.Cookies[options.StateCookie.Name]; - if (string.IsNullOrEmpty(protectedState)) - { - return null; - } - return _bankIdUiStateProtector.Unprotect(protectedState) as BankIdUiSignState; - } - - private void AppendStateCookie(HttpContext httpContext, BankIdSignProperties properties, BankIdSignOptions options, string configKey) - { - Validators.ThrowIfNullOrWhitespace(options.StateCookie.Name, StateCookieNameParameterName); - - var state = new BankIdUiSignState(configKey, properties); - var cookieOptions = options.StateCookie.Build(httpContext, _systemClock.UtcNow); - var cookieValue = _bankIdUiStateProtector.Protect(state); - - httpContext.Response.Cookies.Append(options.StateCookie.Name, cookieValue, cookieOptions); - } - private string GetUiInitUrl(HttpContext httpContext, string callbackPath, BankIdUiOptions uiOptions) { var pathBase = httpContext.Request.PathBase; @@ -174,7 +148,25 @@ private string GetUiInitUrl(HttpContext httpContext, string callbackPath, BankId return $"{signUrl}{queryBuilder}"; } - private void DeleteStateCookie(HttpContext httpContext, BankIdSignOptions options) + private Task GetState(HttpContext httpContext, string stateCookieName) + { + var cookie = httpContext.Request.Cookies[stateCookieName]; + if (cookie is null) + { + return Task.FromResult(null); + } + + var stateKey = new StateKey(cookie); + return _bankIdStateStorage.GetAsync(stateKey); + } + + private Task SaveState(BankIdSignProperties properties, string configKey) + { + var state = new BankIdUiSignState(configKey, properties); + return _bankIdStateStorage.SetAsync(state); + } + + private void DeleteCookie(HttpContext httpContext, BankIdSignOptions options) { Validators.ThrowIfNullOrWhitespace(options.StateCookie.Name, StateCookieNameParameterName); diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/Sign/ServiceCollectionBankIdExtensions.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/Sign/ServiceCollectionBankIdExtensions.cs index f335c4d5..b14528ff 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/Sign/ServiceCollectionBankIdExtensions.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/Sign/ServiceCollectionBankIdExtensions.cs @@ -69,8 +69,8 @@ private static void AddBankIdAuthDefaultServices(IBankIdSignBuilder builder) BankIdCommonConfiguration.AddDefaultServices(services); - services.AddTransient(); - services.AddTransient(); + services.AddSingleton(); + services.AddSingleton, BankIdUiResultProtector>(); services.AddTransient(); } diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/UserContext/Device/IServiceCollectionExtensions.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/UserContext/Device/IServiceCollectionExtensions.cs index 5547f6cd..b03868e4 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/UserContext/Device/IServiceCollectionExtensions.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/UserContext/Device/IServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; using ActiveLogin.Authentication.BankId.AspNetCore.UserContext.Device.Resolvers; +using ActiveLogin.Authentication.BankId.AspNetCore.UserContext.Device.State; using ActiveLogin.Authentication.BankId.Core.UserContext.Device; using ActiveLogin.Authentication.BankId.Core.UserContext.Device.Configuration; using ActiveLogin.Authentication.BankId.Core.UserContext.Device.ResolverFactory; @@ -11,10 +12,10 @@ public static class IServiceCollectionExtensions { public static IServiceCollection AddDefaultDeviceData(this IServiceCollection services) { - services.AddTransient(); - services.AddSingleton(new DefaultBankIdEndUserDeviceDataConfiguration()); - services.AddTransient(); - services.AddTransient(); + services.AddScoped(); + services.AddSingleton(); + services.AddScoped(); + services.AddSingleton, BankIdDeviceDataProtector>(); return services; } diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/UserContext/Device/Resolvers/BankIdDefaultEndUserWebDeviceDataResolver.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/UserContext/Device/Resolvers/BankIdDefaultEndUserWebDeviceDataResolver.cs index f80d271b..7d07467a 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/UserContext/Device/Resolvers/BankIdDefaultEndUserWebDeviceDataResolver.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/UserContext/Device/Resolvers/BankIdDefaultEndUserWebDeviceDataResolver.cs @@ -13,8 +13,8 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.UserContext.Device.Resolv /// public sealed class BankIdDefaultEndUserWebDeviceDataResolver( IHttpContextAccessor httpContextAccessor, - IBankIdDeviceDataProtector protector) - : BankIdEndUserWebDeviceDataResolverBase(httpContextAccessor, protector) + IBankIdDataStateProtector protector +) : BankIdEndUserWebDeviceDataResolverBase(httpContextAccessor, protector) { public override BankIdEndUserDeviceType DeviceType => BankIdEndUserDeviceType.Web; diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/UserContext/Device/Resolvers/BankIdEndUserWebDeviceDataResolverBase.cs b/src/ActiveLogin.Authentication.BankId.AspNetCore/UserContext/Device/Resolvers/BankIdEndUserWebDeviceDataResolverBase.cs index 17572353..34175e8b 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/UserContext/Device/Resolvers/BankIdEndUserWebDeviceDataResolverBase.cs +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/UserContext/Device/Resolvers/BankIdEndUserWebDeviceDataResolverBase.cs @@ -9,7 +9,8 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.UserContext.Device.Resolv public abstract class BankIdEndUserWebDeviceDataResolverBase( IHttpContextAccessor httpContextAccessor, - IBankIdDeviceDataProtector deviceDataProtector) : BankIdDeviceDataResolverBase + IBankIdDataStateProtector deviceDataProtector +) : BankIdDeviceDataResolverBase { protected HttpContext Context => httpContextAccessor.HttpContext ?? throw new InvalidOperationException("HttpContext is null"); private const string DeviceDataCookieNameParameterName = "DeviceDataCookie.Name"; @@ -43,9 +44,22 @@ protected void AppendDeviceDataCookie(DeviceDataState state) Validators.ThrowIfNullOrWhitespace(DeviceDataCookie.Name, DeviceDataCookieNameParameterName); var protectedState = Context.Request.Cookies[DeviceDataCookie.Name]; - return string.IsNullOrEmpty(protectedState) - ? null - : deviceDataProtector.Unprotect(protectedState); + if (string.IsNullOrWhiteSpace(protectedState)) + { + return null; + } + + try + { + return deviceDataProtector.Unprotect(protectedState); + } + catch (Exception) + { + // If we can't unprotect the cookie (e.g., due to key changes or format changes), + // delete the invalid cookie and return null to generate a new device identifier + DeleteDeviceDataCookie(); + return null; + } } protected void DeleteDeviceDataCookie() diff --git a/src/ActiveLogin.Authentication.BankId.AzureMonitor/ActiveLogin.Authentication.BankId.AzureMonitor.csproj b/src/ActiveLogin.Authentication.BankId.AzureMonitor/ActiveLogin.Authentication.BankId.AzureMonitor.csproj index 61576c5a..0d569b55 100644 --- a/src/ActiveLogin.Authentication.BankId.AzureMonitor/ActiveLogin.Authentication.BankId.AzureMonitor.csproj +++ b/src/ActiveLogin.Authentication.BankId.AzureMonitor/ActiveLogin.Authentication.BankId.AzureMonitor.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/ActiveLogin.Authentication.BankId.Core/Flow/BankIdFlowService.cs b/src/ActiveLogin.Authentication.BankId.Core/Flow/BankIdFlowService.cs index 6dca61aa..78abb6fb 100644 --- a/src/ActiveLogin.Authentication.BankId.Core/Flow/BankIdFlowService.cs +++ b/src/ActiveLogin.Authentication.BankId.Core/Flow/BankIdFlowService.cs @@ -114,7 +114,9 @@ private async Task GetAuthRequest(BankIdFlowOptions flowOptions, st var requiredPersonalIdentityNumber = resolvedRequirements.RequiredPersonalIdentityNumber ?? flowOptions.RequiredPersonalIdentityNumber; var requireMrtd = resolvedRequirements.RequireMrtd ?? flowOptions.RequireMrtd; var requirePinCode = resolvedRequirements.RequirePinCode ?? flowOptions.RequirePinCode; - var certificatePolicies = resolvedRequirements.CertificatePolicies.Any() ? resolvedRequirements.CertificatePolicies : flowOptions.CertificatePolicies; + var certificatePolicies = resolvedRequirements.CertificatePolicies.Any() + ? resolvedRequirements.CertificatePolicies + : flowOptions.CertificatePolicies; var resolvedCertificatePolicies = GetResolvedCertificatePolicies(certificatePolicies, flowOptions.SameDevice); var cardReader = resolvedRequirements.CardReader ?? flowOptions.CardReader; diff --git a/src/ActiveLogin.Authentication.BankId.Core/IStateStorage.cs b/src/ActiveLogin.Authentication.BankId.Core/IStateStorage.cs new file mode 100644 index 00000000..26878366 --- /dev/null +++ b/src/ActiveLogin.Authentication.BankId.Core/IStateStorage.cs @@ -0,0 +1,34 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace ActiveLogin.Authentication.BankId.Core; + +public readonly record struct StateKey(string Key) +{ + public static StateKey New() => new($"{Guid.NewGuid():N}"); + + public static implicit operator string(StateKey key) => key.Key; + public override string ToString() => Key; +}; + +public interface IStateStorage +{ + Task GetAsync(StateKey key); + Task SetAsync(T value); + Task RemoveAsync(StateKey key); +} + +public static class BankIdBuilderStateStorageExtensions +{ + public static IBankIdBuilder AddStateStorage(this IBankIdBuilder builder) + where TStateStorage : class, IStateStorage + { + builder.Services.AddSingleton(); + return builder; + } + + public static IBankIdBuilder AddStateStorage(this IBankIdBuilder builder, IStateStorage stateStorage) + { + builder.Services.AddSingleton(stateStorage); + return builder; + } +} diff --git a/src/ActiveLogin.Authentication.BankId.Core/ServiceCollectionBankIdExtensions.cs b/src/ActiveLogin.Authentication.BankId.Core/ServiceCollectionBankIdExtensions.cs index 80872188..260ef99e 100644 --- a/src/ActiveLogin.Authentication.BankId.Core/ServiceCollectionBankIdExtensions.cs +++ b/src/ActiveLogin.Authentication.BankId.Core/ServiceCollectionBankIdExtensions.cs @@ -50,7 +50,7 @@ public static IServiceCollection AddBankId(this IServiceCollection services, Act UseUserAgentFromContext(bankIdBuilder); bankId(bankIdBuilder); - + return services; } @@ -75,7 +75,7 @@ private static void AddBankIdDefaultServices(IBankIdBuilder builder) builder.AddEventListener(); builder.AddResultStore(); - + } private static void UseUserAgentFromContext(this IBankIdBuilder builder) @@ -92,7 +92,7 @@ private static void ConfigureHttpClientWithUserAgent(IServiceProvider sp, HttpCl httpClient.DefaultRequestHeaders.UserAgent.Clear(); httpClient.DefaultRequestHeaders.UserAgent.Add(productInfoHeaderValue); } - + private static (string name, string version) GetActiveLoginInfo() { var productName = BankIdConstants.ProductName; diff --git a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiAuth_Tests.cs b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiAuth_Tests.cs index cfb48492..fd18f59a 100644 --- a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiAuth_Tests.cs +++ b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiAuth_Tests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; @@ -7,12 +6,12 @@ using System.Threading.Tasks; using ActiveLogin.Authentication.BankId.Api; +using ActiveLogin.Authentication.BankId.AspNetCore.Areas.ActiveLogin.Models; using ActiveLogin.Authentication.BankId.AspNetCore.Auth; using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; using ActiveLogin.Authentication.BankId.AspNetCore.Models; using ActiveLogin.Authentication.BankId.AspNetCore.Test.Helpers; using ActiveLogin.Authentication.BankId.Core; -using ActiveLogin.Authentication.BankId.Core.CertificatePolicies; using ActiveLogin.Authentication.BankId.Core.Launcher; using AngleSharp.Html.Dom; @@ -36,30 +35,6 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.Test; public class BankId_UiAuth_Tests : BankId_Ui_Tests_Base { - private const string DefaultStateCookieName = "__ActiveLogin.BankIdUiState"; - - private readonly Mock _bankIdUiOptionsProtector; - private readonly Mock _bankIdUiStateProtector; - - public BankId_UiAuth_Tests() - { - _bankIdUiOptionsProtector = new Mock(); - _bankIdUiOptionsProtector - .Setup(protector => protector.Unprotect(It.IsAny())) - .Returns(new BankIdUiOptions(new List(), false, false, false, false, "/", DefaultStateCookieName, Api.Models.CardReader.class1)); - _bankIdUiOptionsProtector - .Setup(protector => protector.Protect(It.IsAny())) - .Returns("Ignored"); - - var authState = new BankIdUiAuthState(new AuthenticationProperties()); - _bankIdUiStateProtector = new Mock(); - _bankIdUiStateProtector - .Setup(protector => protector.Unprotect(It.IsAny())) - .Returns(authState); - _bankIdUiStateProtector - .Setup(protector => protector.Protect(It.IsAny())) - .Returns("Ignored"); - } [Fact] public async Task BankIdUiAuthController_Returns_404_If_BankId_Is_Not_Registered() @@ -201,16 +176,10 @@ public async Task Authentication_UI_Should_Be_Accessible_Even_When_Site_Requires .Build(); config.Filters.Add(new AuthorizeFilter(policy)); }); - - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); }); // Act - var request = - CreateRequestWithFakeStateCookie(server, "/ActiveLogin/BankId/Auth?returnUrl=%2F&uiOptions=X&orderRef=Y"); - var transaction = await request.GetAsync(); - + var transaction = await CreateBankIdUiRequest(server, "/ActiveLogin/BankId/Auth", AuthStateKeyCookieName, new BankIdUiAuthState(new AuthenticationProperties())); // Assert Assert.Equal(HttpStatusCode.OK, transaction.StatusCode); @@ -220,11 +189,7 @@ public async Task Authentication_UI_Should_Be_Accessible_Even_When_Site_Requires public async Task Init_Returns_Ui_With_Resolved_Cancel_Url() { // Arrange - var options = new BankIdUiOptions(new List(), true, false, false, false, "~/cru", DefaultStateCookieName, Api.Models.CardReader.class1); - _bankIdUiOptionsProtector - .Setup(protector => protector.Unprotect(It.IsAny())) - .Returns(options); - + var uiOptions = CreateUiOptions(AuthStateKeyCookieName, cancelReturnUrl: "~/cru", sameDevice: true); using var server = CreateServer(o => { o.UseSimulatedEnvironment(); @@ -236,17 +201,10 @@ public async Task Init_Returns_Ui_With_Resolved_Cancel_Url() DefaultAppConfiguration(async context => { await context.ChallengeAsync(BankIdAuthDefaults.SameDeviceAuthenticationScheme); - }), - services => - { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); - }); + })); // Act - var request = - CreateRequestWithFakeStateCookie(server, "/ActiveLogin/BankId/Auth?returnUrl=%2F&uiOptions=X&orderRef=Y"); - var transaction = await request.GetAsync(); + var transaction = await CreateBankIdUiRequest(server, "/ActiveLogin/BankId/Auth", AuthStateKeyCookieName, new BankIdUiAuthState(new AuthenticationProperties()), uiOptions); // Assert Assert.Equal(HttpStatusCode.OK, transaction.StatusCode); @@ -270,17 +228,10 @@ public async Task Init_Returns_Ui_With_Script() DefaultAppConfiguration(async context => { await context.ChallengeAsync(BankIdAuthDefaults.SameDeviceAuthenticationScheme); - }), - services => - { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); - }); + })); // Act - var request = - CreateRequestWithFakeStateCookie(server, "/ActiveLogin/BankId/Auth?returnUrl=%2F&uiOptions=X&orderRef=Y"); - var transaction = await request.GetAsync(); + var transaction = await CreateBankIdUiRequest(server, "/ActiveLogin/BankId/Auth", AuthStateKeyCookieName, new BankIdUiAuthState(new AuthenticationProperties())); // Assert Assert.Equal(HttpStatusCode.OK, transaction.StatusCode); @@ -294,7 +245,7 @@ public async Task Init_Returns_Ui_With_Script() Assert.Equal("/", GetInlineJsonValue(transactionContent, "returnUrl")); Assert.Equal("/", GetInlineJsonValue(transactionContent, "cancelReturnUrl")); - Assert.Equal("X", GetInlineJsonValue(transactionContent, "uiOptionsGuid")); + Assert.NotEmpty(GetInlineJsonValue(transactionContent, "uiOptionsGuid")); // Verify real protected options are present } [Fact] @@ -312,30 +263,34 @@ public async Task Init_Preserves_UI_Options() DefaultAppConfiguration(async context => { await context.ChallengeAsync(BankIdAuthDefaults.SameDeviceAuthenticationScheme); - }), - services => - { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); - }); + })); // Act - var request = - CreateRequestWithFakeStateCookie(server, "/ActiveLogin/BankId/Auth?returnUrl=%2F&uiOptions=UIOPTIONS&orderRef=Y"); - var transaction = await request.GetAsync(); + var transaction = await CreateBankIdUiRequest(server, "/ActiveLogin/BankId/Auth", AuthStateKeyCookieName, new BankIdUiAuthState(new AuthenticationProperties())); // Assert Assert.Equal(HttpStatusCode.OK, transaction.StatusCode); var transactionContent = await transaction.Content.ReadAsStringAsync(); - Assert.Equal("UIOPTIONS", GetInlineJsonValue(transactionContent, "uiOptionsGuid")); + Assert.NotEmpty(GetInlineJsonValue(transactionContent, "uiOptionsGuid")); // Verify real protected options are present } [Fact] public async Task Init_Requires_State_Cookie_To_Be_Present() { // Arrange + var uiOptions = CreateUiOptions(AuthStateKeyCookieName, sameDevice: true); + + // Setup mock protector to handle "dummy" string + var mockProtector = new Mock>(); + mockProtector + .Setup(protector => protector.Unprotect("dummy")) + .Returns(uiOptions); + mockProtector + .Setup(protector => protector.Protect(It.IsAny())) + .Returns("dummy"); + using var server = CreateServer(o => { o.UseSimulatedEnvironment(); @@ -350,11 +305,11 @@ public async Task Init_Requires_State_Cookie_To_Be_Present() }), services => { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); + services.AddTransient(s => mockProtector.Object); }); - // Act - var request = server.CreateRequest("/ActiveLogin/BankId/Auth?returnUrl=%2F&uiOptions=X&orderRef=Y"); + // Act - Try to access UI without state cookie (should redirect) + var request = server.CreateRequest("/ActiveLogin/BankId/Auth?returnUrl=%2F&uiOptions=dummy&orderRef=Y"); request.AddHeader("Cookie", ""); var transaction = await request.GetAsync(); @@ -366,16 +321,30 @@ public async Task Init_Requires_State_Cookie_To_Be_Present() [Fact] public async Task AutoLaunch_Sets_Correct_RedirectUri() { - // Arrange mocks - var autoLaunchOptions = - new BankIdUiOptions(new List(), true, false, false, false, string.Empty, DefaultStateCookieName, Api.Models.CardReader.class1); - var mockProtector = new Mock(); + // Arrange + var stateKey = StateKey.New(); + var autoLaunchOptions = CreateUiOptions(AuthStateKeyCookieName, cancelReturnUrl: string.Empty, sameDevice: true); + + // Setup mock protector to handle "TestOptions" and "X" strings + var mockProtector = new Mock>(); mockProtector - .Setup(protector => protector.Unprotect(It.IsAny())) + .Setup(protector => protector.Unprotect("TestOptions")) + .Returns(autoLaunchOptions); + mockProtector + .Setup(protector => protector.Unprotect("X")) .Returns(autoLaunchOptions); mockProtector .Setup(protector => protector.Protect(It.IsAny())) - .Returns("Ignored"); + .Returns("TestOptions"); + + // Setup mock order ref protector for "Y" string + var mockOrderRefProtector = new Mock>(); + mockOrderRefProtector + .Setup(protector => protector.Unprotect(It.IsAny())) + .Returns(new BankIdUiOrderRef("ANY")); + mockOrderRefProtector + .Setup(protector => protector.Protect(It.IsAny())) + .Returns("Y"); using var server = CreateServer( o => @@ -394,44 +363,58 @@ public async Task AutoLaunch_Sets_Correct_RedirectUri() services => { services.AddTransient(s => mockProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); + services.AddTransient(s => mockOrderRefProtector.Object); }); // Arrange acting request var testReturnUrl = "/TestReturnUrl"; var testOptions = "TestOptions"; - var initializeRequestBody = new {returnUrl = testReturnUrl, uiOptions = testOptions}; + var initializeRequestBody = new { returnUrl = testReturnUrl, uiOptions = testOptions }; + var stateCookies = CreateStateCookies(AuthStateKeyCookieName, stateKey, new BankIdUiAuthState(new AuthenticationProperties()), server.Services); // Act - var initializeTransaction = await GetInitializeResponse(server, initializeRequestBody); + var initializeTransaction = await GetInitializeResponse("Auth", server, initializeRequestBody, stateCookies); // Assert Assert.Equal(HttpStatusCode.OK, initializeTransaction.StatusCode); var responseContent = await initializeTransaction.Content.ReadAsStringAsync(); var responseObject = JsonConvert.DeserializeAnonymousType(responseContent, - new {RedirectUri = "", OrderRef = "", IsAutoLaunch = false}); + new { RedirectUri = "", OrderRef = "", IsAutoLaunch = false }); Assert.True(responseObject.IsAutoLaunch); var encodedReturnParam = UrlEncoder.Default.Encode(testReturnUrl); - var expectedUrl = - $"http://localhost/ActiveLogin/BankId/Auth?returnUrl={encodedReturnParam}&uiOptions={testOptions}"; + var expectedUrl = $"http://localhost/ActiveLogin/BankId/Auth?returnUrl={encodedReturnParam}&uiOptions={testOptions}"; Assert.Equal(expectedUrl, responseObject.RedirectUri); } [Fact] public async Task Api_Always_Returns_CamelCase_Json_For_Http200Ok() { - // Arrange mocks - var autoLaunchOptions = - new BankIdUiOptions(new List(), false, false, false, false, string.Empty, DefaultStateCookieName, Api.Models.CardReader.class1); - var mockProtector = new Mock(); + // Arrange + var stateKey = StateKey.New(); + var autoLaunchOptions = CreateUiOptions(AuthStateKeyCookieName, cancelReturnUrl: string.Empty, sameDevice: false); + + // Setup mock protector to handle "TestOptions" and "X" strings + var mockProtector = new Mock>(); mockProtector - .Setup(protector => protector.Unprotect(It.IsAny())) + .Setup(protector => protector.Unprotect("TestOptions")) + .Returns(autoLaunchOptions); + mockProtector + .Setup(protector => protector.Unprotect("X")) .Returns(autoLaunchOptions); mockProtector .Setup(protector => protector.Protect(It.IsAny())) - .Returns("Ignored"); + .Returns("TestOptions"); + + // Setup mock order ref protector for "Y" string + var mockOrderRefProtector = new Mock>(); + mockOrderRefProtector + .Setup(protector => protector.Unprotect(It.IsAny())) + .Returns(new BankIdUiOrderRef("ANY")); + mockOrderRefProtector + .Setup(protector => protector.Protect(It.IsAny())) + .Returns("Y"); using var server = CreateServer( o => @@ -450,21 +433,21 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http200Ok() services => { services.AddTransient(s => mockProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); + services.AddTransient(s => mockOrderRefProtector.Object); services.AddMvc().AddJsonOptions(configure => { configure.JsonSerializerOptions.PropertyNamingPolicy = null; }); }); - // Arrange acting request var testReturnUrl = "/TestReturnUrl"; var testOptions = "TestOptions"; - var initializeRequestBody = new {returnUrl = testReturnUrl, uiOptions = testOptions}; + var initializeRequestBody = new { returnUrl = testReturnUrl, uiOptions = testOptions }; + var stateCookies = CreateStateCookies(AuthStateKeyCookieName, stateKey, new BankIdUiAuthState(new AuthenticationProperties()), server.Services); //Act - var initializeTransaction = await GetInitializeResponse(server, initializeRequestBody); + var initializeTransaction = await GetInitializeResponse("Auth", server, initializeRequestBody, stateCookies); // Assert Assert.Equal(HttpStatusCode.OK, initializeTransaction.StatusCode); @@ -478,12 +461,27 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http200Ok() [Fact] public async Task Api_Always_Returns_CamelCase_Json_For_Http400BadRequest() { - // Arrange mocks - var autoLaunchOptions = - new BankIdUiOptions(new List(), false, false, false, false, string.Empty, DefaultStateCookieName, Api.Models.CardReader.class1); - _bankIdUiOptionsProtector - .Setup(protector => protector.Unprotect(It.IsAny())) + // Arrange + var stateKey = StateKey.New(); + var autoLaunchOptions = CreateUiOptions(AuthStateKeyCookieName, cancelReturnUrl: string.Empty, sameDevice: false); + + // Setup mock protector to handle both "X" for UI call and return "TestOptions" + var mockProtector = new Mock>(); + mockProtector + .Setup(protector => protector.Unprotect("X")) .Returns(autoLaunchOptions); + mockProtector + .Setup(protector => protector.Protect(It.IsAny())) + .Returns("TestOptions"); + + // Setup mock order ref protector for "Y" string + var mockOrderRefProtector = new Mock>(); + mockOrderRefProtector + .Setup(protector => protector.Unprotect(It.IsAny())) + .Returns(new BankIdUiOrderRef("ANY")); + mockOrderRefProtector + .Setup(protector => protector.Protect(It.IsAny())) + .Returns("Y"); using var server = CreateServer( o => @@ -501,16 +499,16 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http400BadRequest() }), services => { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); + services.AddTransient(s => mockProtector.Object); + services.AddTransient(s => mockOrderRefProtector.Object); }); - // Arrange acting request var initializeRequestBody = new { }; + var stateCookies = CreateStateCookies(AuthStateKeyCookieName, stateKey, new BankIdUiAuthState(new AuthenticationProperties()), server.Services); //Act - var initializeTransaction = await GetInitializeResponse(server, initializeRequestBody); + var initializeTransaction = await GetInitializeResponse("Auth", server, initializeRequestBody, stateCookies); // Assert Assert.Equal(HttpStatusCode.BadRequest, initializeTransaction.StatusCode); @@ -524,13 +522,27 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http400BadRequest() [Fact] public async Task Cancel_Calls_CancelApi() { - // Arrange mocks - var autoLaunchOptions = - new BankIdUiOptions(new List(), false, false, false, false, string.Empty, DefaultStateCookieName, Api.Models.CardReader.class1); - _bankIdUiOptionsProtector + // Arrange + var autoLaunchOptions = CreateUiOptions(AuthStateKeyCookieName, cancelReturnUrl: string.Empty, sameDevice: false); + + // Setup mock protectors + var mockOptionsProtector = new Mock>(); + mockOptionsProtector .Setup(protector => protector.Unprotect(It.IsAny())) .Returns(autoLaunchOptions); + mockOptionsProtector + .Setup(protector => protector.Protect(It.IsAny())) + .Returns("TestOptions"); + var mockOrderRefProtector = new Mock>(); + mockOrderRefProtector + .Setup(protector => protector.Unprotect(It.IsAny())) + .Returns(new BankIdUiOrderRef("ANY")); + mockOrderRefProtector + .Setup(protector => protector.Protect(It.IsAny())) + .Returns("MockedProtectedOrderRef"); + var testBankIdApi = new TestBankIdAppApi(new BankIdSimulatedAppApiClient()); + var stateKey = StateKey.New(); using var server = CreateServer( o => @@ -548,15 +560,13 @@ public async Task Cancel_Calls_CancelApi() }), services => { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); + services.AddSingleton(s => mockOptionsProtector.Object); + services.AddSingleton(s => mockOrderRefProtector.Object); services.AddSingleton(s => testBankIdApi); }); - // Arrange csrf info - var loginRequest = - CreateRequestWithFakeStateCookie(server, "/ActiveLogin/BankId/Auth?returnUrl=%2F&uiOptions=X&orderRef=Y"); - var loginResponse = await loginRequest.GetAsync(); + // Arrange csrf info using helper + var loginResponse = await CreateBankIdUiRequest(server, "/ActiveLogin/BankId/Auth", AuthStateKeyCookieName, new BankIdUiAuthState(new AuthenticationProperties())); var loginCookies = loginResponse.Headers.GetValues("set-cookie"); var loginContent = await loginResponse.Content.ReadAsStringAsync(); var csrfToken = GetRequestVerificationToken(loginContent); @@ -564,21 +574,21 @@ public async Task Cancel_Calls_CancelApi() // Arrange acting request var testReturnUrl = "/TestReturnUrl"; var testOptions = "TestOptions"; - var initializeRequest = new JsonContent(new {returnUrl = testReturnUrl, uiOptions = testOptions}); + var initializeRequest = new JsonContent(new { returnUrl = testReturnUrl, uiOptions = testOptions }); initializeRequest.Headers.Add("Cookie", loginCookies); initializeRequest.Headers.Add("RequestVerificationToken", csrfToken); // Act var client = server.CreateClient(); - var initializeTransaction = - await client.PostAsync("/ActiveLogin/BankId/Auth/Api/Initialize", initializeRequest); + var initializeTransaction = await client.PostAsync("/ActiveLogin/BankId/Auth/Api/Initialize", initializeRequest); var initializeResponseContent = await initializeTransaction.Content.ReadAsStringAsync(); - var initializeObject = JsonConvert.DeserializeAnonymousType(initializeResponseContent, - new {RedirectUri = "", OrderRef = "", IsAutoLaunch = false}); + var initializeObject = JsonConvert.DeserializeAnonymousType(initializeResponseContent, new { RedirectUri = "", OrderRef = "", IsAutoLaunch = false }); var cancelRequest = new JsonContent(new { - orderRef = initializeObject.OrderRef, uiOptions = "TestOptions", cancelReturnUrl = "/" + orderRef = initializeObject.OrderRef, + uiOptions = "TestOptions", + cancelReturnUrl = "/" }); cancelRequest.Headers.Add("Cookie", loginCookies); cancelRequest.Headers.Add("RequestVerificationToken", csrfToken); @@ -642,8 +652,8 @@ private static Action DefaultAppConfiguration(Func GetInitializeResponse(TestServer server, object initializeRequestBody) + private Task GetInitializeResponse(TestServer server, object initializeRequestBody, params (string, string)[] cookies) { - return GetInitializeResponse("Auth", server, initializeRequestBody); + return GetInitializeResponse("Auth", server, initializeRequestBody, cookies); } } diff --git a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiPayment_Tests.cs b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiPayment_Tests.cs index 6ef57324..e96119df 100644 --- a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiPayment_Tests.cs +++ b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiPayment_Tests.cs @@ -9,11 +9,11 @@ using ActiveLogin.Authentication.BankId.Api; using ActiveLogin.Authentication.BankId.Api.Models; using ActiveLogin.Authentication.BankId.AspNetCore.Areas.ActiveLogin.Models; +using ActiveLogin.Authentication.BankId.AspNetCore.Auth; using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; using ActiveLogin.Authentication.BankId.AspNetCore.Models; using ActiveLogin.Authentication.BankId.AspNetCore.Payment; using ActiveLogin.Authentication.BankId.AspNetCore.Test.Helpers; -using ActiveLogin.Authentication.BankId.AspNetCore.UserContext.Device.State; using ActiveLogin.Authentication.BankId.Core; using ActiveLogin.Authentication.BankId.Core.CertificatePolicies; using ActiveLogin.Authentication.BankId.Core.Launcher; @@ -40,38 +40,27 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.Test; public class BankId_UiPayment_Tests : BankId_Ui_Tests_Base { - private const string DefaultStateCookieName = "__ActiveLogin.BankIdUiState"; - - private readonly Mock _bankIdUiOrderRefProtector; - private readonly Mock _bankIdUiOptionsProtector; - private readonly Mock _bankIdUiStateProtector; + private readonly Mock> _bankIdUiOrderRefProtector; + private readonly Mock> _bankIdUiOptionsProtector; + private readonly BankIdUiPaymentState _paymentState = new("configKey", new BankIdPaymentProperties(TransactionType.npa, "Test Merchant")); public BankId_UiPayment_Tests() { - _bankIdUiOptionsProtector = new Mock(); + _bankIdUiOptionsProtector = new Mock>(); _bankIdUiOptionsProtector .Setup(protector => protector.Unprotect(It.IsAny())) - .Returns(new BankIdUiOptions(new List(), false, false, false, false, "/", DefaultStateCookieName, Api.Models.CardReader.class1)); + .Returns(new BankIdUiOptions([], false, false, false, false, "/", PaymentStateKeyCookieName, CardReader.class1)); _bankIdUiOptionsProtector .Setup(protector => protector.Protect(It.IsAny())) .Returns("Ignored"); - _bankIdUiOrderRefProtector = new Mock(); + _bankIdUiOrderRefProtector = new Mock>(); _bankIdUiOrderRefProtector .Setup(protector => protector.Unprotect(It.IsAny())) .Returns(new BankIdUiOrderRef("Any")); _bankIdUiOrderRefProtector .Setup(protector => protector.Protect(It.IsAny())) .Returns("Any"); - - var paymentState = new BankIdUiPaymentState("configKey", new BankIdPaymentProperties(TransactionType.npa, "Test Merchant")); - _bankIdUiStateProtector = new Mock(); - _bankIdUiStateProtector - .Setup(protector => protector.Unprotect(It.IsAny())) - .Returns(paymentState); - _bankIdUiStateProtector - .Setup(protector => protector.Protect(It.IsAny())) - .Returns("Ignored"); } [Fact] @@ -85,6 +74,7 @@ public async Task BankIdUiPaymentController_Returns_404_If_BankId_Is_Not_Registe { services.AddMvc(); }); + using var client = new TestServer(webHostBuilder).CreateClient(); // Act @@ -105,6 +95,7 @@ public async Task BankIdUiPaymentApiController_Returns_404_If_BankId_Is_Not_Regi { services.AddMvc(); }); + using var client = new TestServer(webHostBuilder).CreateClient(); // Act @@ -206,29 +197,24 @@ public async Task Payment_UI_Should_Be_Accessible_Even_When_Site_Requires_Auth() .Build(); config.Filters.Add(new AuthorizeFilter(policy)); }); - - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); }); - // Act - var request = CreateRequestWithFakeStateCookie( server, "/ActiveLogin/BankId/Payment?returnUrl=%2F&uiOptions=X&orderRef=Y"); - var transaction = await request.GetAsync(); - + // Act - Use helper to create request with default UI options + var response = await CreateBankIdUiRequest( + server, + "/ActiveLogin/BankId/Payment", + PaymentStateKeyCookieName, + _paymentState + ); // Assert - Assert.Equal(HttpStatusCode.OK, transaction.StatusCode); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); } [Fact] public async Task PaymentInit_Returns_Ui_With_Resolved_Cancel_Url() { // Arrange - var options = new BankIdUiOptions(new List(), true, false, false, false, "~/cru", DefaultStateCookieName, CardReader.class1); - _bankIdUiOptionsProtector - .Setup(protector => protector.Unprotect(It.IsAny())) - .Returns(options); - using var server = CreateServer(o => { o.UseSimulatedEnvironment(); @@ -239,29 +225,40 @@ public async Task PaymentInit_Returns_Ui_With_Resolved_Cancel_Url() }, DefaultAppConfiguration(async context => { - await context.ChallengeAsync(BankIdPaymentDefaults.SameDeviceConfigKey); + var bankIdPaymentService = context.RequestServices.GetRequiredService(); + await bankIdPaymentService.InitiatePaymentAsync(new BankIdPaymentProperties(TransactionType.npa, "Test"), "/al-payment-cb", BankIdPaymentDefaults.SameDeviceConfigKey); }), services => { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); }); - // Act - var request = CreateRequestWithFakeStateCookie(server, "/ActiveLogin/BankId/Payment?returnUrl=%2F&uiOptions=X&orderRef=Y"); - var transaction = await request.GetAsync(); + // Create UI options with specific cancel URL for this test scenario + var uiOptions = CreateUiOptions( + PaymentStateKeyCookieName, + cancelReturnUrl: "~/cru" // This is what we want to test + ); + + // Act - Use helper with specific UI options + var response = await CreateBankIdUiRequest( + server, + "/ActiveLogin/BankId/Payment", + PaymentStateKeyCookieName, + _paymentState, + uiOptions + ); // Assert - Assert.Equal(HttpStatusCode.OK, transaction.StatusCode); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var transactionContent = await transaction.Content.ReadAsStringAsync(); - Assert.Equal("/cru", GetInlineJsonValue(transactionContent, "cancelReturnUrl")); + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal("/cru", GetInlineJsonValue(content, "cancelReturnUrl")); } [Fact] public async Task PaymentInit_Returns_Ui_With_Script() { // Arrange + var stateKey = StateKey.New(); using var server = CreateServer(o => { o.UseSimulatedEnvironment(); @@ -272,17 +269,12 @@ public async Task PaymentInit_Returns_Ui_With_Script() }, DefaultAppConfiguration(async context => { - await context.ChallengeAsync(BankIdPaymentDefaults.SameDeviceConfigKey); - }), - services => - { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); - }); + var bankIdPaymentService = context.RequestServices.GetRequiredService(); + await bankIdPaymentService.InitiatePaymentAsync(new BankIdPaymentProperties(TransactionType.npa, "Test"), "/al-payment-cb", BankIdPaymentDefaults.SameDeviceConfigKey); + })); // Act - var request = CreateRequestWithFakeStateCookie(server, "/ActiveLogin/BankId/Payment?returnUrl=%2F&uiOptions=X&orderRef=Y"); - var transaction = await request.GetAsync(); + var transaction = await CreateBankIdUiRequest(server, "/ActiveLogin/BankId/Payment", PaymentStateKeyCookieName, _paymentState); // Assert Assert.Equal(HttpStatusCode.OK, transaction.StatusCode); @@ -296,12 +288,13 @@ public async Task PaymentInit_Returns_Ui_With_Script() Assert.Equal("/", GetInlineJsonValue(transactionContent, "returnUrl")); Assert.Equal("/", GetInlineJsonValue(transactionContent, "cancelReturnUrl")); - Assert.Equal("X", GetInlineJsonValue(transactionContent, "uiOptionsGuid")); + Assert.NotEmpty(GetInlineJsonValue(transactionContent, "uiOptionsGuid")); // Verify real protected options are present } [Fact] public async Task PaymentInit_Preserves_UI_Options() { + var stateKey = StateKey.New(); // Arrange using var server = CreateServer(o => { @@ -313,25 +306,19 @@ public async Task PaymentInit_Preserves_UI_Options() }, DefaultAppConfiguration(async context => { - await context.ChallengeAsync(BankIdPaymentDefaults.SameDeviceConfigKey); - }), - services => - { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); - }); + var bankIdPaymentService = context.RequestServices.GetRequiredService(); + await bankIdPaymentService.InitiatePaymentAsync(new BankIdPaymentProperties(TransactionType.npa, "Test"), "/al-payment-cb", BankIdPaymentDefaults.SameDeviceConfigKey); + })); // Act - var request = - CreateRequestWithFakeStateCookie(server, "/ActiveLogin/BankId/Payment?returnUrl=%2F&uiOptions=UIOPTIONS&orderRef=Y"); - var transaction = await request.GetAsync(); + var transaction = await CreateBankIdUiRequest(server, "/ActiveLogin/BankId/Payment", PaymentStateKeyCookieName, _paymentState); // Assert Assert.Equal(HttpStatusCode.OK, transaction.StatusCode); var transactionContent = await transaction.Content.ReadAsStringAsync(); - Assert.Equal("UIOPTIONS", GetInlineJsonValue(transactionContent, "uiOptionsGuid")); + Assert.NotEmpty(GetInlineJsonValue(transactionContent, "uiOptionsGuid")); // Verify real protected options are present } [Fact] @@ -348,11 +335,12 @@ public async Task PaymentInit_Requires_State_Cookie_To_Be_Present() }, DefaultAppConfiguration(async context => { - await context.ChallengeAsync(BankIdPaymentDefaults.SameDeviceConfigKey); + var bankIdPaymentService = context.RequestServices.GetRequiredService(); + await bankIdPaymentService.InitiatePaymentAsync(new BankIdPaymentProperties(TransactionType.npa, "Test"), "/al-payment-cb", BankIdPaymentDefaults.SameDeviceConfigKey); }), services => { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); + services.AddSingleton(s => _bankIdUiOptionsProtector.Object); }); // Act @@ -369,8 +357,9 @@ public async Task PaymentInit_Requires_State_Cookie_To_Be_Present() public async Task AutoLaunch_Sets_Correct_RedirectUri() { // Arrange mocks - var autoLaunchOptions = new BankIdUiOptions(new List(), true, false, false, false, string.Empty, DefaultStateCookieName, CardReader.class1); - var mockProtector = new Mock(); + var stateKey = StateKey.New(); + var autoLaunchOptions = new BankIdUiOptions([], true, false, false, false, string.Empty, PaymentStateKeyCookieName, CardReader.class1); + var mockProtector = new Mock>(); mockProtector .Setup(protector => protector.Unprotect(It.IsAny())) .Returns(autoLaunchOptions); @@ -394,17 +383,19 @@ public async Task AutoLaunch_Sets_Correct_RedirectUri() }), services => { - services.AddTransient(s => mockProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); + services.AddSingleton(s => mockProtector.Object); }); + // Create state cookies for the payment state + var stateCookies = CreateStateCookies(PaymentStateKeyCookieName, stateKey, _paymentState, server.Services); + // Arrange acting request var testReturnUrl = "/TestReturnUrl"; var testOptions = "TestOptions"; var initializeRequestBody = new { returnUrl = testReturnUrl, uiOptions = testOptions }; // Act - var initializeTransaction = await GetInitializeResponse(server, initializeRequestBody); + var initializeTransaction = await GetInitializeResponse(server, initializeRequestBody, stateCookies); // Assert Assert.Equal(HttpStatusCode.OK, initializeTransaction.StatusCode); @@ -422,9 +413,9 @@ public async Task AutoLaunch_Sets_Correct_RedirectUri() public async Task Api_Always_Returns_CamelCase_Json_For_Http200Ok() { // Arrange mocks - var autoLaunchOptions = new BankIdUiOptions(new List(), false, false, false, false, string.Empty, DefaultStateCookieName, CardReader.class1); + var autoLaunchOptions = new BankIdUiOptions([], false, false, false, false, string.Empty, PaymentStateKeyCookieName, CardReader.class1); - var mockProtector = new Mock(); + var mockProtector = new Mock>(); mockProtector .Setup(protector => protector.Unprotect(It.IsAny())) .Returns(autoLaunchOptions); @@ -432,6 +423,8 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http200Ok() .Setup(protector => protector.Protect(It.IsAny())) .Returns("Ignored"); + var stateKey = StateKey.New(); + using var server = CreateServer( o => { @@ -448,14 +441,15 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http200Ok() }), services => { - services.AddTransient(s => mockProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); + services.AddSingleton(s => mockProtector.Object); services.AddMvc().AddJsonOptions(configure => { configure.JsonSerializerOptions.PropertyNamingPolicy = null; }); }); + // Create state cookies for the payment state + var stateCookies = CreateStateCookies(PaymentStateKeyCookieName, stateKey, _paymentState, server.Services); // Arrange acting request var testReturnUrl = "/TestReturnUrl"; @@ -463,7 +457,7 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http200Ok() var initializeRequestBody = new { returnUrl = testReturnUrl, uiOptions = testOptions }; //Act - var initializeTransaction = await GetInitializeResponse(server, initializeRequestBody); + var initializeTransaction = await GetInitializeResponse(server, initializeRequestBody, stateCookies); // Assert Assert.Equal(HttpStatusCode.OK, initializeTransaction.StatusCode); @@ -485,7 +479,8 @@ await bankIdPaymentService.InitiatePaymentAsync(new BankIdPaymentProperties(Tran public async Task Api_Always_Returns_CamelCase_Json_For_Http400BadRequest() { // Arrange mocks - var autoLaunchOptions = new BankIdUiOptions(new List(), false, false, false, false, string.Empty, DefaultStateCookieName, CardReader.class1); + var stateKey = StateKey.New(); + var autoLaunchOptions = new BankIdUiOptions(new List(), false, false, false, false, string.Empty, PaymentStateKeyCookieName, CardReader.class1); _bankIdUiOptionsProtector .Setup(protector => protector.Unprotect(It.IsAny())) .Returns(autoLaunchOptions); @@ -506,16 +501,17 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http400BadRequest() }), services => { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); + services.AddSingleton(s => _bankIdUiOptionsProtector.Object); }); + // Create state cookies for the payment state + var stateCookies = CreateStateCookies(PaymentStateKeyCookieName, stateKey, _paymentState, server.Services); // Arrange acting request var initializeRequestBody = new { }; //Act - var initializeTransaction = await GetInitializeResponse(server, initializeRequestBody); + var initializeTransaction = await GetInitializeResponse(server, initializeRequestBody, stateCookies); // Assert Assert.Equal(HttpStatusCode.BadRequest, initializeTransaction.StatusCode); @@ -530,7 +526,8 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http400BadRequest() public async Task Cancel_Calls_CancelApi() { // Arrange mocks - var autoLaunchOptions = new BankIdUiOptions(new List(), false, false, false, false, string.Empty, DefaultStateCookieName, CardReader.class1); + var stateKey = StateKey.New(); + var autoLaunchOptions = new BankIdUiOptions(new List(), false, false, false, false, string.Empty, PaymentStateKeyCookieName, CardReader.class1); _bankIdUiOptionsProtector .Setup(protector => protector.Unprotect(It.IsAny())) .Returns(autoLaunchOptions); @@ -552,12 +549,14 @@ public async Task Cancel_Calls_CancelApi() }), services => { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); + services.AddSingleton(s => _bankIdUiOptionsProtector.Object); services.AddTransient(s => _bankIdUiOrderRefProtector.Object); services.AddSingleton(s => testBankIdApi); }); + // Create state cookies for the payment state + var stateCookies = CreateStateCookies(PaymentStateKeyCookieName, stateKey, _paymentState, server.Services); + //Act var cancelRequest = new JsonContent(new { @@ -567,7 +566,7 @@ public async Task Cancel_Calls_CancelApi() }); // Act - var cancelTransaction = await MakeRequestWithRequiredContext("Payment", "/ActiveLogin/BankId/Payment/Api/Cancel", server, cancelRequest); + var cancelTransaction = await MakeRequestWithRequiredContext("Payment", "/ActiveLogin/BankId/Payment/Api/Cancel", server, cancelRequest, stateCookies); // Assert Assert.Equal(HttpStatusCode.OK, cancelTransaction.StatusCode); @@ -589,6 +588,8 @@ private TestServer CreateServer( .ConfigureServices(services => { services.AddBankId(configureBankId); + services.AddAuthentication() + .AddCookie(); services.AddBankIdPayment(configureBankIdPayment); services.AddMvc(); configureServices?.Invoke(services); @@ -596,7 +597,7 @@ private TestServer CreateServer( return new TestServer(webHostBuilder); } - + private static Action DefaultAppConfiguration(Func testpath) { return app => @@ -619,8 +620,9 @@ private static Action DefaultAppConfiguration(Func context.Response.WriteAsync("")); }; } - private Task GetInitializeResponse(TestServer server, object initializeRequestBody) + + private Task GetInitializeResponse(TestServer server, object initializeRequestBody, params (string, string)[] cookies) { - return GetInitializeResponse("Payment", server, initializeRequestBody); + return GetInitializeResponse("Payment", server, initializeRequestBody, cookies); } } diff --git a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiSign_Tests.cs b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiSign_Tests.cs index 65382ba2..f470ed5a 100644 --- a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiSign_Tests.cs +++ b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiSign_Tests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; @@ -13,12 +12,10 @@ using ActiveLogin.Authentication.BankId.AspNetCore.Sign; using ActiveLogin.Authentication.BankId.AspNetCore.Test.Helpers; using ActiveLogin.Authentication.BankId.Core; -using ActiveLogin.Authentication.BankId.Core.CertificatePolicies; using ActiveLogin.Authentication.BankId.Core.Launcher; using AngleSharp.Html.Dom; -using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -35,41 +32,11 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.Test; +using Cookie = (string Key, string Value); + public class BankId_UiSign_Tests : BankId_Ui_Tests_Base { - private const string DefaultStateCookieName = "__ActiveLogin.BankIdUiState"; - - private readonly Mock _bankIdUiOrderRefProtector; - private readonly Mock _bankIdUiOptionsProtector; - private readonly Mock _bankIdUiStateProtector; - - public BankId_UiSign_Tests() - { - _bankIdUiOptionsProtector = new Mock(); - _bankIdUiOptionsProtector - .Setup(protector => protector.Unprotect(It.IsAny())) - .Returns(new BankIdUiOptions(new List(), false, false, false, false, "/", DefaultStateCookieName, Api.Models.CardReader.class1)); - _bankIdUiOptionsProtector - .Setup(protector => protector.Protect(It.IsAny())) - .Returns("Ignored"); - - _bankIdUiOrderRefProtector = new Mock(); - _bankIdUiOrderRefProtector - .Setup(protector => protector.Unprotect(It.IsAny())) - .Returns(new BankIdUiOrderRef("Any")); - _bankIdUiOrderRefProtector - .Setup(protector => protector.Protect(It.IsAny())) - .Returns("Any"); - - var signState = new BankIdUiSignState("configKey", new BankIdSignProperties("userVisibleData")); - _bankIdUiStateProtector = new Mock(); - _bankIdUiStateProtector - .Setup(protector => protector.Unprotect(It.IsAny())) - .Returns(signState); - _bankIdUiStateProtector - .Setup(protector => protector.Protect(It.IsAny())) - .Returns("Ignored"); - } + private readonly BankIdUiSignState _signState = new("configKey", new BankIdSignProperties("userVisibleData")); [Fact] public async Task BankIdUiSignController_Returns_404_If_BankId_Is_Not_Registered() @@ -82,6 +49,7 @@ public async Task BankIdUiSignController_Returns_404_If_BankId_Is_Not_Registered { services.AddMvc(); }); + using var client = new TestServer(webHostBuilder).CreateClient(); // Act @@ -102,6 +70,7 @@ public async Task BankIdUiAuthApiController_Returns_404_If_BankId_Is_Not_Registe { services.AddMvc(); }); + using var client = new TestServer(webHostBuilder).CreateClient(); // Act @@ -115,14 +84,9 @@ public async Task BankIdUiAuthApiController_Returns_404_If_BankId_Is_Not_Registe public async Task InitiateSign_Redirects_To_Sign() { // Arrange - using var client = CreateServer(o => - { - o.UseSimulatedEnvironment(); - }, - o => - { - o.AddSameDevice(); - }, + using var client = CreateServer( + o => o.UseSimulatedEnvironment(), + o => o.AddSameDevice(), DefaultAppConfiguration(async context => { var bankIdSignService = context.RequestServices.GetRequiredService(); @@ -141,14 +105,9 @@ public async Task InitiateSign_Redirects_To_Sign() public async Task InitiateSignAsync_Redirects_To_Sign_Without_Path_Base() { // Arrange - using var client = CreateServer(o => - { - o.UseSimulatedEnvironment(); - }, - o => - { - o.AddSameDevice(); - }, + using var client = CreateServer( + o => o.UseSimulatedEnvironment(), + o => o.AddSameDevice(), app => { app.Map("/PathBase", appBuilder => @@ -185,16 +144,11 @@ public async Task InitiateSignAsync_Redirects_To_Sign_Without_Path_Base() public async Task Sign_UI_Should_Be_Accessible_Even_When_Site_Requires_Auth() { // Arrange - using var server = CreateServer(o => - { - o.UseSimulatedEnvironment(); - }, - o => - { - o.AddSameDevice(); - }, + using var server = CreateServer( + o => o.UseSimulatedEnvironment(), + o => o.AddSameDevice(), DefaultAppConfiguration(context => Task.CompletedTask), - services => + configureServices: services => { services.AddMvc(config => { @@ -203,15 +157,10 @@ public async Task Sign_UI_Should_Be_Accessible_Even_When_Site_Requires_Auth() .Build(); config.Filters.Add(new AuthorizeFilter(policy)); }); - - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); }); // Act - var request = CreateRequestWithFakeStateCookie( server, "/ActiveLogin/BankId/Sign?returnUrl=%2F&uiOptions=X&orderRef=Y"); - var transaction = await request.GetAsync(); - + var transaction = await CreateBankIdUiRequest(server, "/ActiveLogin/BankId/Sign", SignStateKeyCookieName, _signState); // Assert Assert.Equal(HttpStatusCode.OK, transaction.StatusCode); @@ -221,32 +170,15 @@ public async Task Sign_UI_Should_Be_Accessible_Even_When_Site_Requires_Auth() public async Task SignInit_Returns_Ui_With_Resolved_Cancel_Url() { // Arrange - var options = new BankIdUiOptions(new List(), true, false, false, false, "~/cru", DefaultStateCookieName, Api.Models.CardReader.class1); - _bankIdUiOptionsProtector - .Setup(protector => protector.Unprotect(It.IsAny())) - .Returns(options); + var uiOptions = CreateUiOptions(SignStateKeyCookieName, cancelReturnUrl: "/cru", sameDevice: true); - using var server = CreateServer(o => - { - o.UseSimulatedEnvironment(); - }, - o => - { - o.AddSameDevice(); - }, - DefaultAppConfiguration(async context => - { - await context.ChallengeAsync(BankIdSignDefaults.SameDeviceConfigKey); - }), - services => - { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); - }); + using var server = CreateServer( + o => o.UseSimulatedEnvironment(), + o => o.AddSameDevice(), + DefaultAppConfiguration(context => Task.CompletedTask)); // Act - var request = CreateRequestWithFakeStateCookie(server, "/ActiveLogin/BankId/Sign?returnUrl=%2F&uiOptions=X&orderRef=Y"); - var transaction = await request.GetAsync(); + var transaction = await CreateBankIdUiRequest(server, "/ActiveLogin/BankId/Sign", SignStateKeyCookieName, _signState, uiOptions); // Assert Assert.Equal(HttpStatusCode.OK, transaction.StatusCode); @@ -259,27 +191,15 @@ public async Task SignInit_Returns_Ui_With_Resolved_Cancel_Url() public async Task SignInit_Returns_Ui_With_Script() { // Arrange - using var server = CreateServer(o => - { - o.UseSimulatedEnvironment(); - }, - o => - { - o.AddSameDevice(); - }, - DefaultAppConfiguration(async context => - { - await context.ChallengeAsync(BankIdSignDefaults.SameDeviceConfigKey); - }), - services => - { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); - }); + var uiOptions = CreateUiOptions(SignStateKeyCookieName, sameDevice: true); + + using var server = CreateServer( + o => o.UseSimulatedEnvironment(), + o => o.AddSameDevice(), + DefaultAppConfiguration(context => Task.CompletedTask)); // Act - var request = CreateRequestWithFakeStateCookie(server, "/ActiveLogin/BankId/Sign?returnUrl=%2F&uiOptions=X&orderRef=Y"); - var transaction = await request.GetAsync(); + var transaction = await CreateBankIdUiRequest(server, "/ActiveLogin/BankId/Sign", SignStateKeyCookieName, _signState, uiOptions); // Assert Assert.Equal(HttpStatusCode.OK, transaction.StatusCode); @@ -293,71 +213,67 @@ public async Task SignInit_Returns_Ui_With_Script() Assert.Equal("/", GetInlineJsonValue(transactionContent, "returnUrl")); Assert.Equal("/", GetInlineJsonValue(transactionContent, "cancelReturnUrl")); - Assert.Equal("X", GetInlineJsonValue(transactionContent, "uiOptionsGuid")); + Assert.NotEmpty(GetInlineJsonValue(transactionContent, "uiOptionsGuid")); } [Fact] public async Task SignInit_Preserves_UI_Options() { + var stateKey = StateKey.New(); // Arrange - using var server = CreateServer(o => - { - o.UseSimulatedEnvironment(); - }, - o => - { - o.AddSameDevice(); - }, - DefaultAppConfiguration(async context => - { - await context.ChallengeAsync(BankIdSignDefaults.SameDeviceConfigKey); - }), - services => - { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); - }); + var uiOptions = CreateUiOptions(SignStateKeyCookieName, sameDevice: true); - // Act - var request = - CreateRequestWithFakeStateCookie(server, "/ActiveLogin/BankId/Sign?returnUrl=%2F&uiOptions=UIOPTIONS&orderRef=Y"); + using var server = CreateServer( + o => o.UseSimulatedEnvironment(), + o => o.AddSameDevice(), + DefaultAppConfiguration(context => Task.CompletedTask)); + + // Use the real protector to protect the UI options + var uiOptionsProtector = server.Services.GetRequiredService>(); + var protectedUiOptions = uiOptionsProtector.Protect(uiOptions); + + var cookies = CreateStateCookies(SignStateKeyCookieName, stateKey, _signState, server.Services); + var encodedProtectedOptions = Uri.EscapeDataString(protectedUiOptions); + var request = CreateRequestWithCookies(server, $"/ActiveLogin/BankId/Sign?returnUrl=%2F&uiOptions={encodedProtectedOptions}&orderRef=Y", cookies); var transaction = await request.GetAsync(); // Assert Assert.Equal(HttpStatusCode.OK, transaction.StatusCode); var transactionContent = await transaction.Content.ReadAsStringAsync(); + var responseProtectedOptions = GetInlineJsonValue(transactionContent, "uiOptionsGuid"); - Assert.Equal("UIOPTIONS", GetInlineJsonValue(transactionContent, "uiOptionsGuid")); + // Verify that protected UI options are preserved in the response + Assert.NotEmpty(responseProtectedOptions); + + // Verify we can unprotect the response and get back the original options + var unprotectedResponseOptions = uiOptionsProtector.Unprotect(responseProtectedOptions); + Assert.Equal(uiOptions.SameDevice, unprotectedResponseOptions.SameDevice); + Assert.Equal(uiOptions.StateKeyCookieName, unprotectedResponseOptions.StateKeyCookieName); } [Fact] public async Task SignInit_Requires_State_Cookie_To_Be_Present() { // Arrange - using var server = CreateServer(o => - { - o.UseSimulatedEnvironment(); - }, - o => - { - o.AddSameDevice(); - }, - DefaultAppConfiguration(async context => - { - await context.ChallengeAsync(BankIdSignDefaults.SameDeviceConfigKey); - }), - services => - { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - }); + var uiOptions = CreateUiOptions(SignStateKeyCookieName, sameDevice: true); - // Act - var request = server.CreateRequest("/ActiveLogin/BankId/Sign?returnUrl=%2F&uiOptions=X&orderRef=Y"); - request.AddHeader("Cookie", ""); + using var server = CreateServer( + o => o.UseSimulatedEnvironment(), + o => o.AddSameDevice(), + DefaultAppConfiguration(context => Task.CompletedTask)); + + // Use the real protector to protect the UI options + var uiOptionsProtector = server.Services.GetRequiredService>(); + var protectedUiOptions = uiOptionsProtector.Protect(uiOptions); + + // Act - Make request with valid UI options but NO state cookies + var encodedProtectedOptions = Uri.EscapeDataString(protectedUiOptions); + var request = server.CreateRequest($"/ActiveLogin/BankId/Sign?returnUrl=%2F&uiOptions={encodedProtectedOptions}&orderRef=Y"); + request.AddHeader("Cookie", ""); // Explicitly set empty cookies to ensure no state cookie is present var transaction = await request.GetAsync(); - // Assert + // Assert - Should redirect to cancel URL (/) when state cookie is missing Assert.Equal(HttpStatusCode.Redirect, transaction.StatusCode); Assert.Equal("/", transaction.Headers.Location.ToString()); } @@ -365,15 +281,17 @@ public async Task SignInit_Requires_State_Cookie_To_Be_Present() [Fact] public async Task AutoLaunch_Sets_Correct_RedirectUri() { - // Arrange mocks - var autoLaunchOptions = new BankIdUiOptions(new List(), true, false, false, false, string.Empty, DefaultStateCookieName, Api.Models.CardReader.class1); - var mockProtector = new Mock(); + // Arrange - This test focuses on redirect URI generation logic, so we mock the data protection + // to isolate the test from data protection key management complexity + var autoLaunchOptions = new BankIdUiOptions([], true, false, false, false, string.Empty, SignStateKeyCookieName, Api.Models.CardReader.class1); + var mockProtector = new Mock>(); mockProtector .Setup(protector => protector.Unprotect(It.IsAny())) .Returns(autoLaunchOptions); + mockProtector .Setup(protector => protector.Protect(It.IsAny())) - .Returns("Ignored"); + .Returns("TestProtectedOptions"); using var server = CreateServer( o => @@ -381,27 +299,23 @@ public async Task AutoLaunch_Sets_Correct_RedirectUri() o.UseSimulatedEnvironment(); o.Services.AddTransient(); }, - o => - { - o.AddSameDevice(); - }, - DefaultAppConfiguration(async context => - { - await InitiateSign(context); - }), - services => + o => o.AddSameDevice(), + DefaultAppConfiguration(InitiateSign), + configureServices: services => { services.AddTransient(s => mockProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); }); + var stateKey = StateKey.New(); + var stateCookies = CreateStateCookies(SignStateKeyCookieName, stateKey, _signState, server.Services); + // Arrange acting request var testReturnUrl = "/TestReturnUrl"; - var testOptions = "TestOptions"; + var testOptions = "TestProtectedOptions"; var initializeRequestBody = new { returnUrl = testReturnUrl, uiOptions = testOptions }; // Act - var initializeTransaction = await GetInitializeResponse(server, initializeRequestBody); + var initializeTransaction = await GetInitializeResponse(server, initializeRequestBody, stateCookies); // Assert Assert.Equal(HttpStatusCode.OK, initializeTransaction.StatusCode); @@ -418,16 +332,20 @@ public async Task AutoLaunch_Sets_Correct_RedirectUri() [Fact] public async Task Api_Always_Returns_CamelCase_Json_For_Http200Ok() { - // Arrange mocks - var autoLaunchOptions = new BankIdUiOptions(new List(), false, false, false, false, string.Empty, DefaultStateCookieName, Api.Models.CardReader.class1); + // Arrange - This test focuses on JSON serialization behavior, so we mock the data protection + // to isolate the test from data protection complexity and focus on camelCase formatting + var autoLaunchOptions = new BankIdUiOptions([], false, false, false, false, string.Empty, SignStateKeyCookieName, Api.Models.CardReader.class1); - var mockProtector = new Mock(); + var mockProtector = new Mock>(); mockProtector .Setup(protector => protector.Unprotect(It.IsAny())) .Returns(autoLaunchOptions); + mockProtector .Setup(protector => protector.Protect(It.IsAny())) - .Returns("Ignored"); + .Returns("TestProtectedOptions"); + + var stateKey = StateKey.New(); using var server = CreateServer( o => @@ -435,37 +353,34 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http200Ok() o.UseSimulatedEnvironment(); o.Services.AddTransient(); }, - o => - { - o.AddSameDevice(); - }, - DefaultAppConfiguration(async context => - { - await InitiateSign(context); - }), - services => + o => o.AddSameDevice(), + DefaultAppConfiguration(InitiateSign), + configureServices: services => { services.AddTransient(s => mockProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); services.AddMvc().AddJsonOptions(configure => { + // Deliberately disable camelCase to test that the API overrides this setting configure.JsonSerializerOptions.PropertyNamingPolicy = null; }); }); + // Create state cookies for the sign state + var stateCookies = CreateStateCookies(SignStateKeyCookieName, stateKey, _signState, server.Services); // Arrange acting request var testReturnUrl = "/TestReturnUrl"; - var testOptions = "TestOptions"; + var testOptions = "TestProtectedOptions"; var initializeRequestBody = new { returnUrl = testReturnUrl, uiOptions = testOptions }; - //Act - var initializeTransaction = await GetInitializeResponse(server, initializeRequestBody); + // Act + var initializeTransaction = await GetInitializeResponse(server, initializeRequestBody, stateCookies); // Assert Assert.Equal(HttpStatusCode.OK, initializeTransaction.StatusCode); var responseContent = await initializeTransaction.Content.ReadAsStringAsync(); + // Verify that API returns camelCase JSON even when global setting disables it Assert.Contains("redirectUri", responseContent); Assert.Contains("orderRef", responseContent); Assert.Contains("isAutoLaunch", responseContent); @@ -474,45 +389,46 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http200Ok() private static async Task InitiateSign(HttpContext context) { var bankIdSignService = context.RequestServices.GetRequiredService(); - await bankIdSignService.InitiateSignAsync(new BankIdSignProperties("UVD"), "/al-sign-cb", - BankIdSignDefaults.OtherDeviceConfigKey); + await bankIdSignService.InitiateSignAsync(new BankIdSignProperties("UVD"), "/al-sign-cb", BankIdSignDefaults.OtherDeviceConfigKey); } [Fact] public async Task Api_Always_Returns_CamelCase_Json_For_Http400BadRequest() { // Arrange mocks - var autoLaunchOptions = new BankIdUiOptions(new List(), false, false, false, false, string.Empty, DefaultStateCookieName, Api.Models.CardReader.class1); - _bankIdUiOptionsProtector + var autoLaunchOptions = new BankIdUiOptions([], false, false, false, false, string.Empty, SignStateKeyCookieName, Api.Models.CardReader.class1); + var mockProtector = new Mock>(); + mockProtector .Setup(protector => protector.Unprotect(It.IsAny())) .Returns(autoLaunchOptions); + mockProtector + .Setup(protector => protector.Protect(It.IsAny())) + .Returns("MockedProtectedOptions"); + + var stateKey = StateKey.New(); + using var server = CreateServer( o => { o.UseSimulatedEnvironment(); o.Services.AddTransient(); }, - o => - { - o.AddSameDevice(); - }, - DefaultAppConfiguration(async context => + o => o.AddSameDevice(), + DefaultAppConfiguration(InitiateSign), + configureServices: services => { - await InitiateSign(context); - }), - services => - { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); + services.AddTransient(s => mockProtector.Object); }); + // Create state cookies for the sign state + var stateCookies = CreateStateCookies(SignStateKeyCookieName, stateKey, _signState, server.Services); // Arrange acting request var initializeRequestBody = new { }; //Act - var initializeTransaction = await GetInitializeResponse(server, initializeRequestBody); + var initializeTransaction = await GetInitializeResponse(server, initializeRequestBody, stateCookies); // Assert Assert.Equal(HttpStatusCode.BadRequest, initializeTransaction.StatusCode); @@ -526,66 +442,82 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http400BadRequest() [Fact] public async Task Cancel_Calls_CancelApi() { - // Arrange mocks - var autoLaunchOptions = new BankIdUiOptions(new List(), false, false, false, false, string.Empty, DefaultStateCookieName, Api.Models.CardReader.class1); - _bankIdUiOptionsProtector + // Arrange - This test focuses on verifying that the Cancel API endpoint calls the underlying BankID API + // We mock the data protection components to isolate the test from data protection complexity + var uiOptions = new BankIdUiOptions([], false, false, false, false, string.Empty, SignStateKeyCookieName, Api.Models.CardReader.class1); + var mockOptionsProtector = new Mock>(); + mockOptionsProtector .Setup(protector => protector.Unprotect(It.IsAny())) - .Returns(autoLaunchOptions); + .Returns(uiOptions); + + mockOptionsProtector + .Setup(protector => protector.Protect(It.IsAny())) + .Returns("TestProtectedOptions"); + + var mockOrderRefProtector = new Mock>(); + mockOrderRefProtector + .Setup(protector => protector.Unprotect(It.IsAny())) + .Returns(new BankIdUiOrderRef("TestOrderRef")); + + mockOrderRefProtector + .Setup(protector => protector.Protect(It.IsAny())) + .Returns("TestProtectedOrderRef"); + + // Use a test API client that tracks whether CancelAsync was called var testBankIdApi = new TestBankIdAppApi(new BankIdSimulatedAppApiClient()); + var stateKey = StateKey.New(); + using var server = CreateServer( o => { o.UseSimulatedEnvironment(); o.Services.AddTransient(); }, - o => + o => o.AddSameDevice(), + DefaultAppConfiguration(InitiateSign), + configureServices: services => { - o.AddSameDevice(); - }, - DefaultAppConfiguration(async context => - { - await InitiateSign(context); - }), - services => - { - services.AddTransient(s => _bankIdUiOptionsProtector.Object); - services.AddTransient(s => _bankIdUiStateProtector.Object); - services.AddTransient(s => _bankIdUiOrderRefProtector.Object); + services.AddTransient(s => mockOptionsProtector.Object); + services.AddTransient(s => mockOrderRefProtector.Object); services.AddSingleton(s => testBankIdApi); }); - //Act + // Create state cookies for the sign state + var stateCookies = CreateStateCookies(SignStateKeyCookieName, stateKey, _signState, server.Services); + + // Act - Make a cancel request to the API var cancelRequest = new JsonContent(new { - orderRef = "ANY", - uiOptions = "TestOptions", + orderRef = "TestOrderRef", + uiOptions = "TestProtectedOptions", cancelReturnUrl = "/" }); - // Act - var cancelTransaction = await MakeRequestWithRequiredContext("Sign", "/ActiveLogin/BankId/Sign/Api/Cancel", server, cancelRequest); + var cancelTransaction = await MakeRequestWithRequiredContext("Sign", "/ActiveLogin/BankId/Sign/Api/Cancel", server, cancelRequest, stateCookies); - // Assert + // Assert - Verify that the API was called and the cancel method was invoked Assert.Equal(HttpStatusCode.OK, cancelTransaction.StatusCode); Assert.True(testBankIdApi.CancelAsyncIsCalled); } - private TestServer CreateServer( + private static TestServer CreateServer( Action configureBankId, Action configureBankIdSign, - Action configureApplication, + Action configureApplication = null, Action configureServices = null) { var webHostBuilder = new WebHostBuilder() .UseSolutionRelativeContentRoot(Path.Combine("test", "ActiveLogin.Authentication.BankId.AspNetCore.Test")) .Configure(app => { - configureApplication.Invoke(app); + configureApplication?.Invoke(app); }) .ConfigureServices(services => { services.AddBankId(configureBankId); + services.AddAuthentication() + .AddCookie(); services.AddBankIdSign(configureBankIdSign); services.AddMvc(); configureServices?.Invoke(services); @@ -616,8 +548,8 @@ private static Action DefaultAppConfiguration(Func context.Response.WriteAsync("")); }; } - private Task GetInitializeResponse(TestServer server, object initializeRequestBody) + private Task GetInitializeResponse(TestServer server, object initializeRequestBody, params Cookie[] cookies) { - return GetInitializeResponse("Sign", server, initializeRequestBody); + return GetInitializeResponse("Sign", server, initializeRequestBody, cookies); } } diff --git a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_Ui_Tests_Base.cs b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_Ui_Tests_Base.cs index 44ef81af..fc3d3b19 100644 --- a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_Ui_Tests_Base.cs +++ b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_Ui_Tests_Base.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; @@ -7,82 +8,73 @@ using ActiveLogin.Authentication.BankId.Api; using ActiveLogin.Authentication.BankId.Api.Models; using ActiveLogin.Authentication.BankId.AspNetCore.Test.Helpers; - +using ActiveLogin.Authentication.BankId.Core; +using ActiveLogin.Authentication.BankId.Core.CertificatePolicies; +using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; +using ActiveLogin.Authentication.BankId.AspNetCore.Models; using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; namespace ActiveLogin.Authentication.BankId.AspNetCore.Test; +using Cookie = (string Key, string Value); + public abstract class BankId_Ui_Tests_Base { - private const string DefaultStateCookieName = "__ActiveLogin.BankIdUiState"; + protected const string AuthStateKeyCookieName = "__ActiveLogin.BankIdAuthStateKey"; + protected const string SignStateKeyCookieName = "__ActiveLogin.BankIdUiSignStateKey"; + protected const string PaymentStateKeyCookieName = "__ActiveLogin.BankIdUiPaymentStateKey"; - protected class TestBankIdAppApi : IBankIdAppApiClient - { - private readonly IBankIdAppApiClient _bankIdAppApiClient; + protected class TestBankIdAppApi( + IBankIdAppApiClient bankIdAppApiClient + ) : IBankIdAppApiClient + { public bool CancelAsyncIsCalled { get; private set; } - public TestBankIdAppApi(IBankIdAppApiClient bankIdAppApiClient) - { - _bankIdAppApiClient = bankIdAppApiClient; - } - - public Task AuthAsync(AuthRequest request) - { - return _bankIdAppApiClient.AuthAsync(request); - } - - public Task SignAsync(SignRequest request) - { - return _bankIdAppApiClient.SignAsync(request); - } - - public Task PaymentAsync(PaymentRequest request) - { - return _bankIdAppApiClient.PaymentAsync(request); - } - - public Task PhoneAuthAsync(PhoneAuthRequest request) - { - return _bankIdAppApiClient.PhoneAuthAsync(request); - } - - public Task PhoneSignAsync(PhoneSignRequest request) - { - return _bankIdAppApiClient.PhoneSignAsync(request); - } - - public Task CollectAsync(CollectRequest request) - { - return _bankIdAppApiClient.CollectAsync(request); - } - + public Task AuthAsync(AuthRequest request) => bankIdAppApiClient.AuthAsync(request); + public Task SignAsync(SignRequest request) => bankIdAppApiClient.SignAsync(request); + public Task PaymentAsync(PaymentRequest request) => bankIdAppApiClient.PaymentAsync(request); + public Task PhoneAuthAsync(PhoneAuthRequest request) => bankIdAppApiClient.PhoneAuthAsync(request); + public Task PhoneSignAsync(PhoneSignRequest request) => bankIdAppApiClient.PhoneSignAsync(request); + public Task CollectAsync(CollectRequest request) => bankIdAppApiClient.CollectAsync(request); public Task CancelAsync(CancelRequest request) { CancelAsyncIsCalled = true; - return _bankIdAppApiClient.CancelAsync(request); + return bankIdAppApiClient.CancelAsync(request); } } - protected async Task MakeRequestWithRequiredContext(string bankIdType, string path, TestServer server, HttpContent content) + protected async Task MakeRequestWithRequiredContext(string bankIdType, string path, TestServer server, HttpContent content, params Cookie[] cookies) { var client = server.CreateClient(); // Arrange state cookie var stateRequest = server.CreateRequest("/ANYURL"); var stateResponse = await stateRequest.GetAsync(); - var stateCookies = stateResponse.Headers.GetValues("set-cookie"); + var stateCookies = stateResponse.Headers.TryGetValues("set-cookie", out var stateCookieValues) + ? stateCookieValues + : []; // Arrange csrf info - var loginRequest = CreateRequestWithFakeStateCookie(server, $"/ActiveLogin/BankId/{bankIdType}?returnUrl=%2F&uiOptions=X&orderRef=Y"); + + var loginRequestPath = $"/{BankIdConstants.Routes.ActiveLoginAreaName}/{BankIdConstants.Routes.BankIdPathName}/{bankIdType}?returnUrl=%2F&uiOptions=X&orderRef=Y"; + var loginRequest = CreateRequestWithCookies(server, loginRequestPath, cookies); var loginResponse = await loginRequest.GetAsync(); - var loginCookies = loginResponse.Headers.GetValues("set-cookie"); + var loginCookies = loginResponse.Headers.TryGetValues("set-cookie", out var cookieValues) ? cookieValues : new string[0]; var loginContent = await loginResponse.Content.ReadAsStringAsync(); var csrfToken = GetRequestVerificationToken(loginContent); // Arrange acting request var allCookies = new List(stateCookies); allCookies.AddRange(loginCookies); + + // Add the original cookies that were passed to the method + foreach (var (key, value) in cookies) + { + allCookies.Add($"{key}={value}"); + } + allCookies = allCookies.Distinct().ToList(); var request = content; @@ -92,16 +84,31 @@ protected async Task MakeRequestWithRequiredContext(string return await client.PostAsync(path, request); } - protected async Task GetInitializeResponse(string bankIdType, TestServer server, object initializeRequestBody) + protected async Task GetInitializeResponse(string bankIdType, TestServer server, object initializeRequestBody, params (string key, string value)[] cookies) { var initializeRequest = new JsonContent(initializeRequestBody); - return await MakeRequestWithRequiredContext(bankIdType, $"/ActiveLogin/BankId/{bankIdType}/Api/Initialize", server, initializeRequest); + + var path = string.Format("/{0}/{1}/{2}/{3}/{4}", + BankIdConstants.Routes.ActiveLoginAreaName, + BankIdConstants.Routes.BankIdPathName, + bankIdType, + BankIdConstants.Routes.BankIdApiControllerPath, + BankIdConstants.Routes.BankIdApiInitializeActionName); + return await MakeRequestWithRequiredContext(bankIdType, path, server, initializeRequest, cookies); } - protected static RequestBuilder CreateRequestWithFakeStateCookie(TestServer server, string path) + protected static RequestBuilder CreateRequestWithCookies( + TestServer server, + string path, + params Cookie[] cookies + ) { var request = server.CreateRequest(path); - request.AddHeader("Cookie", $"{DefaultStateCookieName}=TEST"); + if (cookies.Length > 0) + { + var cookieHeader = string.Join("; ", cookies.Select(c => $"{c.Key}={c.Value}")); + request.AddHeader("Cookie", cookieHeader); + } return request; } @@ -113,11 +120,97 @@ protected static string GetRequestVerificationToken(string html) protected static string GetInlineJsonValue(string html, string key) { var match = Regex.Match(html, @"""" + key + @"""[:]\s""(.*)"""); - if (match.Success) - { - return match.Groups[1].Value; - } + return match.Success + ? match.Groups[1].Value + : string.Empty; + } + + /// + /// Creates both cookies needed for the cookie-based state storage: + /// 1. The state key cookie (e.g., PaymentStateKeyCookieName) containing the StateKey + /// 2. The actual state cookie (named with the StateKey) containing the serialized state + /// + protected static Cookie[] CreateStateCookies( + string stateKeyCookieName, + StateKey stateKey, + T state, + IServiceProvider serviceProvider + ) where T : Models.BankIdUiState + { + // Create the state protector to serialize the state data + var stateProtector = serviceProvider.GetRequiredService>(); + var serializedState = stateProtector.Protect(state); + + return + [ + (stateKeyCookieName, stateKey.Key), // Cookie 1: state key cookie + (CookieStateStorage.StateCookieName(stateKey), serializedState) // Cookie 2: state data cookie - return string.Empty; + ]; + } + + /// + /// Creates a BankID UI request with proper state and UI options setup. + /// This helper reduces boilerplate by handling the common pattern of: + /// 1. Creating UI options + /// 2. Creating both required state cookies (state key + state data) + /// 3. Protecting UI options + /// 4. Building the URL with proper parameters + /// + protected static async Task CreateBankIdUiRequest( + TestServer server, + string path, + string stateKeyCookieName, + T state, + BankIdUiOptions uiOptions = null, + string returnUrl = "/", + string orderRef = "Y" + ) where T : Models.BankIdUiState + { + uiOptions ??= new BankIdUiOptions( + [], + true, false, false, false, + returnUrl, + stateKeyCookieName, + CardReader.class1 + ); + + var uiOptionsProtector = server.Services.GetRequiredService>(); + var protectedUiOptions = uiOptionsProtector.Protect(uiOptions); + + var encodedReturnUrl = Uri.EscapeDataString(returnUrl); + var encodedUiOptions = Uri.EscapeDataString(protectedUiOptions); + var fullUrl = $"{path}?returnUrl={encodedReturnUrl}&uiOptions={encodedUiOptions}&orderRef={orderRef}"; + + var stateKey = StateKey.New(); + var stateCookies = CreateStateCookies(stateKeyCookieName, stateKey, state, server.Services); + var request = CreateRequestWithCookies(server, fullUrl, stateCookies); + return await request.GetAsync(); + } + + /// + /// Simplified helper for creating BankID UI options with commonly used parameters + /// + protected static BankIdUiOptions CreateUiOptions( + string stateKeyCookieName, + string cancelReturnUrl = "/", + bool sameDevice = true, + bool requirePinCode = false, + bool requireMrtd = false, + bool returnRisk = false, + CardReader cardReader = CardReader.class1, + List certificatePolicies = null + ) + { + return new BankIdUiOptions( + certificatePolicies ?? [], + sameDevice, + requirePinCode, + requireMrtd, + returnRisk, + cancelReturnUrl, + stateKeyCookieName, + cardReader + ); } } diff --git a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/UserContext/Device/Resolvers/BankIdDefaultEndUserWebDeviceDataResolver_Tests.cs b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/UserContext/Device/Resolvers/BankIdDefaultEndUserWebDeviceDataResolver_Tests.cs index 8a4bb80e..8717a989 100644 --- a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/UserContext/Device/Resolvers/BankIdDefaultEndUserWebDeviceDataResolver_Tests.cs +++ b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/UserContext/Device/Resolvers/BankIdDefaultEndUserWebDeviceDataResolver_Tests.cs @@ -22,7 +22,7 @@ namespace ActiveLogin.Authentication.BankId.AspNetCore.Test.UserContext.Device.R public class BankIdDefaultEndUserWebDeviceDataResolver_Tests { - private class FakeProtector : IBankIdDeviceDataProtector + private class FakeProtector : IBankIdDataStateProtector { public string Protect(DeviceDataState deviceDataState) => JsonSerializer.Serialize(deviceDataState); @@ -68,8 +68,6 @@ public DeviceDataState Unprotect(string protectedDeviceDataState) => return (new BankIdDefaultEndUserWebDeviceDataResolver(mockedAccessor.Object, fakeProtector), createdIdentifier); } - - [Fact] public void GetDeviceData_Returns_DefaultValues() { diff --git a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/UserContext/Device/UseDeviceDataExtensionTests.cs b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/UserContext/Device/UseDeviceDataExtensionTests.cs index 41ed7477..0a00331f 100644 --- a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/UserContext/Device/UseDeviceDataExtensionTests.cs +++ b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/UserContext/Device/UseDeviceDataExtensionTests.cs @@ -1,5 +1,6 @@ using ActiveLogin.Authentication.BankId.AspNetCore.Auth; using ActiveLogin.Authentication.BankId.AspNetCore.DataProtection; +using ActiveLogin.Authentication.BankId.AspNetCore.UserContext.Device.State; using ActiveLogin.Authentication.BankId.Core; using ActiveLogin.Authentication.BankId.Core.UserContext.Device; @@ -26,7 +27,7 @@ public void CanResolve_DefaultDeviceDataServices() Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService()); - Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService>()); }