Intro

Post logo

Have you ever been faced with a situation where you have started working with a new tool/package, read documentation and followed readme.md and still ended up not understanding how to start working with it all? Let’s just make it simple for once.

In this post I will guide you on how to use the ActiveLogin package. This demo will be exactly the same as the one I used on my conference talks at SweTugg and Microsoft Ignite the tour Stockholm.

If you’re interested in reading more about the process of creating ActiveLogin.Authentication read my previous post Introducing ActiveLogin or go to our official marketing place https://activelogin.net

This example is for demo purposes ONLY and aims at showing how you can start using the package. Readers should be advised that for a functioning production implementation you will be required to do more configurations and coding

Prerequisites

So are you up to giving it a try and want to play around a bit?

  • You don’t need an actual BankId certificate, for this example we will use a Simulated environment (more details later).

  • But of course you will need the latest .net core runtime, which is at the moment .Net Core 3.1.3 LTS

  • You can use any IDE or even the cli tools.

Preparation

We will start from the terminal to avoid differences in IDEs.

Let’s check if we have the right version of .net sdk with dotnet --info command:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
> dotnet --info
.NET Core SDK (reflecting any global.json):
 Version:   3.1.201
 Commit:    b1768b4ae7

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  10.15
 OS Platform: Darwin
 RID:         osx.10.15-x64
 Base Path:   /usr/local/share/dotnet/sdk/3.1.201/

Host (useful for support):
  Version: 3.1.3
  Commit:  4a9f85e9f8

.NET Core SDKs installed:
  2.1.302 [/usr/local/share/dotnet/sdk]
  2.2.103 [/usr/local/share/dotnet/sdk]
  3.1.201 [/usr/local/share/dotnet/sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.All 2.1.2 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.2.1 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.2 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.2.1 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.1.2 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.2.1 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

As you can see I use .net core on Mac with some older sdks. The fresh one is Version: 3.1.201

Scaffolding

  • we need to create a new project via cli: dotnet new mvc --output ActiveLoginDemo
1
2
3
4
5
6
7
8
9
> dotnet new mvc --output ActiveLoginDemo
The template "ASP.NET Core Web App (Model-View-Controller)" was created successfully.
This template contains technologies from parties other than Microsoft, see https://aka.ms/aspnetcore/3.1-third-party-notices for details.

Processing post-creation actions...
Running 'dotnet restore' on ActiveLoginDemo/ActiveLoginDemo.csproj...
  Restore completed in 136.01 ms for /Users/ActiveLoginDemo/ActiveLoginDemo.csproj.

Restore succeeded.

I use mvc template to scaffold some code and save time.

  • Lets switch to the new folder and add our package immediately
1
2
> cd ActiveLoginDemo
> dotnet add package ActiveLogin.Authentication.BankId.AspNetCore
  • You can open the project in any IDE, I will show you how to quickly open it in VisualStudio Code
1
> code .

Configuration

Our documentation is pretty big, but actually to start you just need to insert

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options => {
        options.LoginPath = new PathString("/Home/Login");
    })
    .AddBankId(builder =>
    {
        builder
            .UseSimulatedEnvironment()
            .AddSameDevice();
    });

into ConfigureService method inside Startup.cs. Which is basically just registering authentication services and specifying the default schema (line 1), registering cookie services and explicitly configuring the login path (line 2-4). And finally adding our provider with some configuration (line 5-10).

Since our extension is in Microsoft.Extensions.DependencyInjection we don’t need to resolve new using statements except of CookieAuthenticationDefaults and PathStrings. They require their own usings, but any IDE would suggest you the right namespace to add.

We also need to use authentication middleware before app.UseAuthorization(); in Configure method:

1
app.UseAuthentication();

Authentication flow

Technically we already have working code, but since we started from an empty (almost) project we need to add some more code to fulfil the OpenIdConnect authentication flow.

  1. First we need to add [Authorise] attribute above the class definition in our HomeController.cs file. Which will bring using as well.
1
2
3
4
using Microsoft.AspNetCore.Authorization;

[Authorize]
public class HomeController : Controller
  1. We will need one more parameter in HomeController constructor to be able to iterate through all registered authentication schemes:
1
2
3
4
5
6
7
8
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;

