The BankID packages has UI that uses classes from Bootstrap 4, please make sure these styles are available on the page for the expected UI.
BankID requires you to use a client certificate and trust a specific root CA-certificate.
- Read through the BankID Relying Party Guidelines. This ensures you have a basic understanding of the terminology as well as how the flow and security works.
- Download the SSL certificate for test (FPTestcert2.pfx).
- Contact a reseller to get your very own client certificate for production. This will probably take a few business days to get sorted. Please ask for "Direktupphandlad BankID" as they otherwise might refer you to GrandID.
- The root CA-certificates specified in BankID Relying Party Guidelines (#7 for Production and #8 for Test environment) needs to be trusted at the computer where the app will run. Save those certificates as
BankIdRootCertificate-Prod.crt
andBankIdRootCertificate-Test.crt
.- If running in Azure App Service, where trusting custom certificates is not supported, there are extensions to handle that scenario. Instead of trusting the certificate, place it in your web project and make sure
CopyToOutputDirectory
is set toAlways
. - Add the following configuration values. The
FilePath
should point to the certificate you just added, for example:
- If running in Azure App Service, where trusting custom certificates is not supported, there are extensions to handle that scenario. Instead of trusting the certificate, place it in your web project and make sure
{
"ActiveLogin:BankId:CaCertificate:FilePath": "Certificates\\BankIdRootCertificate-[Test or Prod].crt"
}
Note: When using MacOS or Linux, path strings use '/'
for subfolders: "Certificates/BankIdRootCertificate-[Test or Prod].crt"
These are only necessary if you plan to store your certificates in Azure KeyVault (recommended) and use the extension for easy integration with BankID.
- Deploy Azure KeyVault to your subscription. The ARM-template available in AzureProvisioningSample contains configuration that creates a KeyVault and enables Managed Service Identity for the App Service.
- Import the certificates to your Azure Key Vault.
- Add the following to your config, the secret identifier and auth settings.
{
"ActiveLogin:BankId:ClientCertificate": {
"UseManagedIdentity": true,
"AzureAdTenantId": "",
"AzureAdClientId": "",
"AzureAdClientSecret": "",
"AzureKeyVaultUri": "TODO-ADD-YOUR-VALUE",
"AzureKeyVaultSecretName": "TODO-ADD-YOUR-VALUE"
}
}
When configuring the AzureKeyVaultSecretName, the name is retrieved from the Certificates rather than Secrets in the Azure Portal. It is called a secret in the API since this is how Azure Key Vault exposes certificates with private keys.
You can read more about the reasoning behind this in this blog post or in the very extensive official documentation.
If possible, you should use Azure AD Managed Identity to connect to Azure KeyVault. To use Managed Identity, set UseManagedIdentity
to true
.
If Managed Identity can't be used, you can authenticate to Azure Key Vault using Client Credentials. Then set UseManagedIdentity
to false
and instead set values for AzureAdTenantId
, AzureAdClientId
and AzureAdClientSecret
.
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.
services
.AddAuthentication()
.AddBankId(builder =>
{
builder
.UseSimulatedEnvironment()
.AddSameDevice()
.AddOtherDevice()
.UseQrCoderQrCodeGenerator();
})
The faked name and personal identity number can also be customized like this.
services
.AddAuthentication()
.AddBankId(builder =>
{
builder
.UseSimulatedEnvironment("Alice", "Smith", "199908072391")
.AddSameDevice()
.AddOtherDevice()
.UseQrCoderQrCodeGenerator();
});
This will use the real REST API for BankID, connecting to either the Test or Production environment. It requires you to have the certificates described under Preparation above.
services.AddAuthentication()
.AddBankId(builder =>
{
builder
.UseProductionEnvironment()
...
});
These samples uses the production environment, to use the test environment, simply swap .UseProductionEnvironment()
with .UseTestEnvironment()
. You will also have to use a different client and root certificate, see info under Preparation above.
services.AddAuthentication()
.AddBankId(builder =>
{
builder
.UseTestEnvironment()
...
});
services.AddAuthentication()
.AddBankId(builder =>
{
builder
.UseProductionEnvironment()
.UseClientCertificateFromAzureKeyVault(Configuration.GetSection("ActiveLogin:BankId:ClientCertificate"))
...
});
services.AddAuthentication()
.AddBankId(builder =>
{
builder
.UseProductionEnvironment()
.UseClientCertificate(() => new X509Certificate2( ... ))
...
});
BankID uses a self signed root ca certificate that you need to trust. This is not possible in all scenarios, like in Azure App Service. To solve this there is an extension available to trust a custom root certificate using code. It can be used like this.
services.AddAuthentication()
.AddBankId(builder =>
{
builder
.UseProductionEnvironment()
.UseRootCaCertificate(Path.Combine(_environment.ContentRootPath, Configuration.GetValue<string>("ActiveLogin:BankId:CaCertificate:FilePath")))
...
});
- Same device: Launches the BankID app on the same device, no need to enter any personal identity number.
- Other device: You enter your personal identity number and can manually launch the app on your smartphone.
services
.AddAuthentication()
.AddBankId(builder =>
{
builder
.UseProductionEnvironment()
...
.AddSameDevice()
.AddOtherDevice();
});
By default, Add*Device
will use predefined schemas and display names, but they can be changed.
services
.AddAuthentication()
.AddBankId(builder =>
{
builder
.UseProductionEnvironment()
...
.AddSameDevice("custom-auth-scheme", "Custom display name", options => { ... })
.AddOtherDevice(BankIdDefaults.OtherDeviceAuthenticationScheme, "Custom display name", options => { ... });
});
If you want to roll your own, complete custom config, that can be done using .AddCustom()
.
services
.AddAuthentication()
.AddBankId(builder =>
{
builder
...
.AddCustom(options => {
options.BankIdAutoLaunch = true;
options.BankIdAllowChangingPersonalIdentityNumber = false;
});
});
BankId options allows you to set and override some options such as these.
.AddOtherDevice(options =>
{
// If the user can use biometric identification such as fingerprint or face recognition
options.BankIdAllowBiometric = false;
// Limit possible login methods to, for example, only allow BankID on smartcard.
options.BankIdCertificatePolicies = BankIdCertificatePolicies.GetPoliciesForProductionEnvironment(...);
// Issue birthdate claim based on data extracted from the personal identity number
options.IssueBirthdateClaim = true;
// Issue gender claim based on data extracted from the personal identity number
options.IssueGenderClaim = true;
// Turn off qr code and use personal identity number instead
options.BankIdUseQrCode = false;
});
If you want to apply some options for all BankID schemes, you can do so by using .Configure(...)
.
.Configure(options =>
{
options.IssueBirthdateClaim = true;
options.IssueGenderClaim = true;
});
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
- Other Device Scheme returns to PIN input when using PIN input instead of 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.
var props = new AuthenticationProperties
{
RedirectUri = Url.Action(nameof(ExternalLoginCallback)),
Items =
{
{ "returnUrl", "~/" },
{ "cancelReturnUrl", "~/some-custom-cancellation-url" },
{ "scheme", provider }
}
};
return Challenge(props, provider);
During the login flow, quite a lot of things are happening and using our event listeners you can listen and act on those events. By implementing and regestering IBankIdEventListener
you will be notified when an event occurs. A common scenario is logging.
BankIdEvent
is the base class for all events which all events will inherit from. Each event might (and in most cases will) have unique properties relevant for that specific event.
At the moment, we trigger these events:
- AspNet
BankIdAspNetChallengeSuccessEvent
BankIdAspNetAuthenticateSuccessEvent
BankIdAspNetAuthenticateFailureEvent
- Auth
BankIdAuthSuccessEvent
BankIdAuthErrorEvent
- Collect
BankIdCollectPendingEvent
BankIdCollectCompletedEvent
BankIdCollectFailureEvent
BankIdCollectErrorEvent
- Cancel
BankIdCancelSuccessEvent
BankIdCancelErrorEvent
public class BankIdSampleEventListener : IBankIdEventListener
{
public Task HandleAsync(BankIdEvent bankIdEvent)
{
Console.WriteLine($"{bankIdEvent.EventTypeName}: {bankIdEvent.EventSeverity}");
return Task.CompletedTask;
}
}
services
.AddAuthentication()
.AddBankId(builder =>
{
builder
//...
.AddEventListener<BankIdSampleEventListener>();
});
BankIdDebugEventListener
will listen for all events and write them as serialized JSON to the debug log using ILogger.LogDebug(...)
.
Call builder.AddDebugEventListener()
to enable it. Good to have for local development to see all details about what is happening.
services
.AddAuthentication()
.AddBankId(builder =>
{
builder
//...
.AddDebugEventListener();
});
BankIdApplicationInsightsEventListener
will listen for all events and write them to Application Insights.
Call builder.AddApplicationInsightsEventListener()
to enable it. Note that you can supply options to enable logging of metadata, such as personal identity number, age and IP.
Note: This event listener is available is available through a separate package called ActiveLogin.Authentication.BankId.AspNetCore.AzureMonitor
.
services
.AddAuthentication()
.AddBankId(builder =>
{
builder
//...
.AddApplicationInsightsEventListener();
});
BankIdDebugEventListener
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.
services
.AddAuthentication()
.AddBankId(builder =>
{
builder
//...
.AddDebugEventListener();
});
By default, two event listeners will be enabled:
BankIdLoggerEventListener
(Log all events toILogger
)BankIdResultStoreEventListener
(Map the completion event forIBankIdResultStore
, see info below under Store data on auth completion.)
If you want to remove those implementations, remove any class implementing IBankIdEventListener
from the ASP.NET Core services in your Startup.cs
:
services.RemoveAll(typeof(IBankIdEventListener));
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.
There is a shorthand method (AddResultStore
) on the BankIdBuilder to register the implementation.
Note: IBankIdResultStore
is just a shorthand for the BankIdCollectCompletedEvent
as described above.
Sample implementation:
public class BankIdResultSampleLoggerStore : IBankIdResultStore
{
private readonly EventId _eventId = new EventId(101, "StoreCollectCompletedCompletionData");
private readonly ILogger<BankIdResultTraceLoggerStore> _logger;
public BankIdResultSampleLoggerStore(ILogger<BankIdResultTraceLoggerStore> logger)
{
_logger = logger;
}
public Task StoreCollectCompletedCompletionData(string orderRef, CompletionData completionData)
{
_logger.LogTrace(_eventId, "Storing completion data for OrderRef '{OrderRef}' (UserPersonalIdentityNumber: '{UserPersonalIdentityNumber}')", orderRef, completionData.User.PersonalIdentityNumber);
return Task.CompletedTask;
}
}
services
.AddAuthentication()
.AddBankId(builder =>
{
builder
//...
.AddResultStore<BankIdResultSampleLoggerStore>();
});
The default implementation will log all data to the tracelog. If you want to remove that implementation, remove any class implementing IBankIdResultStore
from the ASP.NET Core services in your Startup.cs
:
services.RemoveAll(typeof(IBankIdResultStore));
In some scenarios, like running behind a proxy, you might want to resolve the end user IP yourself and override the default implementaion.
Either register a class implementing IEndUserIpResolver
:
builder.UseEndUserIpResolver<EndUserIpResolver>();
Or use the shorthand version:
builder.UseEndUserIpResolver(httpContext =>
{
return httpContext.Connection.RemoteIpAddress.ToString();
});
Finally, a full sample on how to use BankID in production with client certificate from Azure KeyVault and trusting a custom root certificate.
services
.AddAuthentication()
.AddBankId(builder =>
{
builder
.UseProductionEnvironment()
.UseClientCertificateFromAzureKeyVault(Configuration.GetSection("ActiveLogin:BankId:ClientCertificate"))
.UseRootCaCertificate(Path.Combine(_environment.ContentRootPath, Configuration.GetValue<string>("ActiveLogin:BankId:CaCertificate:FilePath")))
.AddSameDevice(options => { })
.AddOtherDevice(options => { })
.UseQrCoderQrCodeGenerator();
});
By default the ActiveLogin.Authentication.BankId.AspNetCore.Qr
package is needed to generate QR codes using the UseQrCoderQrCodeGenerator
extension method.
If you wish to provide your own implementation of QR code generation simply implement the IBankIdQrCodeGenerator
interface and add your implementation as a service.
services.AddTransient<IBankIdQrCodeGenerator, CustomQrCodeGenerator>();
BankId options allows you to set a list of certificate policies and there is a class available to help you out with this.
.AddOtherDevice(options =>
{
options.BankIdCertificatePolicies = BankIdCertificatePolicies.GetPoliciesForProductionEnvironment(BankIdCertificatePolicy.BankIdOnFile, BankIdCertificatePolicy.MobileBankId);
});
Because the policies have different values for test and production environment, you need to use either .GetPoliciesForProductionEnvironment()
or .GetPoliciesForTestEnvironment()
depending on what environment you are using.
Example:
.AddOtherDevice(options =>
{
var policies = new[] { BankIdCertificatePolicy.BankIdOnFile, BankIdCertificatePolicy.MobileBankId };
if(isProductionEnvironment) {
options.BankIdCertificatePolicies = BankIdCertificatePolicies.GetPoliciesForProductionEnvironment(policies);
} else {
options.BankIdCertificatePolicies = BankIdCertificatePolicies.GetPoliciesForTestEnvironment(policies);
}
});
With the current architecture of Active Login all services are registered "globally" and you can't call .AddBankId()
more than once.
To run Active Login in a multi tenant scenario, where different customers should use different certificates, you could register multiple certificates and on runtime select the correct one per request.
With our current solution, this requires you to disable pooling of the SocketsHttpHandler
so we've decided not to ship that code in the NuGet-package, but below you'll find a sample on how it could be configured. We hope to redesign this in the future.
Note: The code below is a sample and because it disables PooledConnection
it might (and will) have performance implications.
internal static class BankIdBuilderExtensions
{
public static IBankIdBuilder UseClientCertificateResolver(this IBankIdBuilder builder, Func<ServiceProvider, X509CertificateCollection, string, X509Certificate> configureClientCertificateResolver)
{
builder.ConfigureHttpClientHandler(httpClientHandler =>
{
var services = builder.AuthenticationBuilder.Services;
var serviceProvider = services.BuildServiceProvider();
httpClientHandler.PooledConnectionLifetime = TimeSpan.Zero;
httpClientHandler.SslOptions.LocalCertificateSelectionCallback =
(sender, host, certificates, certificate, issuers) => configureClientCertificateResolver(serviceProvider, certificates, host);
});
return builder;
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddAuthentication()
.AddBankId(builder =>
{
builder
.UseClientCertificateFromAzureKeyVault(Configuration.GetSection("ActiveLogin:BankId:ClientCertificate1"))
.UseClientCertificateFromAzureKeyVault(Configuration.GetSection("ActiveLogin:BankId:ClientCertificate2"))
.UseClientCertificateFromAzureKeyVault(Configuration.GetSection("ActiveLogin:BankId:ClientCertificate3"))
.UseClientCertificateResolver((serviceCollection, certificates, hostname) =>
{
// Apply logic here to select the correct certificate
return certificates[0];
});
// ...
}
}
}
The UI is bundled into the package as a Razor Class Library, a technique that allows to override the parts you want to customize. The Views and Controllers that can be customized can be found in the GitHub repo.
The messages are already localized to English and Swedish using the official recommended texts. To select what texts that are used you can for example use the localization middleware in ASP.NET Core.
X509Certificate2 can not be handled in the same way when running in Linux as on Windows. The certificate is Base64 encoded and must be decoded before creating the X509Certificate2 instance. Below is an example for the BankId root certificate:
Copy the content between Begin certificate and End certificate, and paste it into a resource string in a Resource.resx file.
With the certificate in the reosurce, this code can be used to create the X509Certificate2 instance. Note the second line that decodes the Base64 string.
var rootCertEncoded = CertificateResources.BankIdRootTestCertificate;
var rootCertBytes = Convert.FromBase64String(rootCertEncoded);
return new X509Certificate2(rootCertBytes, string.Empty, X509KeyStorageFlags.MachineKeySet);