Creating Verifiable credentials in ASP.NET Core for decentralized identities using Trinsic

This article shows how verifiable credentials can be created in ASP.NET Core for decentralized identities using the Trinsic platform which is a Self-sovereign identity implementation with APIs to integrate. The verifiable credentials can be downloaded to your digital wallet if you have access and can be used in separate application which understands the Trinsic APIs.

Code: https://github.com/swiss-ssi-group/TrinsicAspNetCore

Blogs in this series

Setup

We want implement the flow shown in the following figure. The National Driving license application is responsible for issuing driver licenses and administrating licenses for users which have authenticated correctly. The user can see his or her driver license and a verifiable credential displayed as a QR code which can be used to add the credential to a digital wallet. When the application generates the credential, it adds the credential DID to the blockchain ledger with the cryptographic proof of the issuer and the document. When you scan the QR Code, the DID will get validated and will be added to the wallet along with the request claims. The digital wallet must be able to find the DID on the correct network and the schema and needs to search for the ledger in the correct blockchain. A good wallet should take care of this for you. The schema is required so that the data in the DID document can be understood.

Trinsic Setup

Trinsic is used to connect to the blockchain and create the DIDs, credentials in this example. Trinsic provides good getting started docs.

In Trinsic, you need to create an organisation for the Issuer application.

Click on the details of the organisation to get the API key. This is required for the application. This API Key cannot be replaced or updated, so if you make a mistake and lose this, commit it in code, you would have to create a new organisation. It is almost important to note the network. This is where you can find the DID to get the credentials produced by this issuer.

To issuer credentials, you need to create a template or schema with the claims which are issued in the credential using the template. The issuer application provides values for the claims.

Implementing the ASP.NET Core Issuer

The verifiable credentials issuer is implemented in an ASP.NET Core application using Razor pages and Identity. This application needs to authenticate the users before issuing a verifiable credential for the user. FIDO2 with the correct authenticate flow would be a good choice as this would protect against phishing. You could use credentials as well, if the users of the applications had a trusted ID. You would still have to protect against phishing. The quality of the credentials issued depends on the security of the issuing application. If the application has weak user authentication, then the credentials cannot be trusted. For a bank, gov IDs, drivings license, a high level of security is required. Open ID Connect FAPI with FIDO2 would make a good solution to authenticate the user. Or a user with a trusted gov issued credential together with FIDO2 would also be good.

The ASP.NET Core application initializes the services and adds the Trinsic client using the API Key from the organisation which issues the credentials. The Trinsic.ServiceClients Nuget package is used for the Trinsic integration. ASP.NET Core Identity is used to add, remove users and add driving licenses for the users in the administration part of the application. MFA should be setup but as this is a demo, I have not forced this.

public void ConfigureServices(IServiceCollection services)
{
	services.AddScoped<TrinsicCredentialsService>();
	services.AddScoped<DriverLicenseService>();

	services.AddTrinsicClient(options =>
	{
		// For CredentialsClient and WalletClient
		// API key of National Driving License (Organisation which does the verification)
		options.AccessToken = Configuration["Trinsic:ApiKey"];
		// For ProviderClient
		// options.ProviderKey = providerKey;
	});

	services.AddDbContext<ApplicationDbContext>(options =>
		options.UseSqlServer(
			Configuration.GetConnectionString("DefaultConnection")));
	services.AddDatabaseDeveloperPageExceptionFilter();

	services.AddIdentity<IdentityUser, IdentityRole>(
		options => options.SignIn.RequireConfirmedAccount = false)
	 .AddEntityFrameworkStores<ApplicationDbContext>()
	 .AddDefaultTokenProviders();

	services.AddSingleton<IEmailSender, EmailSender>();
	services.AddScoped<IUserClaimsPrincipalFactory<IdentityUser>,
		AdditionalUserClaimsPrincipalFactory>();

	services.AddAuthorization(options =>
	{
		options.AddPolicy("TwoFactorEnabled",
			x => x.RequireClaim("amr", "mfa")
		);
	});

	services.AddRazorPages();
}

User secrets are used to add the secrets required for the application in development. The secrets can be added to the Json secrets file and not to the code source. If deploying this to Azure, the secrets would be read from Azure Key vault. The application requires the Trinsic API Key and the credential template definition ID created in Trinsic studio.

{
  "ConnectionStrings": {
    "DefaultConnection": "--db-connection-string--"
  },
  "Trinsic": {
    "ApiKey":  "--your-api-key-organisation--",
    "CredentialTemplateDefinitionId": "--Template-credential-definition-id--"
  }
}

The driving license service is responsible for creating driver license for each user. This is just an example of logic and is not related to SSI.

using Microsoft.EntityFrameworkCore;
using NationalDrivingLicense.Data;
using System.Threading.Tasks;

namespace NationalDrivingLicense
{
    public class DriverLicenseService
    {
        private readonly ApplicationDbContext _applicationDbContext;

