internal /*for test only*/ static X509Certificate2?LoadFirstCertificate(IEnumerable <CertificateDescription> certificateDescription) { DefaultCertificateLoader defaultCertificateLoader = new DefaultCertificateLoader(); CertificateDescription certDescription = certificateDescription.First(); defaultCertificateLoader.LoadIfNeeded(certDescription); return(certDescription.Certificate); }
/// <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; } }
internal /*for test only*/ static IEnumerable <X509Certificate2?> LoadAllCertificates(IEnumerable <CertificateDescription> certificateDescriptions) { DefaultCertificateLoader defaultCertificateLoader = new DefaultCertificateLoader(); foreach (var certDescription in certificateDescriptions) { defaultCertificateLoader.LoadIfNeeded(certDescription); yield return(certDescription.Certificate); } }
/// <summary> /// Load the first certificate from the certificate description list. /// </summary> /// <param name="certificateDescriptions">Description of the certificates.</param> /// <returns>First certificate in the certificate description list.</returns> public static X509Certificate2?LoadFirstCertificate(IEnumerable <CertificateDescription> certificateDescriptions) { DefaultCertificateLoader defaultCertificateLoader = new DefaultCertificateLoader(); CertificateDescription? certDescription = certificateDescriptions.FirstOrDefault(c => { defaultCertificateLoader.LoadIfNeeded(c); return(c.Certificate != null); }); return(certDescription?.Certificate); }
/// <summary> /// Load all the certificates from the certificate description list. /// </summary> /// <param name="certificateDescriptions">Description of the certificates.</param> /// <returns>All the certificates in the certificate description list.</returns> public static IEnumerable <X509Certificate2?> LoadAllCertificates(IEnumerable <CertificateDescription> certificateDescriptions) { DefaultCertificateLoader defaultCertificateLoader = new DefaultCertificateLoader(); if (certificateDescriptions != null) { foreach (var certDescription in certificateDescriptions) { defaultCertificateLoader.LoadIfNeeded(certDescription); if (certDescription.Certificate != null) { yield return(certDescription.Certificate); } } } }
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); }
/// <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; } }
/// <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; } }
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); } }); }