AzureAD / microsoft-identity-web

Helps creating protected web apps and web APIs with Microsoft identity platform and Azure AD B2C
MIT License
683 stars 217 forks source link

Exception thrown: 'System.NullReferenceException' in System.Private.CoreLib.dll linked issue:#2410 #2460

Closed emmanuelpare closed 1 year ago

emmanuelpare commented 1 year ago

Microsoft.Identity.Web Library

Microsoft.Identity.Web

Microsoft.Identity.Web version

2.13.4

Web app

Sign-in users

Web API

Protected web APIs (validating tokens)

Token cache serialization

In-memory caches

Description

Iam following issue #2410 https://github.com/AzureAD/microsoft-identity-web/issues/2410 and I will try to give more background to my problem

I tryed a couple of things using (Mr. Prieur advice) but I got no success this is why Iam reopening the case. (I though updating to 2.13.4 might also help but same error). The only way I got it working (using 2.13.4) was to use WebApplication.CreateBuilder(args) instead of Host.CreateDefaultBuilder(). I really just want a console application not a webapp.

Everything work fine under 2.13.2

I am writing a background service to schedule tasks in a console application. The console application needs to query a web API and use some custom dependency services shared with the web UI. This is why I need dependency injection in my console application. The scheduler is authenticated via an Enterprise Application in Azure, exactly like the web UI. Since the change introduced in version 2.13.3, I have encountered a null exception error. See the stack trace below.

Someone mentioned to me to try https://github.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/tree/master/2-Call-OwnApi, but this example does not use dependency injection (or the way I want).

So my question is, is it possible to have a working example of using Microsoft.Web.Identity.Web using the "default worker service template" (dotnet 7)? I will take any advice..

Reproduction steps

Use Host.CreateDefaultBuilder() instead of WebApplication.CreateBuilder(args);

Error message

Stack Trace: at Microsoft.Identity.Web.MergedOptions.PrepareAuthorityInstanceForMsal() at Microsoft.Identity.Web.TokenAcquisition.BuildConfidentialClientApplication(MergedOptions mergedOptions) at Microsoft.Identity.Web.TokenAcquisition.GetOrBuildConfidentialClientApplication(MergedOptions mergedOptions) at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForAppAsync(String scope, String authenticationScheme, String tenant, TokenAcquisitionOptions tokenAcquisitionOptions) at Microsoft.Identity.Web.TokenAcquisition.d__17.MoveNext() at gcb_libs.Services.AzureAuthTokenService.d__5.MoveNext() in C:\Source\GCB-Dashboard\gcb-libs\Services\AzureAuthTokenService.cs:line 42

Id Web logs

No response

Relevant code snippets

using gcb_libs.Services;
using gcb_taskscheduler;
using gcb_taskschudler.Jobs;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;
using Quartz;
using System.IO;

var configuration = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json", true, false)
    .AddEnvironmentVariables()
    .AddUserSecrets<Program>()
    .Build();

var host = Host.CreateDefaultBuilder()
    .ConfigureServices(
        (context, services) =>
        {
            services.AddSingleton<IConfiguration>(configuration);
            services
                .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApp(configuration.GetSection("AzureAd"))
                .EnableTokenAcquisitionToCallDownstreamApi()
                .AddInMemoryTokenCaches();
            services.AddMicrosoftIdentityConsentHandler();
            services.AddScoped<AzureAuthTokenService>();
            services.AddScoped<LogService>();
            services.AddScoped<ScheduledJobService>();
            services.AddHostedService<Worker>();

            // Add Quartz Scheduler Service and configure Tasks.
            services.AddQuartz(q =>
            {
                JobKey syncCVEsJobKey = new("syncCVEsJobKey");
                JobKey syncBillDevicesJobKey = new("syncBillDevicesJobKey");
                JobKey syncDattoJobKey = new("syncDattoJobKey");
                JobKey syncAutotaskJobKey = new("syncAutotaskJobKey");
                JobKey syncCyberCNSJobKey = new("syncCyberCNSJobKey");

                q.AddJob<SyncCVEsJob>(
                    opts => opts.WithIdentity(syncCVEsJobKey).WithDescription("Sync CVEs Job")
                );

                q.AddJob<SyncAutotaskJob>(
                    opts => opts.WithIdentity(syncAutotaskJobKey).WithDescription("Sync Autotask Job")
                );
                q.AddJob<SyncDattoJob>(
                    opts => opts.WithIdentity(syncDattoJobKey).WithDescription("Sync Datto Job")
                );

                q.AddJob<SyncCyberCNSJob>(
                    opts => opts.WithIdentity(syncCyberCNSJobKey).WithDescription("Sync CyberCNS Job")
                );

                q.AddTrigger(
                    opts =>
                        opts.ForJob(syncCVEsJobKey)
                            .WithIdentity("syncCVEsJobKey-trigger")
                            .WithCronSchedule("0 0 */2 * * ?")
                );

                q.AddTrigger(
                    opts =>
                        opts.ForJob(syncAutotaskJobKey)
                            .WithIdentity("syncAutotaskJobKey-trigger")
                            .WithCronSchedule("* 10 */2 * * ?")
                );

                q.AddTrigger(
                    opts =>
                        opts.ForJob(syncDattoJobKey)
                            .WithIdentity("syncDattoJobKey-trigger")
                            .WithCronSchedule("* 20 */2 * * ?")
                );

                q.AddTrigger(
                    opts =>
                        opts.ForJob(syncCyberCNSJobKey)
                            .WithIdentity("syncCyberCNSJobKey-trigger")
                            .WithCronSchedule("* 30 */2 * * ?")
                );

                q.SchedulerId = "GCB-Dashboard";
            });

            services.AddQuartzHostedService(options => options.WaitForJobsToComplete = true);
        }
    )
    .Build();

host.Run();

Regression

2.13.2

Expected behavior

I expect it work like 2.13.2

emmanuelpare commented 1 year ago

I also tryed Host.CreateApplicationBuilder(args); with no success same error. (But it work with 2.13.2)

using gcb_libs.Services;
using gcb_taskscheduler;
using gcb_taskschudler.Jobs;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;
using Quartz;
using System.IO;

var configuration = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json", true, false)
    .AddEnvironmentVariables()
    .AddUserSecrets<Program>()
    .Build();

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddSingleton<IConfiguration>(configuration);
builder.Services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApp(configuration.GetSection("AzureAd"))
                .EnableTokenAcquisitionToCallDownstreamApi()
                .AddInMemoryTokenCaches();
builder.Services.AddMicrosoftIdentityConsentHandler();
builder.Services.AddScoped<AzureAuthTokenService>();
builder.Services.AddScoped<LogService>();
builder.Services.AddScoped<ScheduledJobService>();
builder.Services.AddHostedService<Worker>();

// Add Quartz Scheduler Service and configure Tasks.
builder.Services.AddQuartz(q =>
            {
                JobKey syncCVEsJobKey = new("syncCVEsJobKey");
                JobKey syncBillDevicesJobKey = new("syncBillDevicesJobKey");
                JobKey syncDattoJobKey = new("syncDattoJobKey");
                JobKey syncAutotaskJobKey = new("syncAutotaskJobKey");
                JobKey syncCyberCNSJobKey = new("syncCyberCNSJobKey");

                q.AddJob<SyncCVEsJob>(
                    opts => opts.WithIdentity(syncCVEsJobKey).WithDescription("Sync CVEs Job")
                );

                q.AddJob<SyncAutotaskJob>(
                    opts => opts.WithIdentity(syncAutotaskJobKey).WithDescription("Sync Autotask Job")
                );
                q.AddJob<SyncDattoJob>(
                    opts => opts.WithIdentity(syncDattoJobKey).WithDescription("Sync Datto Job")
                );

                q.AddJob<SyncCyberCNSJob>(
                    opts => opts.WithIdentity(syncCyberCNSJobKey).WithDescription("Sync CyberCNS Job")
                );

                q.AddTrigger(
                    opts =>
                        opts.ForJob(syncCVEsJobKey)
                            .WithIdentity("syncCVEsJobKey-trigger")
                            .WithCronSchedule("0 0 */2 * * ?")
                );

                q.AddTrigger(
                    opts =>
                        opts.ForJob(syncAutotaskJobKey)
                            .WithIdentity("syncAutotaskJobKey-trigger")
                            .WithCronSchedule("* 10 */2 * * ?")
                );

                q.AddTrigger(
                    opts =>
                        opts.ForJob(syncDattoJobKey)
                            .WithIdentity("syncDattoJobKey-trigger")
                            .WithCronSchedule("* 20 */2 * * ?")
                );

                q.AddTrigger(
                    opts =>
                        opts.ForJob(syncCyberCNSJobKey)
                            .WithIdentity("syncCyberCNSJobKey-trigger")
                            .WithCronSchedule("* 30 */2 * * ?")
                );

                q.SchedulerId = "GCB-Dashboard";
            });