        public DriverLicenseService(ApplicationDbContext applicationDbContext)
        {
            _applicationDbContext = applicationDbContext;
        }

        public async Taskbool> HasIdentityDriverLicense(string username)
        {
            if (!string.IsNullOrEmpty(username))
            {
                var driverLicense = await _applicationDbContext.DriverLicenses.FirstOrDefaultAsync(
                    dl => dl.UserName == username && dl.Valid == true
                );

                if (driverLicense != null)
                {
                    return true;
                }
            }

            return false;
        }

        public async Task<DriverLicense> GetDriverLicense(string username)
        {
            var driverLicense = await _applicationDbContext.DriverLicenses.FirstOrDefaultAsync(
                    dl => dl.UserName == username && dl.Valid == true
                );

            return driverLicense;
        }

        public async Task UpdateDriverLicense(DriverLicense driverLicense)
        {
            _applicationDbContext.DriverLicenses.Update(driverLicense);
            await _applicationDbContext.SaveChangesAsync();
        }
    }
}

The Trinsic credentials service is responsible for creating the verifiable credentials. It uses the users drivers license and creates a new credential using the Trinsic client API using the CreateCredentialAsync method. The claims must match the template created in the studio. A Trinsic specific URL is returned. This can be used to create a QR Code which can be scanned from a Trinsic digital wallet.

public class TrinsicCredentialsService
{
        private readonly ICredentialsServiceClient _credentialServiceClient;
        private readonly IConfiguration _configuration;
        private readonly DriverLicenseService _driverLicenseService;

        public TrinsicCredentialsService(ICredentialsServiceClient credentialServiceClient,
            IConfiguration configuration,
            DriverLicenseService driverLicenseService)
        {
            _credentialServiceClient = credentialServiceClient;
            _configuration = configuration;
            _driverLicenseService = driverLicenseService;
        }

        public async Task<string> GetDriverLicenseCredential(string username)
        {
            if (!await _driverLicenseService.HasIdentityDriverLicense(username))
            {
                throw new ArgumentException("user has no valid driver license");
            }

            var driverLicense = await _driverLicenseService.GetDriverLicense(username);

            if (!string.IsNullOrEmpty(driverLicense.DriverLicenseCredentials))
            {
                return driverLicense.DriverLicenseCredentials;
            }

            string connectionId = null; // Can be null | <connection identifier>
            bool automaticIssuance = false;
            IDictionary<string, string> credentialValues = new Dictionary<String, String>() {
                {"Issued At", driverLicense.IssuedAt.ToString()},
                {"Name", driverLicense.Name},
                {"First Name", driverLicense.FirstName},
                {"Date of Birth", driverLicense.DateOfBirth.Date.ToString()},
                {"License Type", driverLicense.LicenseType}
            };

            CredentialContract credential = await _credentialServiceClient
                .CreateCredentialAsync(new CredentialOfferParameters
                {
                    DefinitionId = _configuration["Trinsic:CredentialTemplateDefinitionId"],
                    ConnectionId = connectionId,
                    AutomaticIssuance = automaticIssuance,
                    CredentialValues = credentialValues
                });

            driverLicense.DriverLicenseCredentials = credential.OfferUrl;
            await _driverLicenseService.UpdateDriverLicense(driverLicense);

            return credential.OfferUrl;
        }
}

The DriverLicenseCredentials Razor page uses the Trinsic service and returns the credentials URL if the user has a valid drivers license.

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages;
using NationalDrivingLicense.Data;

namespace NationalDrivingLicense.Pages
{
    public class DriverLicenseCredentialsModel : PageModel
    {
        private readonly TrinsicCredentialsService _trinsicCredentialsService;
        private readonly DriverLicenseService _driverLicenseService;

        public string DriverLicenseMessage { get; set; } = "Loading credentials";
        public bool HasDriverLicense { get; set; } = false;
        public DriverLicense DriverLicense { get; set; }

        public string CredentialOfferUrl { get; set; }
        public DriverLicenseCredentialsModel(TrinsicCredentialsService trinsicCredentialsService,
           DriverLicenseService driverLicenseService)
        {
            _trinsicCredentialsService = trinsicCredentialsService;
            _driverLicenseService = driverLicenseService;
        }
        public async Task OnGetAsync()
        {
            DriverLicense = await _driverLicenseService.GetDriverLicense(HttpContext.User.Identity.Name);

            if (DriverLicense != null)
            {
                var offerUrl = await _trinsicCredentialsService
                    .GetDriverLicenseCredential(HttpContext.User.Identity.Name);
                DriverLicenseMessage = "Add your driver license credentials to your wallet";
                CredentialOfferUrl = offerUrl;
                HasDriverLicense = true;
            }
            else
            {
                DriverLicenseMessage = "You have no valid driver license";
            }
        }
    }
}

