Ejemplo n.º 1
0
        internal static void HandleLegacyTokenDecryptionCertificateParameter(MicrosoftIdentityOptions options, Action <MicrosoftIdentityOptions> configureMicrosoftIdentityOptions, X509Certificate2?tokenDecryptionCertificate)
        {
            // Case where a legacy tokenDecryptionCertificate was passed. We then replace
            // the delegate called by the developer by a delegate which calls the delegate
            // of the developer and insert the certificate in the TokenDecryptionCertificates
            if (tokenDecryptionCertificate != null)
            {
                // Call the method that the developer provided to setup the options
                configureMicrosoftIdentityOptions(options);

                // Prepare a list and add the tokenDecryptionCertificate
                List <CertificateDescription> newCertificateDescriptions = new List <CertificateDescription>
                {
                    CertificateDescription.FromCertificate(tokenDecryptionCertificate),
                };

                // Add as well the token validation certificate descriptions in the options if there are any
                if (options.TokenDecryptionCertificates != null)
                {
                    newCertificateDescriptions.AddRange(options.TokenDecryptionCertificates);
                }
            }
            else
            {
                // just call the method that the developer provided to setup the options
                configureMicrosoftIdentityOptions(options);
            }
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Protects the Web API with Microsoft identity platform (formerly Azure AD v2.0)
        /// This supposes that the configuration files have a section named configSectionName (typically "AzureAD").
        /// </summary>
        /// <param name="services">Service collection to which to add authentication.</param>
        /// <param name="configureConfidentialClientApplicationOptions">The action to configure <see cref="ConfidentialClientApplicationOptions"/>.</param>
        /// <param name="configureMicrosoftIdentityOptions">The action to configure <see cref="MicrosoftIdentityOptions"/>.</param>
        /// <param name="jwtBearerScheme">Scheme for the JwtBearer token.</param>
        /// <returns>The service collection to chain.</returns>
        public static IServiceCollection AddProtectedWebApiCallsProtectedWebApi(
            this IServiceCollection services,
            Action <ConfidentialClientApplicationOptions> configureConfidentialClientApplicationOptions,
            Action <MicrosoftIdentityOptions> configureMicrosoftIdentityOptions,
            string jwtBearerScheme = JwtBearerDefaults.AuthenticationScheme)
        {
            services.Configure <ConfidentialClientApplicationOptions>(configureConfidentialClientApplicationOptions);
            services.Configure <MicrosoftIdentityOptions>(configureMicrosoftIdentityOptions);

            var microsoftIdentityOptions = new MicrosoftIdentityOptions();

            configureMicrosoftIdentityOptions(microsoftIdentityOptions);

            services.AddTokenAcquisition(microsoftIdentityOptions.SingletonTokenAcquisition);
            services.AddHttpContextAccessor();

            services.Configure <JwtBearerOptions>(jwtBearerScheme, options =>
            {
                options.Events ??= new JwtBearerEvents();

                var onTokenValidatedHandler = options.Events.OnTokenValidated;

                options.Events.OnTokenValidated = async context =>
                {
                    await onTokenValidatedHandler(context).ConfigureAwait(false);
                    context.HttpContext.StoreTokenUsedToCallWebAPI(context.SecurityToken as JwtSecurityToken);
                    context.Success();
                };
            });

            return(services);
        }
Ejemplo n.º 3
0
        /// <inheritdoc/>
        public async Task <HttpResponseMessage> CallWebApiForUserAsync(
            string serviceName,
            string?authenticationScheme = null,
            Action <DownstreamWebApiOptions>?calledDownstreamWebApiOptionsOverride = null,
            ClaimsPrincipal?user  = null,
            StringContent?content = null)
        {
            DownstreamWebApiOptions effectiveOptions = MergeOptions(serviceName, calledDownstreamWebApiOptionsOverride);

            if (string.IsNullOrEmpty(effectiveOptions.Scopes))
            {
                throw new ArgumentException(IDWebErrorMessage.ScopesNotConfiguredInConfigurationOrViaDelegate);
            }

            MicrosoftIdentityOptions microsoftIdentityOptions = _microsoftIdentityOptionsMonitor
                                                                .Get(_tokenAcquisition.GetEffectiveAuthenticationScheme(authenticationScheme));

            string apiUrl = effectiveOptions.GetApiUrl();

            CreateProofOfPossessionConfiguration(effectiveOptions, apiUrl);

            string?userflow;

            if (microsoftIdentityOptions.IsB2C && string.IsNullOrEmpty(effectiveOptions.UserFlow))
            {
                userflow = microsoftIdentityOptions.DefaultUserFlow;
            }
            else
            {
                userflow = effectiveOptions.UserFlow;
            }

            AuthenticationResult authResult = await _tokenAcquisition.GetAuthenticationResultForUserAsync(
                effectiveOptions.GetScopes(),
                authenticationScheme,
                effectiveOptions.Tenant,
                userflow,
                user,
                effectiveOptions.TokenAcquisitionOptions)
                                              .ConfigureAwait(false);

            using (HttpRequestMessage httpRequestMessage = new HttpRequestMessage(
                       effectiveOptions.HttpMethod,
                       apiUrl))
            {
                if (content != null)
                {
                    httpRequestMessage.Content = content;
                }

                httpRequestMessage.Headers.Add(
                    Constants.Authorization,
                    authResult.CreateAuthorizationHeader());
                effectiveOptions.CustomizeHttpRequestMessage?.Invoke(httpRequestMessage);
                return(await _httpClient.SendAsync(httpRequestMessage).ConfigureAwait(false));
            }
        }
Ejemplo n.º 4
0
 public AzureADB2COpenIDConnectEventHandlers(
     string schemeName,
     MicrosoftIdentityOptions options,
     ILoginErrorAccessor errorAccessor)
 {
     SchemeName     = schemeName;
     Options        = options;
     _errorAccessor = errorAccessor;
 }
 /// <summary>
 /// Constructor of the TokenAcquisition service. This requires the Azure AD Options to
 /// configure the confidential client application and a token cache provider.
 /// This constructor is called by ASP.NET Core dependency injection
 /// </summary>
 /// <param name="configuration"></param>
 /// <param name="tokenCacheProvider">The App token cache provider</param>
 /// <param name="userTokenCacheProvider">The User token cache provider</param>
 public TokenAcquisition(
     IMsalTokenCacheProvider tokenCacheProvider,
     IHttpContextAccessor httpContextAccessor,
     IOptions <MicrosoftIdentityOptions> microsoftIdentityOptions,
     IOptions <ConfidentialClientApplicationOptions> applicationOptions)
 {
     _httpContextAccessor      = httpContextAccessor;
     _microsoftIdentityOptions = microsoftIdentityOptions.Value;
     _applicationOptions       = applicationOptions.Value;
     _tokenCacheProvider       = tokenCacheProvider;
 }
Ejemplo n.º 6
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="tokenAcquisition">Token acquisition service.</param>
        /// <param name="namedDownstreamWebApiOptions">Named options provider.</param>
        /// <param name="httpClient">HTTP client.</param>
        /// <param name="microsoftIdentityOptions">Configuration options.</param>
        public DownstreamWebApi(
            ITokenAcquisition tokenAcquisition,
            IOptionsMonitor <DownstreamWebApiOptions> namedDownstreamWebApiOptions,
            HttpClient httpClient,
            IOptions <MicrosoftIdentityOptions> microsoftIdentityOptions)
        {
            _tokenAcquisition             = tokenAcquisition;
            _namedDownstreamWebApiOptions = namedDownstreamWebApiOptions;
            _httpClient = httpClient;
#pragma warning disable CA1062 // Validate arguments of public methods
            _microsoftIdentityOptions = microsoftIdentityOptions.Value;
#pragma warning restore CA1062 // Validate arguments of public methods
        }
        internal static string BuildAuthority(MicrosoftIdentityOptions options)
        {
            Uri baseUri  = new Uri(options.Instance);
            var domain   = options.Domain;
            var tenantId = options.TenantId;

            if (options.IsB2C)
            {
                var userFlow = options.DefaultUserFlow;
                return(new Uri(baseUri, new PathString($"{baseUri.PathAndQuery}{domain}/{userFlow}/v2.0")).ToString());
            }

            return(new Uri(baseUri, new PathString($"{baseUri.PathAndQuery}{tenantId}/v2.0")).ToString());
        }
 /// <summary>
 /// Constructor of the TokenAcquisition service. This requires the Azure AD Options to
 /// configure the confidential client application and a token cache provider.
 /// This constructor is called by ASP.NET Core dependency injection.
 /// </summary>
 /// <param name="tokenCacheProvider">The App token cache provider.</param>
 /// <param name="httpContextAccessor">Access to the HttpContext of the request.</param>
 /// <param name="microsoftIdentityOptions">Configuration options.</param>
 /// <param name="applicationOptions">MSAL.NET configuration options.</param>
 /// <param name="httpClientFactory">HTTP client factory.</param>
 /// <param name="logger">Logger.</param>
 public TokenAcquisition(
     IMsalTokenCacheProvider tokenCacheProvider,
     IHttpContextAccessor httpContextAccessor,
     IOptions <MicrosoftIdentityOptions> microsoftIdentityOptions,
     IOptions <ConfidentialClientApplicationOptions> applicationOptions,
     IHttpClientFactory httpClientFactory,
     ILogger <TokenAcquisition> logger)
 {
     _httpContextAccessor      = httpContextAccessor;
     _microsoftIdentityOptions = microsoftIdentityOptions.Value;
     _applicationOptions       = applicationOptions.Value;
     _tokenCacheProvider       = tokenCacheProvider;
     _httpClientFactory        = new MsalAspNetCoreHttpClientFactory(httpClientFactory);
     _logger = logger;
 }
        internal static string BuildAuthority(MicrosoftIdentityOptions options)
        {
            var baseUri  = new Uri(options.Instance);
            var pathBase = baseUri.PathAndQuery.TrimEnd('/');
            var domain   = options.Domain;
            var tenantId = options.TenantId;

            // If there are user flows, then it must build a B2C authority
            if (options.IsB2C)
            {
                var userFlow = options.DefaultUserFlow;
                return(new Uri(baseUri, new PathString($"{pathBase}/{domain}/{userFlow}/v2.0")).ToString());
            }
            else
            {
                return(new Uri(baseUri, new PathString($"{pathBase}/{tenantId}/v2.0")).ToString());
            }
        }
Ejemplo n.º 10
0
        internal static string BuildAuthority(MicrosoftIdentityOptions options)
        {
            if (options == null)
            {
                return(null);
            }

            // Cannot build authority without AAD Instance
            if (string.IsNullOrWhiteSpace(options.Instance))
            {
                return(null);
            }

            var baseUri  = new Uri(options.Instance);
            var pathBase = baseUri.PathAndQuery.TrimEnd('/');
            var domain   = options.Domain;
            var tenantId = options.TenantId;

            // If there are user flows, then it must build a B2C authority
            if (!string.IsNullOrWhiteSpace(options.DefaultUserFlow))
            {
                // Cannot build B2C authority without domain
                if (string.IsNullOrWhiteSpace(domain))
                {
                    return(null);
                }

                var userFlow = options.DefaultUserFlow;
                return(new Uri(baseUri, new PathString($"{pathBase}/{domain}/{userFlow}/v2.0")).ToString());
            }

            else
            {
                // Cannot build AAD authority without tenant id
                if (string.IsNullOrWhiteSpace(tenantId))
                {
                    return(null);
                }

                return(new Uri(baseUri, new PathString($"{pathBase}/{tenantId}/v2.0")).ToString());
            }
        }
        /// <summary>
        /// Constructor of the TokenAcquisition service. This requires the Azure AD Options to
        /// configure the confidential client application and a token cache provider.
        /// This constructor is called by ASP.NET Core dependency injection.
        /// </summary>
        /// <param name="tokenCacheProvider">The App token cache provider.</param>
        /// <param name="httpContextAccessor">Access to the HttpContext of the request.</param>
        /// <param name="microsoftIdentityOptions">Configuration options.</param>
        /// <param name="applicationOptions">MSAL.NET configuration options.</param>
        /// <param name="httpClientFactory">HTTP client factory.</param>
        /// <param name="logger">Logger.</param>
        /// <param name="serviceProvider">Service provider.</param>
        public TokenAcquisition(
            IMsalTokenCacheProvider tokenCacheProvider,
            IHttpContextAccessor httpContextAccessor,
            IOptions <MicrosoftIdentityOptions> microsoftIdentityOptions,
            IOptions <ConfidentialClientApplicationOptions> applicationOptions,
            IHttpClientFactory httpClientFactory,
            ILogger <TokenAcquisition> logger,
            IServiceProvider serviceProvider)
        {
            _httpContextAccessor      = httpContextAccessor;
            _microsoftIdentityOptions = microsoftIdentityOptions.Value;
            _applicationOptions       = applicationOptions.Value;
            _tokenCacheProvider       = tokenCacheProvider;
            _httpClientFactory        = new MsalAspNetCoreHttpClientFactory(httpClientFactory);
            _logger          = logger;
            _serviceProvider = serviceProvider;

            _applicationOptions.ClientId ??= _microsoftIdentityOptions.ClientId;
            _applicationOptions.Instance ??= _microsoftIdentityOptions.Instance;
            _applicationOptions.ClientSecret ??= _microsoftIdentityOptions.ClientSecret;
            _applicationOptions.TenantId ??= _microsoftIdentityOptions.TenantId;
        }
        /// <summary>
        /// Constructor of the TokenAcquisition service. This requires the Azure AD Options to
        /// configure the confidential client application and a token cache provider.
        /// This constructor is called by ASP.NET Core dependency injection.
        /// </summary>
        /// <param name="tokenCacheProvider">The App token cache provider.</param>
        /// <param name="httpContextAccessor">Access to the HttpContext of the request.</param>
        /// <param name="microsoftIdentityOptions">Configuration options.</param>
        /// <param name="applicationOptions">MSAL.NET configuration options.</param>
        /// <param name="httpClientFactory">HTTP client factory.</param>
        /// <param name="logger">Logger.</param>
        /// <param name="serviceProvider">Service provider.</param>
        public TokenAcquisition(
            IMsalTokenCacheProvider tokenCacheProvider,
            IHttpContextAccessor httpContextAccessor,
            IOptions <MicrosoftIdentityOptions> microsoftIdentityOptions,
            IOptions <ConfidentialClientApplicationOptions> applicationOptions,
            IHttpClientFactory httpClientFactory,
            ILogger <TokenAcquisition> logger,
            IServiceProvider serviceProvider)
        {
            _httpContextAccessor      = httpContextAccessor;
            _microsoftIdentityOptions = microsoftIdentityOptions.Value;
            _applicationOptions       = applicationOptions.Value;
            _tokenCacheProvider       = tokenCacheProvider;
            _httpClientFactory        = new MsalAspNetCoreHttpClientFactory(httpClientFactory);
            _logger          = logger;
            _serviceProvider = serviceProvider;

            _applicationOptions.ClientId ??= _microsoftIdentityOptions.ClientId;
            _applicationOptions.Instance ??= _microsoftIdentityOptions.Instance;
            _applicationOptions.ClientSecret ??= _microsoftIdentityOptions.ClientSecret;
            _applicationOptions.TenantId ??= _microsoftIdentityOptions.TenantId;
            _applicationOptions.LegacyCacheCompatibilityEnabled          = _microsoftIdentityOptions.LegacyCacheCompatibilityEnabled;
            DefaultCertificateLoader.UserAssignedManagedIdentityClientId = _microsoftIdentityOptions.UserAssignedManagedIdentityClientId;
        }
Ejemplo n.º 13
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);
        }
