Example #1
0
        private static void AddMicrosoftIdentityWebApiImplementation(
            AuthenticationBuilder builder,
            Action <JwtBearerOptions> configureJwtBearerOptions,
            Action <MicrosoftIdentityOptions> configureMicrosoftIdentityOptions,
            string jwtBearerScheme,
            bool subscribeToJwtBearerMiddlewareDiagnosticsEvents)
        {
            builder.AddJwtBearer(jwtBearerScheme, configureJwtBearerOptions);
            builder.Services.Configure(jwtBearerScheme, configureMicrosoftIdentityOptions);

            builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton <IValidateOptions <MicrosoftIdentityOptions>, MicrosoftIdentityOptionsValidation>());
            builder.Services.AddHttpContextAccessor();
            builder.Services.AddHttpClient();
            builder.Services.TryAddSingleton <MicrosoftIdentityIssuerValidatorFactory>();
            builder.Services.AddOptions <AadIssuerValidatorOptions>();

            if (subscribeToJwtBearerMiddlewareDiagnosticsEvents)
            {
                builder.Services.AddSingleton <IJwtBearerMiddlewareDiagnostics, JwtBearerMiddlewareDiagnostics>();
            }

            // Change the authentication configuration to accommodate the Microsoft identity platform endpoint (v2.0).
            builder.Services.AddOptions <JwtBearerOptions>(jwtBearerScheme)
            .Configure <IServiceProvider, IOptionsMonitor <MicrosoftIdentityOptions> >((options, serviceProvider, microsoftIdentityOptionsMonitor) =>
            {
                var microsoftIdentityOptions = microsoftIdentityOptionsMonitor.Get(jwtBearerScheme);

                if (string.IsNullOrWhiteSpace(options.Authority))
                {
                    options.Authority = AuthorityHelpers.BuildAuthority(microsoftIdentityOptions);
                }

                // This is a Microsoft identity platform web API
                options.Authority = AuthorityHelpers.EnsureAuthorityIsV2(options.Authority);

                if (options.TokenValidationParameters.AudienceValidator == null &&
                    options.TokenValidationParameters.ValidAudience == null &&
                    options.TokenValidationParameters.ValidAudiences == null)
                {
                    RegisterValidAudience registerAudience = new RegisterValidAudience();
                    registerAudience.RegisterAudienceValidation(
                        options.TokenValidationParameters,
                        microsoftIdentityOptions);
                }

                // If the developer registered an IssuerValidator, do not overwrite it
                if (options.TokenValidationParameters.IssuerValidator == null)
                {
                    // Instead of using the default validation (validating against a single tenant, as we do in line of business apps),
                    // we inject our own multi-tenant validation logic (which even accepts both v1.0 and v2.0 tokens)
                    MicrosoftIdentityIssuerValidatorFactory microsoftIdentityIssuerValidatorFactory =
                        serviceProvider.GetRequiredService <MicrosoftIdentityIssuerValidatorFactory>();

                    options.TokenValidationParameters.IssuerValidator =
                        microsoftIdentityIssuerValidatorFactory.GetAadIssuerValidator(options.Authority).Validate;
                }

                // If you provide a token decryption certificate, it will be used to decrypt the token
                if (microsoftIdentityOptions.TokenDecryptionCertificates != null)
                {
                    options.TokenValidationParameters.TokenDecryptionKey =
                        new X509SecurityKey(DefaultCertificateLoader.LoadFirstCertificate(microsoftIdentityOptions.TokenDecryptionCertificates));
                }

                if (options.Events == null)
                {
                    options.Events = new JwtBearerEvents();
                }

                // When an access token for our own web API is validated, we add it to MSAL.NET's cache so that it can
                // be used from the controllers.
                var tokenValidatedHandler       = options.Events.OnTokenValidated;
                options.Events.OnTokenValidated = async context =>
                {
                    if (!microsoftIdentityOptions.AllowWebApiToBeAuthorizedByACL)
                    {
                        // This check is required to ensure that the web API only accepts tokens from tenants where it has been consented and provisioned.
                        if (!context.Principal.Claims.Any(x => x.Type == ClaimConstants.Scope) &&
                            !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Scp) &&
                            !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Roles) &&
                            !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Role))
                        {
                            throw new UnauthorizedAccessException(IDWebErrorMessage.NeitherScopeOrRolesClaimFoundInToken);
                        }
                    }

                    await tokenValidatedHandler(context).ConfigureAwait(false);
                };

                if (subscribeToJwtBearerMiddlewareDiagnosticsEvents)
                {
                    var diagnostics = serviceProvider.GetRequiredService <IJwtBearerMiddlewareDiagnostics>();

                    diagnostics.Subscribe(options.Events);
                }
            });
        }
Example #2
0
        /// <summary>
        /// Add authentication with Microsoft identity platform.
        /// This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options.
        /// </summary>
        /// <param name="builder">AuthenticationBuilder to which to add this configuration</param>
        /// <param name="configureOpenIdConnectOptions">The IConfiguration object</param>
        /// <param name="configureMicrosoftIdentityOptions">The configuration section with the necessary settings to initialize authentication options</param>
        /// <param name="openIdConnectScheme">The OpenIdConnect scheme name to be used. By default it uses "OpenIdConnect"</param>
        /// <param name="cookieScheme">The Cookies scheme name to be used. By default it uses "Cookies"</param>
        /// <param name="subscribeToOpenIdConnectMiddlewareDiagnosticsEvents">
        /// Set to true if you want to debug, or just understand the OpenIdConnect events.
        /// </param>
        /// <returns>The authentication builder for chaining</returns>
        public static AuthenticationBuilder AddSignIn(
            this AuthenticationBuilder builder,
            Action <OpenIdConnectOptions> configureOpenIdConnectOptions,
            Action <MicrosoftIdentityOptions> configureMicrosoftIdentityOptions,
            string openIdConnectScheme = OpenIdConnectDefaults.AuthenticationScheme,
            string cookieScheme        = CookieAuthenticationDefaults.AuthenticationScheme,
            bool subscribeToOpenIdConnectMiddlewareDiagnosticsEvents = false)
        {
            builder.Services.Configure(openIdConnectScheme, configureOpenIdConnectOptions);
            builder.Services.Configure <MicrosoftIdentityOptions>(configureMicrosoftIdentityOptions);
            builder.Services.AddHttpClient();

            var microsoftIdentityOptions = new MicrosoftIdentityOptions();

            configureMicrosoftIdentityOptions(microsoftIdentityOptions);

            builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton <IValidateOptions <MicrosoftIdentityOptions>, MicrosoftIdentityOptionsValidation>());

            var b2cOidcHandlers = new AzureADB2COpenIDConnectEventHandlers(openIdConnectScheme, microsoftIdentityOptions);

            builder.Services.AddSingleton <IOpenIdConnectMiddlewareDiagnostics, OpenIdConnectMiddlewareDiagnostics>();
            builder.AddCookie(cookieScheme);
            builder.AddOpenIdConnect(openIdConnectScheme, options =>
            {
                options.SignInScheme = cookieScheme;

                if (string.IsNullOrWhiteSpace(options.Authority))
                {
                    options.Authority = AuthorityHelpers.BuildAuthority(microsoftIdentityOptions);
                }

                // This is a Microsoft identity platform Web app
                options.Authority = AuthorityHelpers.EnsureAuthorityIsV2(options.Authority);

                // B2C doesn't have preferred_username claims
                if (microsoftIdentityOptions.IsB2C)
                {
                    options.TokenValidationParameters.NameClaimType = "name";
                }
                else
                {
                    options.TokenValidationParameters.NameClaimType = "preferred_username";
                }

                // If the developer registered an IssuerValidator, do not overwrite it
                if (options.TokenValidationParameters.IssuerValidator == null)
                {
                    // If you want to restrict the users that can sign-in to several organizations
                    // Set the tenant value in the appsettings.json file to 'organizations', and add the
                    // issuers you want to accept to options.TokenValidationParameters.ValidIssuers collection
                    options.TokenValidationParameters.IssuerValidator = AadIssuerValidator.GetIssuerValidator(options.Authority).Validate;
                }

                // Avoids having users being presented the select account dialog when they are already signed-in
                // for instance when going through incremental consent
                var redirectToIdpHandler = options.Events.OnRedirectToIdentityProvider;
                options.Events.OnRedirectToIdentityProvider = async context =>
                {
                    var login = context.Properties.GetParameter <string>(OpenIdConnectParameterNames.LoginHint);
                    if (!string.IsNullOrWhiteSpace(login))
                    {
                        context.ProtocolMessage.LoginHint  = login;
                        context.ProtocolMessage.DomainHint = context.Properties.GetParameter <string>(
                            OpenIdConnectParameterNames.DomainHint);

                        // delete the login_hint and domainHint from the Properties when we are done otherwise
                        // it will take up extra space in the cookie.
                        context.Properties.Parameters.Remove(OpenIdConnectParameterNames.LoginHint);
                        context.Properties.Parameters.Remove(OpenIdConnectParameterNames.DomainHint);
                    }

                    // Additional claims
                    if (context.Properties.Items.ContainsKey(OidcConstants.AdditionalClaims))
                    {
                        context.ProtocolMessage.SetParameter(
                            OidcConstants.AdditionalClaims,
                            context.Properties.Items[OidcConstants.AdditionalClaims]);
                    }

                    if (microsoftIdentityOptions.IsB2C)
                    {
                        context.ProtocolMessage.SetParameter("client_info", "1");
                        // When a new Challenge is returned using any B2C user flow different than susi, we must change
                        // the ProtocolMessage.IssuerAddress to the desired user flow otherwise the redirect would use the susi user flow
                        await b2cOidcHandlers.OnRedirectToIdentityProvider(context).ConfigureAwait(false);
                    }

                    await redirectToIdpHandler(context).ConfigureAwait(false);
                };

                if (microsoftIdentityOptions.IsB2C)
                {
                    var remoteFailureHandler       = options.Events.OnRemoteFailure;
                    options.Events.OnRemoteFailure = async context =>
                    {
                        // Handles the error when a user cancels an action on the Azure Active Directory B2C UI.
                        // Handle the error code that Azure Active Directory B2C throws when trying to reset a password from the login page
                        // because password reset is not supported by a "sign-up or sign-in user flow".
                        await b2cOidcHandlers.OnRemoteFailure(context).ConfigureAwait(false);

                        await remoteFailureHandler(context).ConfigureAwait(false);
                    };
                }

                if (subscribeToOpenIdConnectMiddlewareDiagnosticsEvents)
                {
                    var diags = builder.Services.BuildServiceProvider().GetRequiredService <IOpenIdConnectMiddlewareDiagnostics>();

                    diags.Subscribe(options.Events);
                }
            });

            return(builder);
        }
Example #3
0
        /// <summary>
        /// Protects the Web API with Microsoft identity platform (formerly Azure AD v2.0)
        /// This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options.
        /// </summary>
        /// <param name="builder">AuthenticationBuilder to which to add this configuration.</param>
        /// <param name="configureJwtBearerOptions">The action to configure <see cref="JwtBearerOptions"/>.</param>
        /// <param name="configureMicrosoftIdentityOptions">The action to configure the <see cref="MicrosoftIdentityOptions"/>
        /// configuration options.</param>
        /// <param name="jwtBearerScheme">The JwtBearer scheme name to be used. By default it uses "Bearer".</param>
        /// <param name="tokenDecryptionCertificate">Token decryption certificate.</param>
        /// <param name="subscribeToJwtBearerMiddlewareDiagnosticsEvents">
        /// Set to true if you want to debug, or just understand the JwtBearer events.
        /// </param>
        /// <returns>The authentication builder to chain.</returns>
        public static AuthenticationBuilder AddProtectedWebApi(
            this AuthenticationBuilder builder,
            Action <JwtBearerOptions> configureJwtBearerOptions,
            Action <MicrosoftIdentityOptions> configureMicrosoftIdentityOptions,
            X509Certificate2 tokenDecryptionCertificate = null,
            string jwtBearerScheme = JwtBearerDefaults.AuthenticationScheme,
            bool subscribeToJwtBearerMiddlewareDiagnosticsEvents = false)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }
            builder.Services.Configure(jwtBearerScheme, configureJwtBearerOptions);
            builder.Services.Configure <MicrosoftIdentityOptions>(configureMicrosoftIdentityOptions);

            builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton <IValidateOptions <MicrosoftIdentityOptions>, MicrosoftIdentityOptionsValidation>());
            builder.Services.AddHttpContextAccessor();
            builder.Services.AddSingleton <IJwtBearerMiddlewareDiagnostics, JwtBearerMiddlewareDiagnostics>();
            builder.Services.AddHttpClient();

            // Change the authentication configuration to accommodate the Microsoft identity platform endpoint (v2.0).
            builder.AddJwtBearer(jwtBearerScheme, options =>
            {
                // TODO:
                // Suspect. Why not get the IOption<MicrosoftIdentityOptions>?
                var microsoftIdentityOptions = new MicrosoftIdentityOptions(); // configuration.GetSection(configSectionName).Get<MicrosoftIdentityOptions>();
                configureMicrosoftIdentityOptions(microsoftIdentityOptions);

                if (string.IsNullOrWhiteSpace(options.Authority))
                {
                    options.Authority = AuthorityHelpers.BuildAuthority(microsoftIdentityOptions);
                }

                // This is a Microsoft identity platform Web API
                options.Authority = AuthorityHelpers.EnsureAuthorityIsV2(options.Authority);

                // The valid audience could be given as Client Id or as Uri.
                // If it does not start with 'api://', this variant is added to the list of valid audiences.
                EnsureValidAudiencesContainsApiGuidIfGuidProvided(options, microsoftIdentityOptions);

                // If the developer registered an IssuerValidator, do not overwrite it
                if (options.TokenValidationParameters.IssuerValidator == null)
                {
                    // Instead of using the default validation (validating against a single tenant, as we do in line of business apps),
                    // we inject our own multi-tenant validation logic (which even accepts both v1.0 and v2.0 tokens)
                    options.TokenValidationParameters.IssuerValidator = AadIssuerValidator.GetIssuerValidator(options.Authority).Validate;
                }

                // If you provide a token decryption certificate, it will be used to decrypt the token
                if (tokenDecryptionCertificate != null)
                {
                    options.TokenValidationParameters.TokenDecryptionKey = new X509SecurityKey(tokenDecryptionCertificate);
                }

                if (options.Events == null)
                {
                    options.Events = new JwtBearerEvents();
                }

                // When an access token for our own Web API is validated, we add it to MSAL.NET's cache so that it can
                // be used from the controllers.
                var tokenValidatedHandler       = options.Events.OnTokenValidated;
                options.Events.OnTokenValidated = async context =>
                {
                    // This check is required to ensure that the Web API only accepts tokens from tenants where it has been consented and provisioned.
                    if (!context.Principal.Claims.Any(x => x.Type == ClaimConstants.Scope) &&
                        !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Scp) &&
                        !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Roles) &&
                        !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Role))
                    {
                        throw new UnauthorizedAccessException("Neither scope or roles claim was found in the bearer token.");
                    }

                    await tokenValidatedHandler(context).ConfigureAwait(false);
                };

                if (subscribeToJwtBearerMiddlewareDiagnosticsEvents)
                {
                    var diags = builder.Services.BuildServiceProvider().GetRequiredService <IJwtBearerMiddlewareDiagnostics>();

                    diags.Subscribe(options.Events);
                }
            });

            return(builder);
        }
        private static void AddMicrosoftIdentityWebAppInternal(
            AuthenticationBuilder builder,
            Action <MicrosoftIdentityOptions> configureMicrosoftIdentityOptions,
            Action <CookieAuthenticationOptions>?configureCookieAuthenticationOptions,
            string openIdConnectScheme,
            string?cookieScheme,
            bool subscribeToOpenIdConnectMiddlewareDiagnosticsEvents)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            if (configureMicrosoftIdentityOptions == null)
            {
                throw new ArgumentNullException(nameof(configureMicrosoftIdentityOptions));
            }

            builder.Services.Configure(configureMicrosoftIdentityOptions);
            builder.Services.AddHttpClient();

            builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton <IValidateOptions <MicrosoftIdentityOptions>, MicrosoftIdentityOptionsValidation>());

            if (!string.IsNullOrEmpty(cookieScheme))
            {
                Action <CookieAuthenticationOptions> emptyOption = option => { };
                builder.AddCookie(cookieScheme, configureCookieAuthenticationOptions ?? emptyOption);
            }

            builder.Services.TryAddSingleton <MicrosoftIdentityIssuerValidatorFactory>();
            builder.Services.TryAddSingleton <ILoginErrorAccessor>(ctx =>
            {
                // ITempDataDictionaryFactory is not always available, so we don't require it
                var tempFactory = ctx.GetService <ITempDataDictionaryFactory>();
                var env         = ctx.GetService <IHostEnvironment>(); // ex. Azure Functions will not have an env.

                if (env != null)
                {
                    return(TempDataLoginErrorAccessor.Create(tempFactory, env.IsDevelopment()));
                }
                else
                {
                    return(TempDataLoginErrorAccessor.Create(tempFactory, false));
                }
            });

            if (subscribeToOpenIdConnectMiddlewareDiagnosticsEvents)
            {
                builder.Services.AddSingleton <IOpenIdConnectMiddlewareDiagnostics, OpenIdConnectMiddlewareDiagnostics>();
            }

            if (AppServicesAuthenticationInformation.IsAppServicesAadAuthenticationEnabled)
            {
                builder.Services.AddAuthentication(AppServicesAuthenticationDefaults.AuthenticationScheme)
                .AddAppServicesAuthentication();
                return;
            }

            builder.AddOpenIdConnect(openIdConnectScheme, options => { });
            builder.Services.AddOptions <OpenIdConnectOptions>(openIdConnectScheme)
            .Configure <IServiceProvider, IOptions <MicrosoftIdentityOptions> >((options, serviceProvider, microsoftIdentityOptions) =>
            {
                PopulateOpenIdOptionsFromMicrosoftIdentityOptions(options, microsoftIdentityOptions.Value);

                var b2cOidcHandlers = new AzureADB2COpenIDConnectEventHandlers(
                    openIdConnectScheme,
                    microsoftIdentityOptions.Value,
                    serviceProvider.GetRequiredService <ILoginErrorAccessor>());

                if (!string.IsNullOrEmpty(cookieScheme))
                {
                    options.SignInScheme = cookieScheme;
                }

                if (string.IsNullOrWhiteSpace(options.Authority))
                {
                    options.Authority = AuthorityHelpers.BuildAuthority(microsoftIdentityOptions.Value);
                }

                // This is a Microsoft identity platform web app
                options.Authority = AuthorityHelpers.EnsureAuthorityIsV2(options.Authority);

                // B2C doesn't have preferred_username claims
                if (microsoftIdentityOptions.Value.IsB2C)
                {
                    options.TokenValidationParameters.NameClaimType = ClaimConstants.Name;
                }
                else
                {
                    options.TokenValidationParameters.NameClaimType = ClaimConstants.PreferredUserName;
                }

                // If the developer registered an IssuerValidator, do not overwrite it
                if (options.TokenValidationParameters.ValidateIssuer && options.TokenValidationParameters.IssuerValidator == null)
                {
                    // If you want to restrict the users that can sign-in to several organizations
                    // Set the tenant value in the appsettings.json file to 'organizations', and add the
                    // issuers you want to accept to options.TokenValidationParameters.ValidIssuers collection
                    MicrosoftIdentityIssuerValidatorFactory microsoftIdentityIssuerValidatorFactory =
                        serviceProvider.GetRequiredService <MicrosoftIdentityIssuerValidatorFactory>();

                    options.TokenValidationParameters.IssuerValidator =
                        microsoftIdentityIssuerValidatorFactory.GetAadIssuerValidator(options.Authority).Validate;
                }

                // Avoids having users being presented the select account dialog when they are already signed-in
                // for instance when going through incremental consent
                var redirectToIdpHandler = options.Events.OnRedirectToIdentityProvider;
                options.Events.OnRedirectToIdentityProvider = async context =>
                {
                    var login = context.Properties.GetParameter <string>(OpenIdConnectParameterNames.LoginHint);
                    if (!string.IsNullOrWhiteSpace(login))
                    {
                        context.ProtocolMessage.LoginHint = login;

                        // delete the login_hint from the Properties when we are done otherwise
                        // it will take up extra space in the cookie.
                        context.Properties.Parameters.Remove(OpenIdConnectParameterNames.LoginHint);
                    }

                    var domainHint = context.Properties.GetParameter <string>(OpenIdConnectParameterNames.DomainHint);
                    if (!string.IsNullOrWhiteSpace(domainHint))
                    {
                        context.ProtocolMessage.DomainHint = domainHint;

                        // delete the domain_hint from the Properties when we are done otherwise
                        // it will take up extra space in the cookie.
                        context.Properties.Parameters.Remove(OpenIdConnectParameterNames.DomainHint);
                    }

                    context.ProtocolMessage.SetParameter(Constants.ClientInfo, Constants.One);
                    context.ProtocolMessage.SetParameter(Constants.TelemetryHeaderKey, IdHelper.CreateTelemetryInfo());

                    // Additional claims
                    if (context.Properties.Items.TryGetValue(OidcConstants.AdditionalClaims, out var additionClaims))
                    {
                        context.ProtocolMessage.SetParameter(
                            OidcConstants.AdditionalClaims,
                            additionClaims);
                    }

                    if (microsoftIdentityOptions.Value.IsB2C)
                    {
                        // When a new Challenge is returned using any B2C user flow different than susi, we must change
                        // the ProtocolMessage.IssuerAddress to the desired user flow otherwise the redirect would use the susi user flow
                        await b2cOidcHandlers.OnRedirectToIdentityProvider(context).ConfigureAwait(false);
                    }

                    await redirectToIdpHandler(context).ConfigureAwait(false);
                };

                if (microsoftIdentityOptions.Value.IsB2C)
                {
                    var remoteFailureHandler       = options.Events.OnRemoteFailure;
                    options.Events.OnRemoteFailure = async context =>
                    {
                        // Handles the error when a user cancels an action on the Azure Active Directory B2C UI.
                        // Handle the error code that Azure Active Directory B2C throws when trying to reset a password from the login page
                        // because password reset is not supported by a "sign-up or sign-in user flow".
                        await b2cOidcHandlers.OnRemoteFailure(context).ConfigureAwait(false);

                        await remoteFailureHandler(context).ConfigureAwait(false);
                    };
                }

                if (subscribeToOpenIdConnectMiddlewareDiagnosticsEvents)
                {
                    var diagnostics = serviceProvider.GetRequiredService <IOpenIdConnectMiddlewareDiagnostics>();

                    diagnostics.Subscribe(options.Events);
                }
            });
        }
        private static void AddMicrosoftIdentityWebApiImplementation(
            AuthenticationBuilder builder,
            Action <JwtBearerOptions> configureJwtBearerOptions,
            Action <MicrosoftIdentityOptions> configureMicrosoftIdentityOptions,
            string jwtBearerScheme,
            bool subscribeToJwtBearerMiddlewareDiagnosticsEvents)
        {
            builder.AddJwtBearer(jwtBearerScheme, configureJwtBearerOptions);
            builder.Services.Configure(jwtBearerScheme, configureMicrosoftIdentityOptions);

            builder.Services.AddHttpContextAccessor();
            builder.Services.AddHttpClient();
            builder.Services.TryAddSingleton <MicrosoftIdentityIssuerValidatorFactory>();
            builder.Services.AddRequiredScopeAuthorization();
            builder.Services.AddRequiredScopeOrAppPermissionAuthorization();
            builder.Services.AddOptions <AadIssuerValidatorOptions>();

            if (subscribeToJwtBearerMiddlewareDiagnosticsEvents)
            {
                builder.Services.AddTransient <IJwtBearerMiddlewareDiagnostics, JwtBearerMiddlewareDiagnostics>();
            }

            // Change the authentication configuration to accommodate the Microsoft identity platform endpoint (v2.0).
            builder.Services.AddOptions <JwtBearerOptions>(jwtBearerScheme)
            .Configure <IServiceProvider, IOptionsMonitor <MergedOptions>, IOptionsMonitor <MicrosoftIdentityOptions>, IOptions <MicrosoftIdentityOptions> >((
                                                                                                                                                                 options,
                                                                                                                                                                 serviceProvider,
                                                                                                                                                                 mergedOptionsMonitor,
                                                                                                                                                                 msIdOptionsMonitor,
                                                                                                                                                                 msIdOptions) =>
            {
                MicrosoftIdentityBaseAuthenticationBuilder.SetIdentityModelLogger(serviceProvider);
                MergedOptions mergedOptions = mergedOptionsMonitor.Get(jwtBearerScheme);
                MergedOptions.UpdateMergedOptionsFromJwtBearerOptions(options, mergedOptions);
                MergedOptions.UpdateMergedOptionsFromMicrosoftIdentityOptions(msIdOptions.Value, mergedOptions);
                MergedOptions.UpdateMergedOptionsFromMicrosoftIdentityOptions(msIdOptionsMonitor.Get(jwtBearerScheme), mergedOptions);

                MergedOptionsValidation.Validate(mergedOptions);

                if (string.IsNullOrWhiteSpace(options.Authority))
                {
                    options.Authority = AuthorityHelpers.BuildAuthority(mergedOptions);
                }

                // This is a Microsoft identity platform web API
                options.Authority = AuthorityHelpers.EnsureAuthorityIsV2(options.Authority);

                if (options.TokenValidationParameters.AudienceValidator == null &&
                    options.TokenValidationParameters.ValidAudience == null &&
                    options.TokenValidationParameters.ValidAudiences == null)
                {
                    RegisterValidAudience registerAudience = new RegisterValidAudience();
                    registerAudience.RegisterAudienceValidation(
                        options.TokenValidationParameters,
                        mergedOptions);
                }

                // If the developer registered an IssuerValidator, do not overwrite it
                if (options.TokenValidationParameters.ValidateIssuer && options.TokenValidationParameters.IssuerValidator == null)
                {
                    // Instead of using the default validation (validating against a single tenant, as we do in line of business apps),
                    // we inject our own multi-tenant validation logic (which even accepts both v1.0 and v2.0 tokens)
                    MicrosoftIdentityIssuerValidatorFactory microsoftIdentityIssuerValidatorFactory =
                        serviceProvider.GetRequiredService <MicrosoftIdentityIssuerValidatorFactory>();

                    options.TokenValidationParameters.IssuerValidator =
                        microsoftIdentityIssuerValidatorFactory.GetAadIssuerValidator(options.Authority).Validate;
                }

                // If you provide a token decryption certificate, it will be used to decrypt the token
                if (mergedOptions.TokenDecryptionCertificates != null)
                {
                    DefaultCertificateLoader.UserAssignedManagedIdentityClientId = mergedOptions.UserAssignedManagedIdentityClientId;
                    IEnumerable <X509Certificate2?> certificates          = DefaultCertificateLoader.LoadAllCertificates(mergedOptions.TokenDecryptionCertificates);
                    IEnumerable <X509SecurityKey> keys                    = certificates.Select(c => new X509SecurityKey(c));
                    options.TokenValidationParameters.TokenDecryptionKeys = keys;
                }

                if (options.Events == null)
                {
                    options.Events = new JwtBearerEvents();
                }

                // When an access token for our own web API is validated, we add it to MSAL.NET's cache so that it can
                // be used from the controllers.

                if (!mergedOptions.AllowWebApiToBeAuthorizedByACL)
                {
                    ChainOnTokenValidatedEventForClaimsValidation(options.Events, jwtBearerScheme);
                }

                if (subscribeToJwtBearerMiddlewareDiagnosticsEvents)
                {
                    var diagnostics = serviceProvider.GetRequiredService <IJwtBearerMiddlewareDiagnostics>();

                    diagnostics.Subscribe(options.Events);
                }
            });
        }