/// <summary>
        /// Creates an MSAL Confidential client application.
        /// </summary>
        private async Task <IConfidentialClientApplication> BuildConfidentialClientApplicationAsync()
        {
            if (!_applicationOptions.Instance.EndsWith("/", StringComparison.InvariantCulture))
            {
                _applicationOptions.Instance += "/";
            }

            string authority;
            IConfidentialClientApplication app;

            MicrosoftIdentityOptionsValidation microsoftIdentityOptionsValidation = new MicrosoftIdentityOptionsValidation();

            microsoftIdentityOptionsValidation.ValidateEitherClientCertificateOrClientSecret(
                _applicationOptions.ClientSecret,
                _microsoftIdentityOptions.ClientCertificates);

            try
            {
                var builder = ConfidentialClientApplicationBuilder
                              .CreateWithApplicationOptions(_applicationOptions)
                              .WithRedirectUri(CreateRedirectUri())
                              .WithHttpClientFactory(_httpClientFactory);

                if (_microsoftIdentityOptions.IsB2C)
                {
                    authority = $"{_applicationOptions.Instance}tfp/{_microsoftIdentityOptions.Domain}/{_microsoftIdentityOptions.DefaultUserFlow}";
                    builder.WithB2CAuthority(authority);
                }
                else
                {
                    authority = $"{_applicationOptions.Instance}{_applicationOptions.TenantId}/";
                    builder.WithAuthority(authority);
                }

                if (_microsoftIdentityOptions.ClientCertificates != null)
                {
                    X509Certificate2 certificate = DefaultCertificateLoader.LoadFirstCertificate(_microsoftIdentityOptions.ClientCertificates);
                    builder.WithCertificate(certificate);
                }

                app = builder.Build();
                // Initialize token cache providers
                await _tokenCacheProvider.InitializeAsync(app.AppTokenCache).ConfigureAwait(false);

                await _tokenCacheProvider.InitializeAsync(app.UserTokenCache).ConfigureAwait(false);

                return(app);
            }
            catch (Exception ex)
            {
                _logger.LogInformation(
                    ex,
                    string.Format(
                        CultureInfo.InvariantCulture,
                        "Exception acquiring token for a confidential client. "));
                throw;
            }
        }
Exemple #2
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);
                }
            });
        }
        /// <summary>
        /// Creates an MSAL confidential client application.
        /// </summary>
        private async Task <IConfidentialClientApplication> BuildConfidentialClientApplicationAsync()
        {
            var    request    = CurrentHttpContext?.Request;
            string?currentUri = null;

            if (!string.IsNullOrEmpty(_applicationOptions.RedirectUri))
            {
                currentUri = _applicationOptions.RedirectUri;
            }

            if (request != null && string.IsNullOrEmpty(currentUri))
            {
                currentUri = UriHelper.BuildAbsolute(
                    request.Scheme,
                    request.Host,
                    request.PathBase,
                    _microsoftIdentityOptions.CallbackPath.Value ?? string.Empty);
            }

            PrepareAuthorityInstanceForMsal();

            MicrosoftIdentityOptionsValidation.ValidateEitherClientCertificateOrClientSecret(
                _applicationOptions.ClientSecret,
                _microsoftIdentityOptions.ClientCertificates);

            try
            {
                var builder = ConfidentialClientApplicationBuilder
                              .CreateWithApplicationOptions(_applicationOptions)
                              .WithHttpClientFactory(_httpClientFactory)
                              .WithLogging(
                    Log,
                    ConvertMicrosoftExtensionsLogLevelToMsal(_logger),
                    enablePiiLogging: _applicationOptions.EnablePiiLogging);

                // The redirect URI is not needed for OBO
                if (!string.IsNullOrEmpty(currentUri))
                {
                    builder.WithRedirectUri(currentUri);
                }

                string authority;

                if (_microsoftIdentityOptions.IsB2C)
                {
                    authority = $"{_applicationOptions.Instance}{ClaimConstants.Tfp}/{_microsoftIdentityOptions.Domain}/{_microsoftIdentityOptions.DefaultUserFlow}";
                    builder.WithB2CAuthority(authority);
                }
                else
                {
                    authority = $"{_applicationOptions.Instance}{_applicationOptions.TenantId}/";
                    builder.WithAuthority(authority);
                }

                if (_microsoftIdentityOptions.ClientCertificates != null)
                {
                    X509Certificate2?certificate = DefaultCertificateLoader.LoadFirstCertificate(_microsoftIdentityOptions.ClientCertificates);
                    builder.WithCertificate(certificate);
                }

                IConfidentialClientApplication app = builder.Build();
                _application = app;
                // Initialize token cache providers
                await _tokenCacheProvider.InitializeAsync(app.AppTokenCache).ConfigureAwait(false);

                await _tokenCacheProvider.InitializeAsync(app.UserTokenCache).ConfigureAwait(false);

                return(app);
            }
            catch (Exception ex)
            {
                _logger.LogInformation(
                    ex,
                    IDWebErrorMessage.ExceptionAcquiringTokenForConfidentialClient);
                throw;
            }
        }
        /// <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="tokenDecryptionCertificate">Token decryption certificate.</param>
        /// <param name="jwtBearerScheme">The JwtBearer scheme name to be used. By default it uses "Bearer".</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);

                if (options.TokenValidationParameters.AudienceValidator == 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)
                    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);
                }
                else if (tokenDecryptionCertificate == null && 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 =>
                {
                    // 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);
        }
Exemple #5
0
        /// <summary>
        /// Creates an MSAL Confidential client application.
        /// </summary>
        private async Task <IConfidentialClientApplication> BuildConfidentialClientApplicationAsync()
        {
            var    request    = CurrentHttpContext.Request;
            string currentUri = UriHelper.BuildAbsolute(
                request.Scheme,
                request.Host,
                request.PathBase,
                _microsoftIdentityOptions.CallbackPath.Value ?? string.Empty);

            if (!_applicationOptions.Instance.EndsWith("/", StringComparison.InvariantCulture))
            {
                _applicationOptions.Instance += "/";
            }

            MicrosoftIdentityOptionsValidation.ValidateEitherClientCertificateOrClientSecret(
                _applicationOptions.ClientSecret,
                _microsoftIdentityOptions.ClientCertificates);

            try
            {
                var builder = ConfidentialClientApplicationBuilder
                              .CreateWithApplicationOptions(_applicationOptions)
                              .WithRedirectUri(currentUri)
                              .WithHttpClientFactory(_httpClientFactory);

                string authority;

                if (_microsoftIdentityOptions.IsB2C)
                {
                    authority = $"{_applicationOptions.Instance}tfp/{_microsoftIdentityOptions.Domain}/{_microsoftIdentityOptions.DefaultUserFlow}";
                    builder.WithB2CAuthority(authority);
                }
                else
                {
                    authority = $"{_applicationOptions.Instance}{_applicationOptions.TenantId}/";
                    builder.WithAuthority(authority);
                }

                if (_microsoftIdentityOptions.ClientCertificates != null)
                {
                    X509Certificate2?certificate = DefaultCertificateLoader.LoadFirstCertificate(_microsoftIdentityOptions.ClientCertificates);
                    builder.WithCertificate(certificate);
                }

                IConfidentialClientApplication app = builder.Build();
                // Initialize token cache providers
                await _tokenCacheProvider.InitializeAsync(app.AppTokenCache).ConfigureAwait(false);

                await _tokenCacheProvider.InitializeAsync(app.UserTokenCache).ConfigureAwait(false);

                return(app);
            }
            catch (Exception ex)
            {
                _logger.LogInformation(
                    ex,
                    string.Format(
                        CultureInfo.InvariantCulture,
                        IDWebErrorMessage.ExceptionAcquiringTokenForConfidentialClient));
                throw;
            }
        }
Exemple #6
0
        /// <summary>
        /// Creates an MSAL confidential client application.
        /// </summary>
        private IConfidentialClientApplication BuildConfidentialClientApplication()
        {
            var    httpContext = CurrentHttpContext;
            var    request     = httpContext?.Request;
            string?currentUri  = null;

            if (!string.IsNullOrEmpty(_applicationOptions.RedirectUri))
            {
                currentUri = _applicationOptions.RedirectUri;
            }

            if (request != null && string.IsNullOrEmpty(currentUri))
            {
                currentUri = BuildCurrentUriFromRequest(httpContext !, request);
            }

            PrepareAuthorityInstanceForMsal();

            MicrosoftIdentityOptionsValidation.ValidateEitherClientCertificateOrClientSecret(
                _applicationOptions.ClientSecret,
                _microsoftIdentityOptions.ClientCertificates);

            try
            {
                var builder = ConfidentialClientApplicationBuilder
                              .CreateWithApplicationOptions(_applicationOptions)
                              .WithHttpClientFactory(_httpClientFactory)
                              .WithLogging(
                    Log,
                    ConvertMicrosoftExtensionsLogLevelToMsal(_logger),
                    enablePiiLogging: _applicationOptions.EnablePiiLogging)
                              .WithExperimentalFeatures();

                // The redirect URI is not needed for OBO
                if (!string.IsNullOrEmpty(currentUri))
                {
                    builder.WithRedirectUri(currentUri);
                }

                string authority;

                if (_microsoftIdentityOptions.IsB2C)
                {
                    authority = $"{_applicationOptions.Instance}{ClaimConstants.Tfp}/{_microsoftIdentityOptions.Domain}/{_microsoftIdentityOptions.DefaultUserFlow}";
                    builder.WithB2CAuthority(authority);
                }
                else
                {
                    authority = $"{_applicationOptions.Instance}{_applicationOptions.TenantId}/";
                    builder.WithAuthority(authority);
                }

                if (_microsoftIdentityOptions.ClientCertificates != null)
                {
                    X509Certificate2?certificate = DefaultCertificateLoader.LoadFirstCertificate(_microsoftIdentityOptions.ClientCertificates);
                    builder.WithCertificate(certificate);
                }

                IConfidentialClientApplication app = builder.Build();
                _application = app;
                // Initialize token cache providers
                _tokenCacheProvider.Initialize(app.AppTokenCache);
                _tokenCacheProvider.Initialize(app.UserTokenCache);
                return(app);
            }
            catch (Exception ex)
            {
                Logger.TokenAcquisitionError(
                    _logger,
                    IDWebErrorMessage.ExceptionAcquiringTokenForConfidentialClient,
                    ex);
                throw;
            }
        }