Ejemplo n.º 14
0
        internal static void PopulateOpenIdOptionsFromMicrosoftIdentityOptions(OpenIdConnectOptions options, MicrosoftIdentityOptions microsoftIdentityOptions)
        {
            options.Authority                     = microsoftIdentityOptions.Authority;
            options.ClientId                      = microsoftIdentityOptions.ClientId;
            options.ClientSecret                  = microsoftIdentityOptions.ClientSecret;
            options.Configuration                 = microsoftIdentityOptions.Configuration;
            options.ConfigurationManager          = microsoftIdentityOptions.ConfigurationManager;
            options.GetClaimsFromUserInfoEndpoint = microsoftIdentityOptions.GetClaimsFromUserInfoEndpoint;
            foreach (ClaimAction c in microsoftIdentityOptions.ClaimActions)
            {
                options.ClaimActions.Add(c);
            }

            options.RequireHttpsMetadata = microsoftIdentityOptions.RequireHttpsMetadata;
            options.MetadataAddress      = microsoftIdentityOptions.MetadataAddress;
            options.Events                     = microsoftIdentityOptions.Events;
            options.MaxAge                     = microsoftIdentityOptions.MaxAge;
            options.ProtocolValidator          = microsoftIdentityOptions.ProtocolValidator;
            options.SignedOutCallbackPath      = microsoftIdentityOptions.SignedOutCallbackPath;
            options.SignedOutRedirectUri       = microsoftIdentityOptions.SignedOutRedirectUri;
            options.RefreshOnIssuerKeyNotFound = microsoftIdentityOptions.RefreshOnIssuerKeyNotFound;
            options.AuthenticationMethod       = microsoftIdentityOptions.AuthenticationMethod;
            options.Resource                   = microsoftIdentityOptions.Resource;
            options.ResponseMode               = microsoftIdentityOptions.ResponseMode;
            options.ResponseType               = microsoftIdentityOptions.ResponseType;
            options.Prompt                     = microsoftIdentityOptions.Prompt;

            foreach (string scope in microsoftIdentityOptions.Scope)
            {
                options.Scope.Add(scope);
            }

            options.RemoteSignOutPath         = microsoftIdentityOptions.RemoteSignOutPath;
            options.SignOutScheme             = microsoftIdentityOptions.SignOutScheme;
            options.StateDataFormat           = microsoftIdentityOptions.StateDataFormat;
            options.StringDataFormat          = microsoftIdentityOptions.StringDataFormat;
            options.SecurityTokenValidator    = microsoftIdentityOptions.SecurityTokenValidator;
            options.TokenValidationParameters = microsoftIdentityOptions.TokenValidationParameters;
            options.UseTokenLifetime          = microsoftIdentityOptions.UseTokenLifetime;
            options.SkipUnrecognizedRequests  = microsoftIdentityOptions.SkipUnrecognizedRequests;
            options.DisableTelemetry          = microsoftIdentityOptions.DisableTelemetry;
            options.NonceCookie = microsoftIdentityOptions.NonceCookie;
            options.UsePkce     = microsoftIdentityOptions.UsePkce;
#if DOTNET_50_AND_ABOVE
            options.AutomaticRefreshInterval = microsoftIdentityOptions.AutomaticRefreshInterval;
            options.RefreshInterval          = microsoftIdentityOptions.RefreshInterval;
            options.MapInboundClaims         = microsoftIdentityOptions.MapInboundClaims;
#endif
            options.BackchannelTimeout          = microsoftIdentityOptions.BackchannelTimeout;
            options.BackchannelHttpHandler      = microsoftIdentityOptions.BackchannelHttpHandler;
            options.Backchannel                 = microsoftIdentityOptions.Backchannel;
            options.DataProtectionProvider      = microsoftIdentityOptions.DataProtectionProvider;
            options.CallbackPath                = microsoftIdentityOptions.CallbackPath;
            options.AccessDeniedPath            = microsoftIdentityOptions.AccessDeniedPath;
            options.ReturnUrlParameter          = microsoftIdentityOptions.ReturnUrlParameter;
            options.SignInScheme                = microsoftIdentityOptions.SignInScheme;
            options.RemoteAuthenticationTimeout = microsoftIdentityOptions.RemoteAuthenticationTimeout;
            options.Events                 = microsoftIdentityOptions.Events;
            options.SaveTokens             = microsoftIdentityOptions.SaveTokens;
            options.CorrelationCookie      = microsoftIdentityOptions.CorrelationCookie;
            options.ClaimsIssuer           = microsoftIdentityOptions.ClaimsIssuer;
            options.Events                 = microsoftIdentityOptions.Events;
            options.EventsType             = microsoftIdentityOptions.EventsType;
            options.ForwardDefault         = microsoftIdentityOptions.ForwardDefault;
            options.ForwardAuthenticate    = microsoftIdentityOptions.ForwardAuthenticate;
            options.ForwardChallenge       = microsoftIdentityOptions.ForwardChallenge;
            options.ForwardForbid          = microsoftIdentityOptions.ForwardForbid;
            options.ForwardSignIn          = microsoftIdentityOptions.ForwardSignIn;
            options.ForwardSignOut         = microsoftIdentityOptions.ForwardSignOut;
            options.ForwardDefaultSelector = microsoftIdentityOptions.ForwardDefaultSelector;
        }
Ejemplo n.º 15
0
 public AzureADB2COpenIDConnectEventHandlers(string schemeName, MicrosoftIdentityOptions options)
 {
     SchemeName = schemeName;
     Options    = options;
 }
Ejemplo n.º 16
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);
        }
Ejemplo n.º 17
0
        /// <summary>
        /// Ensure that if the audience is a GUID, api://{audience} is also added
        /// as a valid audience (this is the default App ID URL in the app registration
        /// portal).
        /// </summary>
        /// <param name="options"><see cref="JwtBearerOptions"/> for which to ensure that
        /// api://GUID is a valid audience.</param>
        /// <param name="msIdentityOptions">Configuration options.</param>
        internal static void EnsureValidAudiencesContainsApiGuidIfGuidProvided(JwtBearerOptions options, MicrosoftIdentityOptions msIdentityOptions)
        {
            options.TokenValidationParameters.ValidAudiences ??= new List <string>();
            var validAudiences = new List <string>(options.TokenValidationParameters.ValidAudiences);

            if (!string.IsNullOrWhiteSpace(options.Audience))
            {
                validAudiences.Add(options.Audience);
                if (!options.Audience.StartsWith("api://", StringComparison.OrdinalIgnoreCase) &&
                    Guid.TryParse(options.Audience, out _))
                {
                    validAudiences.Add($"api://{options.Audience}");
                }
            }
            else
            {
                validAudiences.Add(msIdentityOptions.ClientId);
                validAudiences.Add($"api://{msIdentityOptions.ClientId}");
            }
            options.TokenValidationParameters.ValidAudiences = validAudiences;
        }
