Affinidi Login with .NET

In this guide, learn how to enable passwordless login in your application using .NET SDK 8.0 and the OpenID Connect library.

The Affinidi Login is a passwordless authentication solution that verifies user identity using Affinidi Vault as the identity provider managed by the end-user.

In this lab, we will use .NET SDK 8.0 with OpenID Connect module from the available sample applications to take you through the step-by-step guide for creating a Login Configuration and setting up the application to implement passwordless authentication for end-users.

Before you begin
  1. Set up Affinidi Vault account. Follow the guide below if you haven’t set it up yet.
Set up Affinidi Vault
  1. Set up an Affinidi Vault account using the web app or install the mobile app .

The same installation steps for mobile app.

  1. Click on Start if you are creating a new account, or click on Restore from Backup if you have an existing backup of your Affinidi Vault.

Use this guide to learn how to migrate your existing Affinidi Vault account.

Affinidi Vault Setup
  1. Secure your Vault by providing a passphrase. Use this passphrase to unlock your Vault.
Affinidi Vault Passphrase
  1. Provide your Email Address to verify with OTP.
Affinidi Vault Email Verification

After successfully providing the OTP, you are redirected to your Affinidi Vault dashboard.

  1. Get the Redirect URI of your application for OIDC. This is the URI configured on your Login Configuration to receive the idToken after successful authorisation.

  2. Optionally, Install the Affinidi CLI. Follow the guide below if it hasn’t been installed.

Set up Affinidi CLI
  1. Download and install NodeJS on your machine if you haven’t set it up yet.
  1. Install Affinidi CLI using Node Package Manager (npm).
npm install -g @affinidi/cli
  1. Verify that the installation is successful.
affinidi --version
  1. Install .NET SDK 8.0 on your machine if you haven’t installed yet using this guide .

Download Application

You can clone this sample application from our Github and start exploring how to integrate Affinidi Login to provide a passwordless login experience for your end-users.

Create Login Configuration

To create a Login Configuration, you can either use Affinidi CLI or  Affinidi Portal.

Expand the section below for your preferred method:

.NET Sample App Settings:

Name: dotNET App

Redirect URIs: http://localhost:5068/signin-oidc

Expand the section below for your preferred method:

Using Affinidi CLI
  1. Log in to Affinidi CLI by running:
affinidi start
  1. Once you have successfully logged in, create the Login Configuration by running:
affinidi login create-config --name='dotNET App' --redirect-uris='http://localhost:5068/signin-oidc'
  • --name is what you want your login configuration to be called.
  • --redirect-uris is the URL on your application where the user gets redirected after the successful authentication.

Learn more on how to manage your Login Configurations using Affinidi CLI.

Using Affinidi Portal
Create new Login Configuratioin
  1. Go to  Affinidi Login under the Services section.

  2. Click on the Create Login Configuration and provide the required details.

  • Name is the string that describes your login configuration.
  • Redirect URIs is the URL on your application where the user gets redirected after the successful authentication.
  1. Click on create and confirm if all the details are correct.
Login Configuratation new client
  1. After confirming the details, another popup shows the Client ID and Client Secret for your Login Configuration. Copy the generated Client Credentials and use them to integrate with Affinidi Login.

  2. After copying the Client ID and Client Secret and closing the popup, you are redirected back to the Affinidi Login page.

Login Configuration uses the default Presentation Definition (presentationDefinition) and ID Token Mapping (idTokenMapping) that is used to request the user’s email address during the authentication flow.

Learn more about customising the Presentation Definition and ID Token using this guide.

Set up the Sample Application

After creating the Login Configuration required to set up the sample application. Let’s start setting up the .NET application by configuring the following settings:

Configure Env Variables

Create the .env file using the following command:

cp .env.example .env

Set the environment variables based on the auth credentials received from the Login Configuration created earlier:

{
  "auth": {
    "clientId": "<AUTH.CLIENT_ID>",
    "clientSecret": "<AUTH.CLIENT_SECRET>",
    "issuer": "https://<PROJECT_ID>.apse1.login.affinidi.io"
  }
}

Set the following fields in the .env file

PROVIDER_CLIENT_ID="<AUTH.CLIENT_ID>"
PROVIDER_CLIENT_SECRET="<AUTH.CLIENT_SECRET>"
PROVIDER_ISSUER="<AUTH.CLIENT_ISSUER>"

Build and Run

dotnet build
dotnet run

After successfully running the command, go to http://localhost:5068/ to access the page with the Affinidi Login button.

Key Changes to Sample Application

Enabling Affinidi Login

In the Startup.cs, we have added the OpenID Connect and set the client credentials and issuer URL generated after creating Login Configuration.

using System.IdentityModel.Tokens.Jwt;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;


