microsoft-identity-web: [Bug] Microsoft.Identity.Web.MergedOptions.PrepareAuthorityInstanceForMsal() throws a null reference exception

Which version of Microsoft Identity Web are you using? Note that to get help, you need to run the latest version.

Version is 1.22.3

Where is the issue?

  • Web app
    • Sign-in users
    • Sign-in users and call web APIs
  • Web API
    • Protected web APIs (validating tokens)
    • Protected web APIs (validating scopes)
    • Protected web APIs call downstream web APIs
  • Token cache serialization
    • In-memory caches
    • Session caches
    • Distributed caches
  • Other (please describe)

Is this a new or an existing app?

This is a new app Repro I have the exact same problem as in #1507 . There’s the setup code:

            services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApp(
                    openIdConnectScheme: OpenIdConnectDefaults.AuthenticationScheme,
                    displayName: "Azure AD B2C",
                    configureMicrosoftIdentityOptions: options =>
                        ConfigureMicrosoftIdentityOptions(
                            options,
                            shellSettings),
                    configureCookieAuthenticationOptions: ConfigureCookieAuthenticationOptions,
                    cookieScheme: null,
                    subscribeToOpenIdConnectMiddlewareDiagnosticsEvents: env.IsDevelopment())
                .EnableTokenAcquisitionToCallDownstreamApi()
                .AddDownstreamWebApi(AuthenticationConstants.IdentityApiName,
                    options => { Configuration.GetSection("Portal:Authentication:Api").Bind(options); })
                .AddDistributedTokenCaches();

ConfigureMicrosoftIdentityOptions is defined below:

    protected virtual void ConfigureMicrosoftIdentityOptions(
        MicrosoftIdentityOptions options,
        ShellSettings shellSettings)
    {
        Configuration.GetSection(PortalAuthenticationConfigSection).Bind(options);
        var signInPolicies = Configuration.GetSection(PortalAuthenticationConfigSection).Get<SignInPolicyOptions>();
        options.SignUpSignInPolicyId = GetSignInPolicy(signInPolicies);
        var urlPrefix = !string.IsNullOrEmpty(shellSettings.RequestUrlPrefix)
            ? shellSettings.RequestUrlPrefix.Trim('/').PadLeft(1, '/')
            : null;
        options.CallbackPath = $"{urlPrefix}/{AuthenticationConstants.CallbackPaths.CallbackPath}";
        options.SignedOutCallbackPath = $"{urlPrefix}/{AuthenticationConstants.CallbackPaths.SignedOutCallbackPath}";
        options.SignedOutRedirectUri = $"{urlPrefix}/{AuthenticationConstants.CallbackPaths.SignedOutPath}";
        options.RemoteSignOutPath = $"{urlPrefix}/{AuthenticationConstants.CallbackPaths.RemoteSignOutPath}";
        options.ErrorPath = $"{urlPrefix}/{AuthenticationConstants.CallbackPaths.ErrorPath}";
        options.ResetPasswordPath = $"{urlPrefix}/{AuthenticationConstants.CallbackPaths.ResetPasswordPath}";
        options.AccessDeniedPath = $"{urlPrefix}/{AuthenticationConstants.CallbackPaths.AccessDeniedPath}";
        options.GetClaimsFromUserInfoEndpoint = true;
        //options.SaveTokens = true;
        options.SignInScheme = IdentityConstants.ExternalScheme;
        //options.ForwardDefault = IdentityConstants.ExternalScheme;
        options.TokenValidationParameters = new()
        {
            NameClaimType = "name"
        };
}

I then inject an instance of IDownstreamWebApi into a controller and call it as follows:

                    var organizations =  await _api.CallWebApiForUserAsync<OrganizationSummary[]>(AuthenticationConstants.IdentityApiName,
                        api =>
                        {
                            api.RelativePath = "organizations/oauth2";
                            api.HttpMethod = HttpMethod.Get;
                        });

I can sign in fine using the above code, so authentication works ok. It’s when I need to call the downstream api that I run into issues Expected behavior A clear and concise description of what you expected to happen (or code). I should be able to call the downstream API without errors Actual behavior IDownsteamWebApi.CallWebApiForUserAsync throws NullReferenceException with the following 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.<GetAuthenticationResultForUserAsync>d__16.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at Microsoft.Identity.Web.DownstreamWebApi.<CallWebApiForUserAsync>d__5.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at MyNamespace.OnboardingController.<Index>d__19.MoveNext() 

Possible solution

Additional context / logs / screenshots / link to code I tried manually configurating the ConfidentialClientApplicationOptions but it made no difference.

            services.PostConfigure<ConfidentialClientApplicationOptions>(OpenIdConnectDefaults.AuthenticationScheme,
                options =>
                {
                    Configuration.GetSection(PortalAuthenticationConfigSection).Bind(options);
                    options.EnablePiiLogging = env.IsDevelopment();
                    Debug.Assert(!string.IsNullOrEmpty(options.Instance), "!string.IsNullOrEmpty(options.Instance)");
                });

The above code runs ok and i can confirm that Instance was not null at that point but it some how still gets overwritten somewhere

Add any other context about the problem here, such as logs and screenshots, or even links to code.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 18

Most upvoted comments

Upon further investigation it seems that the problem might be in Microsoft.Identity.Web.TokenAcquisition

        public string GetEffectiveAuthenticationScheme(string? authenticationScheme)
        {
            if (authenticationScheme != null)
            {
                return authenticationScheme;
            }
            else
            {
                return _serviceProvider.GetService<IAuthenticationSchemeProvider>()?.GetDefaultAuthenticateSchemeAsync()?.Result?.Name ??
                    ((CurrentHttpContext?.GetTokenUsedToCallWebAPI() != null)
                    ? JwtBearerDefaults.AuthenticationScheme : OpenIdConnectDefaults.AuthenticationScheme);
            }
        }

For what ever reason when the authentication scheme is not explicitly specified it might not be returning the right scheme. When I explicitly set the authentication scheme to OpenIdConnectionDefaults.AuthenticationScheme it doesn’t throw a null reference exception:

                    var organizations = await _api.CallWebApiForUserAsync<OrganizationSummary[]>(
                        AuthenticationConstants.IdentityApiName,
                        api =>
                        {
                            api.RelativePath = "organizations/oauth2";
                            api.HttpMethod = HttpMethod.Get;
                        },
                        authenticationScheme: OpenIdConnectDefaults.AuthenticationScheme);

Released in 1.24.0.