builder.Services.AddQuartzHostedService(options => options.WaitForJobsToComplete = true);
var host = builder.Build();
host.Run();
emmanuelpare commented 1 year ago

I also tryed without webapp but I got this error:   | Message | "Unable to resolve service for type 'Microsoft.Identity.Web.TokenCacheProviders.IMsalTokenCacheProvider' while attempting to activate 'Microsoft.Identity.Web.TokenAcquisitionAspNetCore'."

using gcb_libs.Services;
using gcb_taskscheduler;
using gcb_taskschudler.Jobs;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;
using Quartz;
using System.IO;

var configuration = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json", true, false)
    .AddEnvironmentVariables()
    .AddUserSecrets<Program>()
    .Build();

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddSingleton<IConfiguration>(configuration);

builder.Services.AddTokenAcquisition()
.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme);

builder.Services.AddMicrosoftIdentityConsentHandler();
builder.Services.AddScoped<AzureAuthTokenService>();
builder.Services.AddScoped<LogService>();
builder.Services.AddScoped<ScheduledJobService>();
builder.Services.AddHostedService<Worker>();

// Add Quartz Scheduler Service and configure Tasks.
builder.Services.AddQuartz(q =>
            {
                JobKey syncCVEsJobKey = new("syncCVEsJobKey");
                JobKey syncBillDevicesJobKey = new("syncBillDevicesJobKey");
                JobKey syncDattoJobKey = new("syncDattoJobKey");
                JobKey syncAutotaskJobKey = new("syncAutotaskJobKey");
                JobKey syncCyberCNSJobKey = new("syncCyberCNSJobKey");

                q.AddJob<SyncCVEsJob>(
                    opts => opts.WithIdentity(syncCVEsJobKey).WithDescription("Sync CVEs Job")
                );

                q.AddJob<SyncAutotaskJob>(
                    opts => opts.WithIdentity(syncAutotaskJobKey).WithDescription("Sync Autotask Job")
                );
                q.AddJob<SyncDattoJob>(
                    opts => opts.WithIdentity(syncDattoJobKey).WithDescription("Sync Datto Job")
                );

                q.AddJob<SyncCyberCNSJob>(
                    opts => opts.WithIdentity(syncCyberCNSJobKey).WithDescription("Sync CyberCNS Job")
                );

                q.AddTrigger(
                    opts =>
                        opts.ForJob(syncCVEsJobKey)
                            .WithIdentity("syncCVEsJobKey-trigger")
                            .WithCronSchedule("0 0 */2 * * ?")
                );

                q.AddTrigger(
                    opts =>
                        opts.ForJob(syncAutotaskJobKey)
                            .WithIdentity("syncAutotaskJobKey-trigger")
                            .WithCronSchedule("* 10 */2 * * ?")
                );

                q.AddTrigger(
                    opts =>
                        opts.ForJob(syncDattoJobKey)
                            .WithIdentity("syncDattoJobKey-trigger")
                            .WithCronSchedule("* 20 */2 * * ?")
                );

                q.AddTrigger(
                    opts =>
                        opts.ForJob(syncCyberCNSJobKey)
                            .WithIdentity("syncCyberCNSJobKey-trigger")
                            .WithCronSchedule("* 30 */2 * * ?")
                );

                q.SchedulerId = "GCB-Dashboard";
            });

builder.Services.AddQuartzHostedService(options => options.WaitForJobsToComplete = true);
var host = builder.Build();
host.Run();
jennyf19 commented 1 year ago

Seems to be a duplicate

jmprieur commented 1 year ago

@emmanuelpare this should be fixed (in the main branch) we are planning to have a release of IdWeb tomorrow PST

emmanuelpare commented 1 year ago

thanks JM I will test the new version for sure and will keep you updated. Iam using it on a daemon console application calling a protected api

emmanuelpare commented 1 year ago

Good news 2.15.1 fixed my issue

jmprieur commented 1 year ago

Thanks for confirming, @emmanuelpare :-)