namespace Affinidi_Login_Demo_App;

public class Startup
    {

        public IWebHostEnvironment Environment {get; }
        public IConfiguration Configuration {get; }

        public Startup(IWebHostEnvironment environment, IConfiguration config) {
            Environment = environment;
            Configuration = config;
            DotNetEnv.Env.Load();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseStaticFiles();

            app.UseEndpoints(endpoints => {
                endpoints.MapRazorPages();
            });
        }

        public void ConfigureServices(IServiceCollection services)
        {
            // Prevent WS-Federation claim names being written to tokens
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
            
            services.AddAuthentication(options => {
                
                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            })
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => {
                
                // Use the strongest setting in production, which also enables HTTP on developer workstations
                options.Cookie.SameSite = SameSiteMode.Strict;
            })
            .AddOpenIdConnect(options => {

                // Use the same settings for temporary cookies
                options.NonceCookie.SameSite = SameSiteMode.Strict;
                options.CorrelationCookie.SameSite = SameSiteMode.Strict;
                options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                
                // Set the main OpenID Connect settings
                options.Authority = System.Environment.GetEnvironmentVariable("PROVIDER_ISSUER");
                options.ClientId = System.Environment.GetEnvironmentVariable("PROVIDER_CLIENT_ID");
                options.ClientSecret = System.Environment.GetEnvironmentVariable("PROVIDER_CLIENT_SECRET");
                string scopeString = System.Environment.GetEnvironmentVariable("SCOPE");
                options.ResponseType = OpenIdConnectResponseType.Code;
                options.ResponseMode = OpenIdConnectResponseMode.Query;
                options.Scope.Clear();
                scopeString?.Split(" ", StringSplitOptions.TrimEntries).ToList().ForEach(scope => {
                    options.Scope.Add(scope);
                });

                // If required, override the issuer and audience used to validate ID tokens
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidIssuer = options.Authority,
                    ValidAudience = options.ClientId
                };

                // This example gets user information for display from the user info endpoint
                options.GetClaimsFromUserInfoEndpoint = true;

                // // Handle the post logout redirect URI
                // options.Events.OnRedirectToIdentityProviderForSignOut = (context) =>
                // {
                //     context.ProtocolMessage.PostLogoutRedirectUri = Configuration.GetValue<string>("OpenIdConnect:PostLogoutRedirectUri");
                //     return Task.CompletedTask;
                // };
                
                // Save tokens issued to encrypted cookies
                options.SaveTokens = true;

                // Set this in developer setups if the OpenID Provider uses plain HTTP
                options.RequireHttpsMetadata = false;

                /* Uncomment to debug HTTP requests from the web backend to the Identity Server
                   Run a tool such as MITM proxy to view the request and response messages
                /*options.BackchannelHttpHandler = new HttpClientHandler()
                {
                    Proxy = new WebProxy("http://127.0.0.1:8888"),
                    UseProxy = true,
                };*/
            });

            services.AddAuthorization();
            services.AddRazorPages();

            // Add this app's types to dependency injection
            services.AddSingleton<TokenClient>();
        }
    }

We have also defined the Data Model in the AffinidiDataModel.cs to store the user info parsed from the ID Token that Affinidi Login will send.

namespace Affinidi_Login_Demo_App;

public class AffinidiDataModel
{
    public string? given_name {get;set;}
    public string? family_name {get;set;}
    public string? did {get;set;}
    public string? email {get;set;}
    // public string? 

}

Summary

sequenceDiagram
    actor User
    participant .NET
    participant Affinidi Login
    participant Affinidi Vault
    participant Affinidi Verifier

    User->>.NET: My Login
    .NET->>Affinidi Login: Authenticate user
    Note over .NET, Affinidi Login:  login_challenge
    Affinidi Login->>Affinidi Vault: Verify user identity
    Note over Affinidi Login, Affinidi Vault:  presentationDefinition
    Affinidi Vault->>User: Request user confirmation to share Email VC
    User->>Affinidi Vault: User confirmed consent to share Email VC
    Affinidi Vault->>Affinidi Vault: Generate VP Token from VC
    Affinidi Vault->>Affinidi Login: Send Email VP Token
    Affinidi Login->>Affinidi Verifier: Validate VP Token
    Note over Affinidi Login, Affinidi Verifier:  vp_token, presentation_submission, presentation_definition
    Affinidi Login->>Affinidi Login: Generate idToken
    Affinidi Login->>.NET: Send generated idToken from VP
    .NET->>User: Provide access to the user

Using the .NET SDK 8.0 with OpenID Connect as the sample application, we have configured it to integrate with Affinidi Login as the Auth provider and parse the idToken sent by the Affinidi Login to confirm the user’s successful authentication using the Affinidi Vault.