The Razor page template displays the QR code and information about the driver license issued to the logged in user.

@page
@model NationalDrivingLicense.Pages.DriverLicenseCredentialsModel
@{
}

<h3>@Model.DriverLicenseMessage</h3>
<br />
<br />

@if (Model.HasDriverLicense)
{
    <div class="container-fluid">
        <div class="row">
            <div class="col-sm">
                <div class="qr" id="qrCode"></div>
            </div>
            <div class="col-sm">
                <div>
                    <img src="~/ndl_car_01.png" width="200" alt="Driver License">
                    <div>
                        <b>Driver Licence: @Html.DisplayFor(model => model.DriverLicense.UserName)</b>
                        <hr />
                        <dl class="row">
                            <dt class="col-sm-4">Issued</dt>
                            <dd class="col-sm-8">
                                @Model.DriverLicense.IssuedAt.ToString("MM/dd/yyyy")
                            </dd>
                            <dt class="col-sm-4">
                                @Html.DisplayNameFor(model => model.DriverLicense.Name)
                            </dt>
                            <dd class="col-sm-8">
                                @Html.DisplayFor(model => model.DriverLicense.Name)
                            </dd>
                            <dt class="col-sm-4">First Name</dt>
                            <dd class="col-sm-8">
                                @Html.DisplayFor(model => model.DriverLicense.FirstName)
                            </dd>
                            <dt class="col-sm-4">License Type</dt>
                            <dd class="col-sm-8">
                                @Html.DisplayFor(model => model.DriverLicense.LicenseType)
                            </dd>
                            <dt class="col-sm-4">Date of Birth</dt>
                            <dd class="col-sm-8">
                                @Model.DriverLicense.DateOfBirth.ToString("MM/dd/yyyy")
                            </dd>
                            <dt class="col-sm-4">Issued by</dt>
                            <dd class="col-sm-8">
                                @Html.DisplayFor(model => model.DriverLicense.Issuedby)
                            </dd>
                            <dt class="col-sm-4">
                                @Html.DisplayNameFor(model => model.DriverLicense.Valid)
                            </dt>
                            <dd class="col-sm-8">
                                @Html.DisplayFor(model => model.DriverLicense.Valid)
                            </dd>
                        </dl>
                    </div>
                </div>
            </div>
        </div>
    </div>
}

@section scripts {
    <script src="~/js/qrcode.min.js"></script>
    <script type="text/javascript">
        new QRCode(document.getElementById("qrCode"),
            {
                text: "@Html.Raw(Model.CredentialOfferUrl)",
                width: 300,
                height: 300
            });
    $(document).ready(() => {
        document.getElementById('begin_token_check').click();
    });
    </script>
}

When the application is started, you can register and create a new license in the license administration.

Add licences as required. The credentials will not be created here, only when you try to get a driver license as a user.

The QR code of the license is displayed which can be scanned and added to your Trinsic digital wallet.

Notes

This works fairly good but has a number of problems. The digital wallets are vendor specific and the QR Code, credential links are dependent on the product used to create this. The wallet implementations and the URL created for the credentials are all specific and rely on good will of the different implementations of the different vendors. This requires an RFC specification or something like this, if SSI should become easy to use and mainstream. Without this, users would require n-wallets for all the different applications and would also have problems using credentials between different systems.

Another problem is the organisations API keys use the represent the issuer or the verifier applications. If this API keys get leaked which they will, the keys are hard to replace.

Using the wallet, the user also needs to know which network to use to load the credentials, or to login to your product. A default user will not know where to find the required DID.

If signing in using the wallet credentials, the application does not protect against phishing. This is not good enough for high security authentication. FIDO2 and WebAuthn should be used if handling such sensitive data as this is designed for.

Self sovereign identities is in the very early stages but holds lots of potential. A lot will depend on how easy it is to use and how easy it is to implement and share credentials between systems. The quality of the credential will depend on the quality of the application issuing it.

In a follow up blog to this one, Matteo will use the verifiable credentials added to the digital wallet and verify them in a second application.

Links

https://studio.trinsic.id/

https://www.youtube.com/watch?v=mvF5gfMG9ps

https://github.com/trinsic-id/verifier-reference-app

https://docs.trinsic.id/docs/tutorial

Self sovereign identity

https://techcommunity.microsoft.com/t5/identity-standards-blog/ion-we-have-liftoff/ba-p/1441555

3 comments

  1. […] Creating Verifiable credentials in ASP.NET Core for decentralized identities using Trinsic (Damien Bowden) […]

  2. […] Creating Verifiable credentials in ASP.NET Core for decentralized identities using Trinsic – Damien Bowden […]

  3. […] Creating Verifiable credentials in ASP.NET Core for decentralized identities using Trinsic […]

Leave a Reply to Dew Drop – April 5, 2021 (#3416) – Morning Dew by Alvin Ashcraft Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: