Tracking Duende Identity Server's license validity

Β·

4 min read

The problem

  • Do you want to check the validity of identity at runtime, but couldn't find the proper API exposed by Duende Identity Server that you can consume to achieve this?

  • Have you just updated the license with a new one but you aren't sure if you production instance is running with the new license or the old one, putting you at the risk of unintentionally running Identity Server on production with an expired license?

  • Or, do you want to monitor the license expiry date and you would like to get alerted early before the license expires so that you can initiate the procurement process to obtain a new license?

The solution

If the answer to any of the questions is yes, and if you are running Duende Identity Server v7 or above, then say no more! Duende Identity Server v7 ships with a new improvement that makes it much easier to track the status of the license by making the license object public and available in the DI system. Even though there is no bits of documentation about this newly added API in the official documentation website as of the time of writing this article, it's quite straight forward to consume, it's as simple as adding a dependency to IdentityServerLicense type which will either be resolved to an object that represents the claims available in the license, or will be resolved to null in case there is no license (e.g. when running on any environment other that production, which, in that case, it's allowed to run Duende Identity Server without a license).

Now, let's get to the coding part and implement an ASP.NET Core health check in order to tackle the problem outlined above:

public class DuendeIdentityServerLicenseHealthCheck : IHealthCheck
{
    private readonly IdentityServerLicense _license;
    private readonly IWebHostEnvironment _env;
    private readonly ILogger<DuendeIdentityServerLicenseHealthCheck> _logger;

    public DuendeIdentityServerLicenseHealthCheck(
        IdentityServerLicense license,
        IWebHostEnvironment env,
        ILogger<DuendeIdentityServerLicenseHealthCheck> logger)
    {
        _license = license;
        _env = env;
        _logger = logger;
    }

    public Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        if (!_env.IsProduction())
        {
            return Task.FromResult(
                HealthCheckResult.Healthy("Duende Identity Server's license is not required if the environment is not production."));
        }

        if (license == null)
        {
            return Task.FromResult(
                new HealthCheckResult(
                    context.Registration.FailureStatus, 
                    "Invalid or missing Duende Identity Server's license."));
        }

        var healthCheckData = new Dictionary<string, object>
        {
            { "Expiration", license.Expiration }
        };

        if (license.Expiration < DateTime.Now)
        {
            return Task.FromResult(
                new HealthCheckResult(
                    context.Registration.FailureStatus, 
                    "Duende Identity Server's license has expired.",
                    null, //No exception
                    healthCheckData));
        }

        return Task.FromResult(
                HealthCheckResult.Healthy(
                    "Duende Identity Server's license is valid.",
                    healthCheckData));
    }
}

The sample health check above does the following:

  • For non-production environment(s) it always returns healthy status

  • For production it returns failure status when the license is invalid, missing, or expired. Otherwise, it returns healthy status.

  • In either cases, whether the license has expired or not, it emits the license expiration date. This is can come in quite handy for monitoring the expiration of the license and get notified in a timely fashion to start procuring the new license.

To register the health check above add the following to Program.cs:

builder.Services.AddHealthChecks()
    .AddCheck<DuendeIdentityServerLicenseHealthCheck>(
        "Duende Identity Server's license health check",
        failureStatus: HealthStatus.Unhealthy,
        tags: new[] { "license" });

Mapping the health check to a route depends on what do you want to achieve with the health check. For example, if you are running on k8s and you don't want the pod running Identity Server to start accepting traffic in production if the license is invalid, then you can run this health check during the readiness probe. In some other cases, you might just want to map a separate health check route dedicated for running the license health check. Either way, mapping the health check to a route will look like this:

app.MapHealthChecks("/license-healthz", new HealthCheckOptions
{
    Predicate = healthCheck => healthCheck.Tags.Contains("license")
});

With that, we have utilized the newly added license API in Duende Identity Server v7 in order to:

  • Implement a runtime health check to ensure that production is running with a valid license

  • When updating the license, provide a mechanism to ensure that the license has been updated correctly and Identity Server is running with the new license

  • Provide a way to monitor the license expiration

Β