public HomeController(ILogger<HomeController> logger,
    IAuthenticationSchemeProvider authenticationSchemeProvider)
{
    _logger = logger;
    _authenticationSchemeProvider = authenticationSchemeProvider;
}

with using statement:

1
using Microsoft.AspNetCore.Authorization;
  1. And next the biggest amount of code that we will write today is; right after the last method in HomeController.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[AllowAnonymous]
public async Task<IActionResult> Login()
{
    var schemes = await _authenticationSchemeProvider.GetAllSchemesAsync();
    var providers = schemes
        .Where(x => x.DisplayName != null)
        .Select(x => {
            dynamic schema = new System.Dynamic.ExpandoObject();
            schema.DisplayName = x.DisplayName;
            schema.AuthenticationScheme = x.Name;
            return schema;
        });

    return View(providers);
}

[AllowAnonymous]
public IActionResult ExternalLogin(string provider)
{
    var props = new AuthenticationProperties
    {
        RedirectUri = Url.Action(nameof(ExternalLoginCallback)),
        Items =
        {
            {"returnUrl", "~/"},
            {"scheme", provider}
        }
    };

    return Challenge(props, provider);
}

[AllowAnonymous]
[HttpGet]
public async Task<IActionResult> ExternalLoginCallback()
{
    var result = await HttpContext.AuthenticateAsync();
    if (result?.Succeeded != true)
    {
        throw new Exception("External authentication error");
    }

    return Redirect("~/");
}

First method Login is just a simple Get action that shows all registered authentication schemes. ExternalLogin let us trigger Challenge with the selected scheme provider. And last one ExternalLoginCallback is the loop back when we finish the whole authentication flow and can issue the cookies with await HttpContext.AuthenticateAsync().

  1. Now since we mentioned the new page return View(providers); in Login method we need to add this view under Views\Home\Login.cshtml:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@model IEnumerable<dynamic>
@{
    Layout = "_Layout";
}
@{
    ViewData["Title"] = "Demo";
}

<style type="text/css">
    .choose-auth-provider {
        width: 100%;
        max-width: 320px;
        margin: 0 auto;
        text-align: center;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
    }
    .choose-auth-provider a {
        text-align: center;
    }
    .choose-auth-provider .choose-auth-provider-logo {
        width: 133px;
        height: 133px;
    }
</style>

@if (Model.Any())
{
    <div class="choose-auth-provider">
        <h1 class=" mb-5 mt-2 font-weight-normal">Active Login</h1>

        <ul class="list-inline">
            @foreach (var provider in Model)
            {
                <li class="mb-2">
                    <a class="btn btn-primary btn-block btn-lg"
                       asp-action="ExternalLogin"
                       asp-route-provider="@provider.AuthenticationScheme"
                       asp-route-returnUrl="~/">
                        @provider.DisplayName
                    </a>
                </li>
            }
        </ul>
    </div>
}

@if (!Model.Any())
{
    <div class="alert alert-warning">
        <strong>Invalid login request</strong>
        There are no login schemes configured for this client.
    </div>
}

Notice that I use a dynamic object for the page model but of course, in the real world, you should use a proper ViewModel class.

  1. One last (small) thing. When we finished the flow and authenticated whoever needs to be authenticated, we need to show some proof and verifications, right? :) So, what we do is, basically just show all the claims that our authenticated user has in Views\Home\Index.cshtml. I just replace the whole code again with:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@{
    ViewData["Title"] = "Claims";
}

<h2 class="h4 m2-1 mt-5 text-center">All claims</h2>
<div class="card">
    <div class="card-body">
        <dl>
            @foreach (var claim in User.Claims)
            {
                <dt>@claim.Type</dt>
                <dd>@claim.Value</dd>
            }
        </dl>
    </div>
</div>

Build and run

Ok, we are ready to go. Let’s build the project and start it. It will automatically redirect us to Home\Login page and ask us to choose the authentication provider: Choose provider We click BankID (Denna enhet) and enjoy the simulated request/response flow with BankID endpoint which leads us to the final page with our claims: Authentication flow

Simulated environment has some delays between responses and, tadaa, simulates all respones that real BankID endpoint would return.

Playing around

