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); } }
/// <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); }
/// <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)); } }
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; }
/// <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()); } }
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; }
/// <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); }
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; }
public AzureADB2COpenIDConnectEventHandlers(string schemeName, MicrosoftIdentityOptions options) { SchemeName = schemeName; Options = options; }
/// <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); }
/// <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; }
/// <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); }
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); } } }