Open Closed

Identity Service – X.509 Certificate Validity Monitoring and Alerts #10913


User avatar
0
Repunjay_TASC created

Hi Team,

We recently encountered an authentication issue in the Web UI due to an invalid X.509 encryption certificate configured in the Identity Service. The application logs reported the following error in identity service:

System.InvalidOperationException: When using X.509 encryption credentials, at least one of the registered certificates must be valid

The issue occurred because the Identity Service could not identify a valid registered certificate for the required encryption operation. We resolved it by creating a new Identity Service build image and redeploying it. To prevent similar issues in the future, we would like to explore proactive monitoring and alerting mechanisms. Could you please advise on the following?

  1. Certificate validity monitoring: How can we monitor the validity and expiry dates of X.509 certificates used by the Identity Service?
  2. Expiry notifications: How can we configure automated alerts to notify the support team in advance of certificate expiry, allowing sufficient time for renewal or replacement?
  3. Preventive validation: Can we incorporate certificate validity checks into the CI/CD pipeline or application health checks to detect invalid or expired certificates before deployment?

Please share your recommendations on the best approach to implement these preventive measures.

Below is the code snippet for your reference.

AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); if (!hostingEnvironment.IsDevelopment()) { PreConfigure<AbpOpenIddictAspNetCoreOptions>(options => { options.AddDevelopmentEncryptionAndSigningCertificate = false; });

            PreConfigure&lt;OpenIddictServerBuilder&gt;(builder =>
            {
                builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration));
                builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration));
                

private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration) { var fileName = "authserver.pfx"; var passPhrase = "XXXXXXXX-XXXX-XXXXX-XXXX-XXXXX8ED"; var file = Path.Combine(hostingEnv.ContentRootPath, fileName);

        if (!File.Exists(file))
        {
            throw new FileNotFoundException($"Signing Certificate couldn't found: {file}");
        }

        return new X509Certificate2(file, passPhrase);
    }
Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