You may have noticed that we got automatically logged in as GivenName Surname with 199908072391 personal number. This is our hardcoded test user and test personal number. But you can also provide your own user in BankID provider registration in Startup.cs. We have several overloads so you can play around and choose what ever option suits you better:

1
.UseSimulatedEnvironment("Nikolay", "Test", "189711119819")

By the way, to avoid some guessing and problems with GDPR you can use a test data set from Skatteverket with 20 000 personal numbers that do not correlate with real people. A colleague of mine, and also an author and contributor to this package, created a nice website https://swedish.identityinfo.net/ where you can filter, validate and export the whole test data set for personal identity numbers and coordination numbers.

With existing code we added only one scheme: .AddSameDevice(); which launches (in not simulated environment) BankID application on the same device, lets add more options and tune them:

1
2
3
4
5
.AddSameDevice("bankid-samedevice", "same device super name", options => {
    options.IssueBirthdateClaim = true;
    options.IssueGenderClaim = true;
})
.AddOtherDevice();

Now we have two providers, one of them with custom name (note that we did not change any other code except of provider registration): Two providers If we choose the first option again we can see more claims: Two providers

All of the possible options are documented and you can find them on github.

Monitoring

With default logging settings you should be able to see trace logs from ActiveLogin. Here is my example from output:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/usr/local/share/dotnet/dotnet /Users/nikolayk/Desktop/ActiveLoginDemo/bin/Debug/netcoreapp3.1/ActiveLoginDemo.dll
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /Users/nikolayk/Desktop/ActiveLoginDemo
info: ActiveLogin.Authentication.BankId.AspNetCore.BankIdHandler[12]
      AuthenticationScheme: bankid-samedevice was challenged.
info: ActiveLogin.Authentication.BankId.AspNetCore.Areas.BankIdAuthentication.Controllers.BankIdApiController[211]
      BankID auth succeeded with the OrderRef 'f1a31192-19e8-4a66-ad79-1eb017bead2f'
info: ActiveLogin.Authentication.BankId.AspNetCore.Areas.BankIdAuthentication.Controllers.BankIdApiController[223]
      BankID collect is pending for OrderRef 'f1a31192-19e8-4a66-ad79-1eb017bead2f' with HintCode 'OutstandingTransaction'
info: ActiveLogin.Authentication.BankId.AspNetCore.Areas.BankIdAuthentication.Controllers.BankIdApiController[223]
      BankID collect is pending for OrderRef 'f1a31192-19e8-4a66-ad79-1eb017bead2f' with HintCode 'OutstandingTransaction'
info: ActiveLogin.Authentication.BankId.AspNetCore.Areas.BankIdAuthentication.Controllers.BankIdApiController[223]
      BankID collect is pending for OrderRef 'f1a31192-19e8-4a66-ad79-1eb017bead2f' with HintCode 'Started'
info: ActiveLogin.Authentication.BankId.AspNetCore.Areas.BankIdAuthentication.Controllers.BankIdApiController[223]
      BankID collect is pending for OrderRef 'f1a31192-19e8-4a66-ad79-1eb017bead2f' with HintCode 'Started'
info: ActiveLogin.Authentication.BankId.AspNetCore.Areas.BankIdAuthentication.Controllers.BankIdApiController[223]
      BankID collect is pending for OrderRef 'f1a31192-19e8-4a66-ad79-1eb017bead2f' with HintCode 'UserSign'
info: ActiveLogin.Authentication.BankId.AspNetCore.Areas.BankIdAuthentication.Controllers.BankIdApiController[224]
      BankID collect is completed for OrderRef 'f1a31192-19e8-4a66-ad79-1eb017bead2f'
info: ActiveLogin.Authentication.BankId.AspNetCore.BankIdHandler[111]
      BankID authentication ticket created

Look at namespaces ActiveLogin.Authentication.BankId.AspNetCore.BankIdHandler and ActiveLogin.Authentication.BankId.AspNetCore.Areas.BankIdAuthentication.Controllers.BankIdApiController.

Summary

I hope that with this post I show how easy it is to start working with BankID by using the ActiveLogin package, please try it, play with settings and give us feedback. As always you can find more documentation on github https://github.com/ActiveLogin/ActiveLogin.Authentication.

In the next topic I will show you how to use exactly the same code in test environment scenario with BankID app instead of a simulated environment.