Ejemplo n.º 18
0
        /// <summary>
        /// Add MSAL support to the Web App or Web API.
        /// </summary>
        /// <param name="services">Service collection to which to add authentication.</param>
        /// <param name="initialScopes">Initial scopes to request at sign-in.</param>
        /// <param name="configureMicrosoftIdentityOptions">The action to set the <see cref="MicrosoftIdentityOptions"/>.</param>
        /// <param name="configureConfidentialClientApplicationOptions">The action to set the <see cref="ConfidentialClientApplicationOptions"/>.</param>
        /// <param name="openIdConnectScheme">Optional name for the open id connect authentication scheme
        /// (by default OpenIdConnectDefaults.AuthenticationScheme). This can be specified when you want to support
        /// several OpenIdConnect identity providers.</param>
        /// <returns>The service collection for chaining.</returns>
        public static IServiceCollection AddWebAppCallsProtectedWebApi(
            this IServiceCollection services,
            IEnumerable <string> initialScopes,
            Action <MicrosoftIdentityOptions> configureMicrosoftIdentityOptions,
            Action <ConfidentialClientApplicationOptions> configureConfidentialClientApplicationOptions,
            string openIdConnectScheme = OpenIdConnectDefaults.AuthenticationScheme)
        {
            // Ensure that configuration options for MSAL.NET, HttpContext accessor and the Token acquisition service
            // (encapsulating MSAL.NET) are available through dependency injection
            services.Configure <MicrosoftIdentityOptions>(configureMicrosoftIdentityOptions);
            services.Configure <ConfidentialClientApplicationOptions>(configureConfidentialClientApplicationOptions);

            services.AddHttpContextAccessor();

            var microsoftIdentityOptions = new MicrosoftIdentityOptions();

            configureMicrosoftIdentityOptions(microsoftIdentityOptions);

            services.AddTokenAcquisition(microsoftIdentityOptions.SingletonTokenAcquisition);

            services.Configure <OpenIdConnectOptions>(openIdConnectScheme, options =>
            {
                options.ResponseType = OpenIdConnectResponseType.CodeIdToken;

                // This scope is needed to get a refresh token when users sign-in with their Microsoft personal accounts
                // It's required by MSAL.NET and automatically provided when users sign-in with work or school accounts
                options.Scope.Add(OidcConstants.ScopeOfflineAccess);
                if (initialScopes != null)
                {
                    foreach (string scope in initialScopes)
                    {
                        if (!options.Scope.Contains(scope))
                        {
                            options.Scope.Add(scope);
                        }
                    }
                }

                // Handling the auth redemption by MSAL.NET so that a token is available in the token cache
                // where it will be usable from Controllers later (through the TokenAcquisition service)
                var codeReceivedHandler = options.Events.OnAuthorizationCodeReceived;
                options.Events.OnAuthorizationCodeReceived = async context =>
                {
                    var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService <ITokenAcquisition>() as ITokenAcquisitionInternal;
                    await tokenAcquisition.AddAccountToCacheFromAuthorizationCodeAsync(context, options.Scope).ConfigureAwait(false);
                    await codeReceivedHandler(context).ConfigureAwait(false);
                };

                // Handling the token validated to get the client_info for cases where tenantId is not present (example: B2C)
                var onTokenValidatedHandler     = options.Events.OnTokenValidated;
                options.Events.OnTokenValidated = async context =>
                {
                    ClientInfo clientInfoFromServer;
                    if (context.Request.Form.ContainsKey(ClaimConstants.ClientInfo))
                    {
                        context.Request.Form.TryGetValue(ClaimConstants.ClientInfo, out Microsoft.Extensions.Primitives.StringValues value);

                        if (!string.IsNullOrEmpty(value))
                        {
                            clientInfoFromServer = ClientInfo.CreateFromJson(value);

                            if (clientInfoFromServer != null)
                            {
                                context.Principal.Identities.FirstOrDefault().AddClaim(new Claim(ClaimConstants.UniqueTenantIdentifier, clientInfoFromServer.UniqueTenantIdentifier));
                                context.Principal.Identities.FirstOrDefault().AddClaim(new Claim(ClaimConstants.UniqueObjectIdentifier, clientInfoFromServer.UniqueObjectIdentifier));
                            }
                        }
                    }

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

                // Handling the sign-out: removing the account from MSAL.NET cache
                var signOutHandler = options.Events.OnRedirectToIdentityProviderForSignOut;
                options.Events.OnRedirectToIdentityProviderForSignOut = async context =>
                {
                    // Remove the account from MSAL.NET token cache
                    var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService <ITokenAcquisition>() as ITokenAcquisitionInternal;
                    await tokenAcquisition.RemoveAccountAsync(context).ConfigureAwait(false);
                    await signOutHandler(context).ConfigureAwait(false);
                };
            });
            return(services);
        }
Ejemplo n.º 19
0
        internal static void UpdateMergedOptionsFromMicrosoftIdentityOptions(MicrosoftIdentityOptions microsoftIdentityOptions, MergedOptions mergedOptions)
        {
            mergedOptions.ResetPasswordPath = microsoftIdentityOptions.ResetPasswordPath;
            mergedOptions.ErrorPath         = microsoftIdentityOptions.ErrorPath;
            mergedOptions.AccessDeniedPath  = microsoftIdentityOptions.AccessDeniedPath;
            mergedOptions.AllowWebApiToBeAuthorizedByACL = microsoftIdentityOptions.AllowWebApiToBeAuthorizedByACL;
            mergedOptions.AuthenticationMethod           = microsoftIdentityOptions.AuthenticationMethod;
            if (string.IsNullOrEmpty(mergedOptions.Authority) && !string.IsNullOrEmpty(microsoftIdentityOptions.Authority))
            {
                mergedOptions.Authority = microsoftIdentityOptions.Authority;
            }

#if DOTNET_50_AND_ABOVE
            mergedOptions.AutomaticRefreshInterval = microsoftIdentityOptions.AutomaticRefreshInterval;
#endif
            mergedOptions.Backchannel ??= microsoftIdentityOptions.Backchannel;
            mergedOptions.BackchannelHttpHandler ??= microsoftIdentityOptions.BackchannelHttpHandler;
            mergedOptions.BackchannelTimeout = microsoftIdentityOptions.BackchannelTimeout;
            mergedOptions.CallbackPath       = microsoftIdentityOptions.CallbackPath;
            if (string.IsNullOrEmpty(mergedOptions.ClaimsIssuer) && !string.IsNullOrEmpty(microsoftIdentityOptions.ClaimsIssuer))
            {
                mergedOptions.ClaimsIssuer = microsoftIdentityOptions.ClaimsIssuer;
            }

            mergedOptions.ClientCertificates ??= microsoftIdentityOptions.ClientCertificates;
            if (string.IsNullOrEmpty(mergedOptions.ClientId) && !string.IsNullOrEmpty(microsoftIdentityOptions.ClientId))
            {
                mergedOptions.ClientId = microsoftIdentityOptions.ClientId;
            }

            if (string.IsNullOrEmpty(mergedOptions.ClientSecret) && !string.IsNullOrEmpty(microsoftIdentityOptions.ClientSecret))
            {
                mergedOptions.ClientSecret = microsoftIdentityOptions.ClientSecret;
            }

            mergedOptions.Configuration ??= microsoftIdentityOptions.Configuration;
            mergedOptions.ConfigurationManager ??= microsoftIdentityOptions.ConfigurationManager;
            mergedOptions.CorrelationCookie = microsoftIdentityOptions.CorrelationCookie;
            mergedOptions.DataProtectionProvider ??= microsoftIdentityOptions.DataProtectionProvider;
            mergedOptions.DisableTelemetry = microsoftIdentityOptions.DisableTelemetry;

            if (string.IsNullOrEmpty(mergedOptions.Domain) && !string.IsNullOrEmpty(microsoftIdentityOptions.Domain))
            {
                mergedOptions.Domain = microsoftIdentityOptions.Domain;
            }

            if (string.IsNullOrEmpty(mergedOptions.EditProfilePolicyId) && !string.IsNullOrEmpty(microsoftIdentityOptions.EditProfilePolicyId))
            {
                mergedOptions.EditProfilePolicyId = microsoftIdentityOptions.EditProfilePolicyId;
            }

            mergedOptions.Events.OnAccessDenied                         += microsoftIdentityOptions.Events.OnAccessDenied;
            mergedOptions.Events.OnAuthenticationFailed                 += microsoftIdentityOptions.Events.OnAuthenticationFailed;
            mergedOptions.Events.OnAuthorizationCodeReceived            += microsoftIdentityOptions.Events.OnAuthorizationCodeReceived;
            mergedOptions.Events.OnMessageReceived                      += microsoftIdentityOptions.Events.OnMessageReceived;
            mergedOptions.Events.OnRedirectToIdentityProvider           += microsoftIdentityOptions.Events.OnRedirectToIdentityProvider;
            mergedOptions.Events.OnRedirectToIdentityProviderForSignOut += microsoftIdentityOptions.Events.OnRedirectToIdentityProviderForSignOut;
            mergedOptions.Events.OnRemoteFailure                        += microsoftIdentityOptions.Events.OnRemoteFailure;
            mergedOptions.Events.OnRemoteSignOut                        += microsoftIdentityOptions.Events.OnRemoteSignOut;
            mergedOptions.Events.OnSignedOutCallbackRedirect            += microsoftIdentityOptions.Events.OnSignedOutCallbackRedirect;
            mergedOptions.Events.OnTicketReceived                       += microsoftIdentityOptions.Events.OnTicketReceived;
            mergedOptions.Events.OnTokenResponseReceived                += microsoftIdentityOptions.Events.OnTokenResponseReceived;
            mergedOptions.Events.OnTokenValidated                       += microsoftIdentityOptions.Events.OnTokenValidated;
            mergedOptions.Events.OnUserInformationReceived              += microsoftIdentityOptions.Events.OnUserInformationReceived;

            mergedOptions.EventsType ??= microsoftIdentityOptions.EventsType;
            if (string.IsNullOrEmpty(mergedOptions.ForwardAuthenticate) && !string.IsNullOrEmpty(microsoftIdentityOptions.ForwardAuthenticate))
            {
                mergedOptions.ForwardAuthenticate = microsoftIdentityOptions.ForwardAuthenticate;
            }

            if (string.IsNullOrEmpty(mergedOptions.ForwardChallenge) && !string.IsNullOrEmpty(microsoftIdentityOptions.ForwardChallenge))
            {
                mergedOptions.ForwardChallenge = microsoftIdentityOptions.ForwardChallenge;
            }

            if (string.IsNullOrEmpty(mergedOptions.ForwardDefault) && !string.IsNullOrEmpty(microsoftIdentityOptions.ForwardDefault))
            {
                mergedOptions.ForwardDefault = microsoftIdentityOptions.ForwardDefault;
            }

            mergedOptions.ForwardDefaultSelector ??= microsoftIdentityOptions.ForwardDefaultSelector;
            if (string.IsNullOrEmpty(mergedOptions.ForwardForbid) && !string.IsNullOrEmpty(microsoftIdentityOptions.ForwardForbid))
            {
                mergedOptions.ForwardForbid = microsoftIdentityOptions.ForwardForbid;
            }

            if (string.IsNullOrEmpty(mergedOptions.ForwardSignIn) && !string.IsNullOrEmpty(microsoftIdentityOptions.ForwardSignIn))
            {
                mergedOptions.ForwardSignIn = microsoftIdentityOptions.ForwardSignIn;
            }

            if (string.IsNullOrEmpty(mergedOptions.ForwardSignOut) && !string.IsNullOrEmpty(microsoftIdentityOptions.ForwardSignOut))
            {
                mergedOptions.ForwardSignOut = microsoftIdentityOptions.ForwardSignOut;
            }

            mergedOptions.GetClaimsFromUserInfoEndpoint = microsoftIdentityOptions.GetClaimsFromUserInfoEndpoint;
            if (string.IsNullOrEmpty(mergedOptions.Instance) && !string.IsNullOrEmpty(microsoftIdentityOptions.Instance))
            {
                mergedOptions.Instance = microsoftIdentityOptions.Instance;
            }

            mergedOptions.LegacyCacheCompatibilityEnabled = microsoftIdentityOptions.LegacyCacheCompatibilityEnabled;

#if DOTNET_50_AND_ABOVE
            mergedOptions.MapInboundClaims = microsoftIdentityOptions.MapInboundClaims;
#endif

            mergedOptions.MaxAge = microsoftIdentityOptions.MaxAge;
            if (string.IsNullOrEmpty(mergedOptions.MetadataAddress) && !string.IsNullOrEmpty(microsoftIdentityOptions.MetadataAddress))
            {
                mergedOptions.MetadataAddress = microsoftIdentityOptions.MetadataAddress;
            }

            mergedOptions.NonceCookie = microsoftIdentityOptions.NonceCookie;
            if (string.IsNullOrEmpty(mergedOptions.Prompt) && !string.IsNullOrEmpty(microsoftIdentityOptions.Prompt))
            {
                mergedOptions.Prompt = microsoftIdentityOptions.Prompt;
            }

            mergedOptions.ProtocolValidator ??= microsoftIdentityOptions.ProtocolValidator;

#if DOTNET_50_AND_ABOVE
            mergedOptions.RefreshInterval = microsoftIdentityOptions.RefreshInterval;
#endif

            mergedOptions.RefreshOnIssuerKeyNotFound  = microsoftIdentityOptions.RefreshOnIssuerKeyNotFound;
            mergedOptions.RemoteAuthenticationTimeout = microsoftIdentityOptions.RemoteAuthenticationTimeout;
            mergedOptions.RemoteSignOutPath           = microsoftIdentityOptions.RemoteSignOutPath;
            mergedOptions.RequireHttpsMetadata        = microsoftIdentityOptions.RequireHttpsMetadata;
            if (string.IsNullOrEmpty(mergedOptions.ResetPasswordPolicyId) && !string.IsNullOrEmpty(microsoftIdentityOptions.ResetPasswordPolicyId))
            {
                mergedOptions.ResetPasswordPolicyId = microsoftIdentityOptions.ResetPasswordPolicyId;
            }

            if (string.IsNullOrEmpty(mergedOptions.Resource) && !string.IsNullOrEmpty(microsoftIdentityOptions.Resource))
            {
                mergedOptions.Resource = microsoftIdentityOptions.Resource;
            }

            if (string.IsNullOrEmpty(mergedOptions.ResponseMode) && !string.IsNullOrEmpty(microsoftIdentityOptions.ResponseMode))
            {
                mergedOptions.ResponseMode = microsoftIdentityOptions.ResponseMode;
            }

            mergedOptions.ResponseType = microsoftIdentityOptions.ResponseType;

            if (string.IsNullOrEmpty(mergedOptions.ReturnUrlParameter) && !string.IsNullOrEmpty(microsoftIdentityOptions.ReturnUrlParameter))
            {
                mergedOptions.ReturnUrlParameter = microsoftIdentityOptions.ReturnUrlParameter;
            }

            mergedOptions.SaveTokens = microsoftIdentityOptions.SaveTokens;
            mergedOptions.SecurityTokenValidator ??= microsoftIdentityOptions.SecurityTokenValidator;
            mergedOptions.SendX5C               = microsoftIdentityOptions.SendX5C;
            mergedOptions.WithSpaAuthCode       = microsoftIdentityOptions.WithSpaAuthCode;
            mergedOptions.SignedOutCallbackPath = microsoftIdentityOptions.SignedOutCallbackPath;
            if (string.IsNullOrEmpty(mergedOptions.SignedOutRedirectUri) && !string.IsNullOrEmpty(microsoftIdentityOptions.SignedOutRedirectUri))
            {
                mergedOptions.SignedOutRedirectUri = microsoftIdentityOptions.SignedOutRedirectUri;
            }

            if (string.IsNullOrEmpty(mergedOptions.SignInScheme) && !string.IsNullOrEmpty(microsoftIdentityOptions.SignInScheme))
            {
                mergedOptions.SignInScheme = microsoftIdentityOptions.SignInScheme;
            }

            if (string.IsNullOrEmpty(mergedOptions.SignOutScheme) && !string.IsNullOrEmpty(microsoftIdentityOptions.SignOutScheme))
            {
                mergedOptions.SignOutScheme = microsoftIdentityOptions.SignOutScheme;
            }

            if (string.IsNullOrEmpty(mergedOptions.SignUpSignInPolicyId) && !string.IsNullOrEmpty(microsoftIdentityOptions.SignUpSignInPolicyId))
            {
                mergedOptions.SignUpSignInPolicyId = microsoftIdentityOptions.SignUpSignInPolicyId;
            }

            mergedOptions.SkipUnrecognizedRequests = microsoftIdentityOptions.SkipUnrecognizedRequests;
            mergedOptions.StateDataFormat ??= microsoftIdentityOptions.StateDataFormat;
            mergedOptions.StringDataFormat ??= microsoftIdentityOptions.StringDataFormat;
            if (string.IsNullOrEmpty(mergedOptions.TenantId) && !string.IsNullOrEmpty(microsoftIdentityOptions.TenantId))
            {
                mergedOptions.TenantId = microsoftIdentityOptions.TenantId;
            }

            mergedOptions.TokenDecryptionCertificates ??= microsoftIdentityOptions.TokenDecryptionCertificates;
            mergedOptions.TokenValidationParameters = microsoftIdentityOptions.TokenValidationParameters.Clone();
            mergedOptions.UsePkce = microsoftIdentityOptions.UsePkce;
            if (string.IsNullOrEmpty(mergedOptions.UserAssignedManagedIdentityClientId) && !string.IsNullOrEmpty(microsoftIdentityOptions.UserAssignedManagedIdentityClientId))
            {
                mergedOptions.UserAssignedManagedIdentityClientId = microsoftIdentityOptions.UserAssignedManagedIdentityClientId;
            }

            mergedOptions.ClientCredentialsUsingManagedIdentity ??= microsoftIdentityOptions.ClientCredentialsUsingManagedIdentity;
            mergedOptions.UseTokenLifetime = microsoftIdentityOptions.UseTokenLifetime;
            mergedOptions._confidentialClientApplicationOptions = null;

            mergedOptions.Scope.Clear();

            foreach (var scope in microsoftIdentityOptions.Scope)
            {
                if (!string.IsNullOrWhiteSpace(scope) && !mergedOptions.Scope.Any(s => string.Equals(s, scope, StringComparison.OrdinalIgnoreCase)))
                {
                    mergedOptions.Scope.Add(scope);
                }
            }
        }