3 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • Root cause / what that OpenIddict error means

      • System.InvalidOperationException: When using X.509 encryption credentials, at least one of the registered certificates must be valid
      • This is thrown by OpenIddict when none of the registered X.509 certificates are considered valid at runtime (typically: expired/not-yet-valid, or sometimes the cert loads but private key isn’t usable). In your code, you register a PFX via AddSigningCertificate(...) + AddEncryptionCertificate(...), so if that PFX becomes invalid/expired, AuthServer/IdentityService will fail.
    • 1) Certificate validity monitoring (recommended approach)

      • Implement a small runtime check in the Identity Service that loads the same certificate(s) you register with OpenIddict and records:
        • NotBefore, NotAfter
        • DateTime.UtcNow vs NotAfter
        • (optionally) whether the private key is available: certificate.HasPrivateKey
      • Then expose this via a monitoring-friendly surface:
        • App logs (warn when expiring soon, error when expired)
        • Metrics (e.g., Prometheus/Grafana) if you already use metrics
        • A health check endpoint (see #3)

      Example (simple reusable validator you can call from health checks and/or startup):

    using System.Security.Cryptography.X509Certificates;
    
      public static class CertificateValidity
      {
          public static void EnsureValid(X509Certificate2 cert, int warnBeforeDays = 30)
          {
              var now = DateTimeOffset.UtcNow;
    
              if (now < cert.NotBefore.ToUniversalTime())
              {
                  throw new InvalidOperationException(
                      $"Certificate is not yet valid. NotBefore={cert.NotBefore:u}");
              }
    
              if (now > cert.NotAfter.ToUniversalTime())
              {
                  throw new InvalidOperationException(
                      $"Certificate is expired. NotAfter={cert.NotAfter:u}");
              }
    
              // Optional: ensure private key exists when you expect one
              if (!cert.HasPrivateKey)
              {
                  throw new InvalidOperationException("Certificate doesn't have a private key.");
              }
    
              var remaining = cert.NotAfter.ToUniversalTime() - now.UtcDateTime;
              if (remaining.TotalDays <= warnBeforeDays)
              {
                  // log warning from your caller (health check can degrade, startup can warn, etc.)
              }
          }
      }
    
    • 2) Expiry notifications / alerting

      • The most reliable pattern is:
        1. Publish a health check that fails (or “Degraded”) when the certificate is nearing expiry.
        2. Let your existing platform monitoring (Kubernetes liveness/readiness probes + Prometheus, Azure Monitor, Application Insights, etc.) alert on:
          • health endpoint status
          • metric threshold (days-to-expiry)
          • log query (if you prefer log-based alerts)
      • In ABP microservice environments, this is typically done outside ABP itself (in your infrastructure), but ABP can provide the endpoint/metric/log signals.
    • 3) Preventive validation in CI/CD and/or application health checks

      • CI/CD gate (pre-deploy):
        • Add a pipeline step that loads the PFX (from the same secret store used by deployment) and verifies NotAfter is beyond your required window (e.g., must be valid for at least 30 days).
        • This is the best way to prevent deploying an already-expired cert image/secret.
      • Application health check (runtime / post-deploy):
        • Add an ASP.NET Core health check that loads the cert (same code path as production) and fails if invalid/expired or expiring soon.
        • Your orchestrator can then block traffic (readiness probe) or alert immediately.

      Example health check:

    using Microsoft.Extensions.Diagnostics.HealthChecks;
      using System.Security.Cryptography.X509Certificates;
    
      public sealed class OpenIddictCertificateHealthCheck : IHealthCheck
      {
          private readonly IWebHostEnvironment _env;
          private readonly IConfiguration _configuration;
    
          public OpenIddictCertificateHealthCheck(IWebHostEnvironment env, IConfiguration configuration)
          {
              _env = env;
              _configuration = configuration;
          }
    
          public Task<HealthCheckResult> CheckHealthAsync(
              HealthCheckContext context,
              CancellationToken cancellationToken = default)
          {
              try
              {
                  // Load the same cert you use for OpenIddict
                  var cert = LoadCertificate(_env, _configuration);
    
                  // Fail if invalid/expired; optionally degrade if expiring soon
                  CertificateValidity.EnsureValid(cert, warnBeforeDays: 30);
    
                  var daysLeft = (cert.NotAfter.ToUniversalTime() - DateTime.UtcNow).TotalDays;
                  if (daysLeft <= 30)
                  {
                      return Task.FromResult(HealthCheckResult.Degraded(
                          $"Certificate is expiring soon. NotAfter={cert.NotAfter:u}, DaysLeft={daysLeft:0.##}"));
                  }
    
                  return Task.FromResult(HealthCheckResult.Healthy(
                      $"Certificate OK. NotAfter={cert.NotAfter:u}, DaysLeft={daysLeft:0.##}"));
              }
              catch (Exception ex)
              {
                  return Task.FromResult(HealthCheckResult.Unhealthy("Certificate validation failed.", ex));
              }
          }
    
          private static X509Certificate2 LoadCertificate(IWebHostEnvironment env, IConfiguration configuration)
          {
              var fileName = "authserver.pfx";
              var passPhrase = configuration["AuthServer:CertificatePassPhrase"] ?? "CHANGE-ME";
              var file = Path.Combine(env.ContentRootPath, fileName);
    
              // Consider also passing X509KeyStorageFlags as needed for IIS/Azure
              return new X509Certificate2(file, passPhrase);
          }
      }
    

    Register health check (in your module):

    public override void ConfigureServices(ServiceConfigurationContext context)
      {
          context.Services.AddHealthChecks()
              .AddCheck<OpenIddictCertificateHealthCheck>("openiddict_certificate");
      }
    
    • Important operational recommendation (to avoid sudden failures)

      • OpenIddict’s own message mentions rollover: “To use key rollover, register both the new certificate and the old one”.
      • Practically, this means during renewal/rotation, deploy in a way that the service always has at least one currently-valid cert registered (often by temporarily registering both old+new during a rotation window).
    • Notes specific to ABP/OpenIddict production usage

      • ABP strongly recommends using a real production certificate for OpenIddict and configuring it appropriately (rather than relying on dev cert behavior).
      • If you run under IIS/Azure and face certificate load/private-key issues, use the ABP/OpenIddict production helper and pass X509KeyStorageFlags as shown in the ABP community article.

    Sources:

    • https://abp.io/docs/latest/solution-templates/layered-web-application/deployment/openiddict-deployment
    • https://abp.io/community/articles/0znavo8r
    • https://abp.io/community/articles/kwnjkun1
    • https://abp.io/support/questions/9016

    This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    Repunjay_TASC created

    Please assign to support team to review and confirm.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    OpenIddict throws this error when all registered encryption certificates are outside their validity period.

    Your snippet matches the older microservice template. The AuthServer Dockerfile in that template creates the pfx file during the image build:

    RUN dotnet dev-certs https -v -ep /app/authserver.pfx -p <your-passphrase>
    

    A new certificate created by this command is valid for 365 days. Please check if your Dockerfile still has this line, and check the NotBefore and NotAfter dates of the pfx file that the AuthServer loads.

    I suggest you create your own certificate with the lifetime you need and mount it into the container, instead of creating it during the image build. Use an RSA certificate with a key of at least 2048 bits, and the pfx file must contain the private key. If the certificate has a Key Usage extension, it needs both Digital Signature and Key Encipherment, because you use the same file for signing and encryption. Your code loads the file at startup, so restart the AuthServer after you replace it.

    For the monitoring and the alerts, you can add a health check. It reads the certificate that OpenIddict uses and returns Degraded when it expires within 30 days.

    using System;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.Extensions.Diagnostics.HealthChecks;
    using Microsoft.Extensions.Options;
    using Microsoft.IdentityModel.Tokens;
    using OpenIddict.Server;
    
    namespace MyCompanyName.MyProjectName;
    
    public class OpenIddictCertificateHealthCheck : IHealthCheck
    {
        private static readonly TimeSpan WarningThreshold = TimeSpan.FromDays(30);
    
        private readonly IOptionsMonitor<OpenIddictServerOptions> _options;
    
        public OpenIddictCertificateHealthCheck(IOptionsMonitor<OpenIddictServerOptions> options)
        {
            _options = options;
        }
    
        public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
        {
            var options = _options.CurrentValue;
    
            var signing = GetExpiration(options.SigningCredentials[0].Key);
            var encryption = GetExpiration(options.EncryptionCredentials[0].Key);
            var expiration = signing < encryption ? signing : encryption;
            var remaining = expiration - DateTime.UtcNow;
    
            var data = new Dictionary<string, object>
            {
                { "expiration", expiration.ToString("u") },
                { "remainingDays", (int)remaining.TotalDays }
            };
    
            if (remaining <= TimeSpan.Zero)
            {
                return Task.FromResult(HealthCheckResult.Unhealthy($"The OpenIddict certificate expired on {expiration:u}.", data: data));
            }
    
            if (remaining <= WarningThreshold)
            {
                return Task.FromResult(HealthCheckResult.Degraded($"The OpenIddict certificate expires in {(int)remaining.TotalDays} days.", data: data));
            }
    
            return Task.FromResult(HealthCheckResult.Healthy($"The OpenIddict certificate expires on {expiration:u}.", data));
        }
    
        private static DateTime GetExpiration(SecurityKey key)
        {
            return key is X509SecurityKey x509SecurityKey
                ? x509SecurityKey.Certificate.NotAfter.ToUniversalTime()
                : DateTime.MaxValue;
        }
    }
    

    Register it in ConfigureServices:

    context.Services.AddHealthChecks()
        .AddCheck<OpenIddictCertificateHealthCheck>("openiddict-certificate");
    

    Then add the endpoint to your existing UseConfiguredEndpoints call in OnApplicationInitialization:

    app.UseConfiguredEndpoints(endpoints =>
    {
        endpoints.MapHealthChecks("/health/certificate");
    });
    

    Your monitoring system can call this endpoint and send the alert. For Degraded the endpoint returns HTTP 200 with Degraded in the response body, so the alert has to check the body. It also has to fire on any non-200 response or when the endpoint is unreachable. The endpoint returns 503 or 500 when the certificate is no longer valid.

    For the pipeline, this command returns exit code 1 when the certificate expires within 30 days:

    openssl pkcs12 -in authserver.pfx -passin pass:<your-passphrase> -nokeys | openssl x509 -noout -dates -checkend 2592000
    

    It prints both dates, but the exit code checks the expiry date only. Run it against the pfx file that the AuthServer will load.

    Before deployment, start the new image with the certificate and call the health check endpoint. It has to return Healthy. Also test a login against it, because the health check does not issue a token.

    When you replace the certificate, register the new one together with the old one and restart the AuthServer. The new certificate must already be valid at that time. OpenIddict then uses the one with the later expiry date. Keep the old one registered until the tokens issued with it have expired.

    PreConfigure<OpenIddictServerBuilder>(builder =>
    {
        builder.AddSigningCertificate(oldCertificate);
        builder.AddEncryptionCertificate(oldCertificate);
        builder.AddSigningCertificate(newCertificate);
        builder.AddEncryptionCertificate(newCertificate);
    });
    

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on October 08, 2026, 12:23
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.