diff --git a/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationTokenAcquisition.cs b/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationTokenAcquisition.cs
index 807dbc499..8479096f6 100644
--- a/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationTokenAcquisition.cs
+++ b/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationTokenAcquisition.cs
@@ -18,7 +18,12 @@ namespace Microsoft.Identity.Web
///
public class AppServicesAuthenticationTokenAcquisition : ITokenAcquisition
{
- private IConfidentialClientApplication? _confidentialClientApplication;
+ private readonly object _applicationSyncObj = new object();
+
+ ///
+ /// Please call GetOrCreateApplication instead of accessing this field directly.
+ ///
+ private IConfidentialClientApplication? _application;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IMsalHttpClientFactory _httpClientFactory;
private readonly IMsalTokenCacheProvider _tokenCacheProvider;
@@ -70,22 +75,28 @@ public AppServicesAuthenticationTokenAcquisition(
private IConfidentialClientApplication GetOrCreateApplication()
{
- if (_confidentialClientApplication == null)
+ if (_application == null)
{
- ConfidentialClientApplicationOptions options = new ConfidentialClientApplicationOptions()
+ lock (_applicationSyncObj)
{
- ClientId = AppServicesAuthenticationInformation.ClientId,
- ClientSecret = AppServicesAuthenticationInformation.ClientSecret,
- Instance = AppServicesAuthenticationInformation.Issuer,
- };
- _confidentialClientApplication = ConfidentialClientApplicationBuilder.CreateWithApplicationOptions(options)
- .WithHttpClientFactory(_httpClientFactory)
- .Build();
- _tokenCacheProvider.Initialize(_confidentialClientApplication.AppTokenCache);
- _tokenCacheProvider.Initialize(_confidentialClientApplication.UserTokenCache);
+ if (_application == null)
+ {
+ var options = new ConfidentialClientApplicationOptions()
+ {
+ ClientId = AppServicesAuthenticationInformation.ClientId,
+ ClientSecret = AppServicesAuthenticationInformation.ClientSecret,
+ Instance = AppServicesAuthenticationInformation.Issuer,
+ };
+ _application = ConfidentialClientApplicationBuilder.CreateWithApplicationOptions(options)
+ .WithHttpClientFactory(_httpClientFactory)
+ .Build();
+ _tokenCacheProvider.Initialize(_application.AppTokenCache);
+ _tokenCacheProvider.Initialize(_application.UserTokenCache);
+ }
+ }
}
- return _confidentialClientApplication;
+ return _application;
}
///
@@ -109,16 +120,29 @@ public async Task GetAccessTokenForAppAsync(
}
///
- public async Task GetAccessTokenForUserAsync(
+ public Task GetAccessTokenForUserAsync(
IEnumerable scopes,
string? tenantId = null,
string? userFlow = null,
ClaimsPrincipal? user = null,
TokenAcquisitionOptions? tokenAcquisitionOptions = null)
{
- string accessToken = GetAccessToken(CurrentHttpContext?.Request.Headers);
+ var httpContext = CurrentHttpContext;
+ string accessToken;
+ if (httpContext != null)
+ {
+ // Need to lock due to https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?#do-not-access-httpcontext-from-multiple-threads
+ lock (httpContext)
+ {
+ accessToken = GetAccessToken(httpContext.Request.Headers);
+ }
+ }
+ else
+ {
+ accessToken = string.Empty;
+ }
- return await Task.FromResult(accessToken).ConfigureAwait(false);
+ return Task.FromResult(accessToken);
}
private string GetAccessToken(IHeaderDictionary? headers)
@@ -145,7 +169,6 @@ private string GetAccessToken(IHeaderDictionary? headers)
}
///
-#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
public async Task GetAuthenticationResultForUserAsync(
IEnumerable scopes,
string? tenantId = null,
@@ -209,6 +232,5 @@ public Task GetAuthenticationResultForAppAsync(string scop
{
throw new NotImplementedException();
}
-#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
}
}
diff --git a/src/Microsoft.Identity.Web/AuthorizeForScopesAttribute.cs b/src/Microsoft.Identity.Web/AuthorizeForScopesAttribute.cs
index eaf5234ca..78f96abc6 100644
--- a/src/Microsoft.Identity.Web/AuthorizeForScopesAttribute.cs
+++ b/src/Microsoft.Identity.Web/AuthorizeForScopesAttribute.cs
@@ -3,6 +3,7 @@
using System;
using System.Globalization;
+using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -101,17 +102,27 @@ public override void OnException(ExceptionContext context)
incrementalConsentScopes = Scopes;
}
+ HttpRequest httpRequest;
+ ClaimsPrincipal user;
+ HttpContext httpContext = context.HttpContext;
+
+ lock (httpContext)
+ {
+ httpRequest = httpContext.Request;
+ user = httpContext.User;
+ }
+
AuthenticationProperties properties = IncrementalConsentAndConditionalAccessHelper.BuildAuthenticationProperties(
incrementalConsentScopes,
msalUiRequiredException,
- context.HttpContext.User,
+ user,
UserFlow);
- if (IsAjaxRequest(context.HttpContext.Request) && (!string.IsNullOrEmpty(context.HttpContext.Request.Headers[Constants.XReturnUrl])
- || !string.IsNullOrEmpty(context.HttpContext.Request.Query[Constants.XReturnUrl])))
+ if (IsAjaxRequest(httpRequest) && (!string.IsNullOrEmpty(httpRequest.Headers[Constants.XReturnUrl])
+ || !string.IsNullOrEmpty(httpRequest.Query[Constants.XReturnUrl])))
{
- string redirectUri = !string.IsNullOrEmpty(context.HttpContext.Request.Headers[Constants.XReturnUrl]) ? context.HttpContext.Request.Headers[Constants.XReturnUrl]
- : context.HttpContext.Request.Query[Constants.XReturnUrl];
+ string redirectUri = !string.IsNullOrEmpty(httpRequest.Headers[Constants.XReturnUrl]) ? httpRequest.Headers[Constants.XReturnUrl]
+ : httpRequest.Query[Constants.XReturnUrl];
UrlHelper urlHelper = new UrlHelper(context);
if (urlHelper.IsLocalUrl(redirectUri))
diff --git a/src/Microsoft.Identity.Web/HttpContextExtensions.cs b/src/Microsoft.Identity.Web/HttpContextExtensions.cs
index cc7a5c220..fb23b3baa 100644
--- a/src/Microsoft.Identity.Web/HttpContextExtensions.cs
+++ b/src/Microsoft.Identity.Web/HttpContextExtensions.cs
@@ -16,7 +16,11 @@ internal static class HttpContextExtensions
/// it can be used in the actions.
internal static void StoreTokenUsedToCallWebAPI(this HttpContext httpContext, JwtSecurityToken? token)
{
- httpContext.Items.Add(Constants.JwtSecurityTokenUsedToCallWebApi, token);
+ // lock due to https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?#do-not-access-httpcontext-from-multiple-threads
+ lock (httpContext)
+ {
+ httpContext.Items.Add(Constants.JwtSecurityTokenUsedToCallWebApi, token);
+ }
}
///
@@ -26,7 +30,11 @@ internal static void StoreTokenUsedToCallWebAPI(this HttpContext httpContext, Jw
/// used to call the web API.
internal static JwtSecurityToken? GetTokenUsedToCallWebAPI(this HttpContext httpContext)
{
- return httpContext.Items[Constants.JwtSecurityTokenUsedToCallWebApi] as JwtSecurityToken;
+ // lock due to https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?#do-not-access-httpcontext-from-multiple-threads
+ lock (httpContext)
+ {
+ return httpContext.Items[Constants.JwtSecurityTokenUsedToCallWebApi] as JwtSecurityToken;
+ }
}
}
}
diff --git a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml
index c5d466661..58d0bcbe8 100644
--- a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml
+++ b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml
@@ -123,6 +123,11 @@
Implementation of ITokenAcquisition for App Services authentication (EasyAuth).
+
+
+ Please call GetOrCreateApplication instead of accessing this field directly.
+
+
Constructor of the AppServicesAuthenticationTokenAcquisition.
@@ -1989,6 +1994,11 @@
Token acquisition service.
+
+
+ Please call GetOrBuildConfidentialClientApplication instead of accessing this field directly.
+
+
Constructor of the TokenAcquisition service. This requires the Azure AD Options to
@@ -2151,11 +2161,6 @@
OpenID Connect event.
A that represents a completed account removal operation.
-
-
- Creates an MSAL confidential client application, if needed.
-
-
Creates an MSAL confidential client application.
diff --git a/src/Microsoft.Identity.Web/MicrosoftIdentityConsentAndConditionalAccessHandler.cs b/src/Microsoft.Identity.Web/MicrosoftIdentityConsentAndConditionalAccessHandler.cs
index c6dee66a4..7623d7052 100644
--- a/src/Microsoft.Identity.Web/MicrosoftIdentityConsentAndConditionalAccessHandler.cs
+++ b/src/Microsoft.Identity.Web/MicrosoftIdentityConsentAndConditionalAccessHandler.cs
@@ -43,11 +43,21 @@ public ClaimsPrincipal User
{
get
{
- return _user ??
-#pragma warning disable CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case.
- (!IsBlazorServer ? _httpContextAccessor.HttpContext.User :
-#pragma warning restore CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case.
- throw new InvalidOperationException(IDWebErrorMessage.BlazorServerUserNotSet));
+ if (_user != null)
+ {
+ return _user;
+ }
+
+ HttpContext httpContext = _httpContextAccessor!.HttpContext!;
+ ClaimsPrincipal user;
+
+ lock (httpContext)
+ {
+ user = httpContext.User;
+ }
+
+ return !IsBlazorServer ? user :
+ throw new InvalidOperationException(IDWebErrorMessage.BlazorServerUserNotSet);
}
set
{
@@ -62,11 +72,21 @@ public string? BaseUri
{
get
{
- return _baseUri ??
-#pragma warning disable CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case
- (!IsBlazorServer ? CreateBaseUri(_httpContextAccessor.HttpContext.Request) :
-#pragma warning restore CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case
- throw new InvalidOperationException(IDWebErrorMessage.BlazorServerBaseUriNotSet));
+ if (_baseUri != null)
+ {
+ return _baseUri;
+ }
+
+ HttpRequest httpRequest;
+ HttpContext httpContext = _httpContextAccessor!.HttpContext!;
+
+ lock (httpContext)
+ {
+ httpRequest = httpContext.Request;
+ }
+
+ return !IsBlazorServer ? CreateBaseUri(httpRequest) :
+ throw new InvalidOperationException(IDWebErrorMessage.BlazorServerBaseUriNotSet);
}
set
{
@@ -160,14 +180,19 @@ public void ChallengeUser(
}
else
{
-#pragma warning disable CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case.
- var request = _httpContextAccessor.HttpContext.Request;
-#pragma warning restore CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case.
+ HttpRequest httpRequest;
+ HttpContext httpContext = _httpContextAccessor!.HttpContext!;
+
+ lock (httpContext)
+ {
+ httpRequest = httpContext.Request;
+ }
+
redirectUri = string.Format(
CultureInfo.InvariantCulture,
"{0}/{1}",
- CreateBaseUri(request),
- request.Path.ToString().TrimStart('/'));
+ CreateBaseUri(httpRequest),
+ httpRequest.Path.ToString().TrimStart('/'));
}
string url = $"{BaseUri}/{Constants.BlazorChallengeUri}{redirectUri}"
@@ -181,9 +206,12 @@ public void ChallengeUser(
}
else
{
-#pragma warning disable CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case.
- _httpContextAccessor.HttpContext.Response.Redirect(url);
-#pragma warning restore CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case.
+ HttpContext httpContext = _httpContextAccessor!.HttpContext!;
+
+ lock (httpContext)
+ {
+ httpContext.Response.Redirect(url);
+ }
}
}
diff --git a/src/Microsoft.Identity.Web/Resource/RequiredScopeFilter.cs b/src/Microsoft.Identity.Web/Resource/RequiredScopeFilter.cs
index 61af440fe..5a2a4e470 100644
--- a/src/Microsoft.Identity.Web/Resource/RequiredScopeFilter.cs
+++ b/src/Microsoft.Identity.Web/Resource/RequiredScopeFilter.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
+using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
@@ -55,31 +56,51 @@ private void ValidateEffectiveScopes(AuthorizationFilterContext context)
throw new InvalidOperationException(IDWebErrorMessage.MissingRequiredScopesForAuthorizationFilter);
}
- if (context.HttpContext.User == null || context.HttpContext.User.Claims == null || !context.HttpContext.User.Claims.Any())
+ IEnumerable userClaims;
+ ClaimsPrincipal user;
+ HttpContext httpContext = context.HttpContext;
+
+ // Need to lock due to https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?#do-not-access-httpcontext-from-multiple-threads
+ lock (httpContext)
+ {
+ user = httpContext.User;
+ userClaims = user.Claims;
+ }
+
+ if (user == null || userClaims == null || !userClaims.Any())
{
- context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
+ lock (httpContext)
+ {
+ httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
+ }
+
throw new UnauthorizedAccessException(IDWebErrorMessage.UnauthenticatedUser);
}
else
{
// Attempt with Scp claim
- Claim? scopeClaim = context.HttpContext.User.FindFirst(ClaimConstants.Scp);
+ Claim? scopeClaim = user.FindFirst(ClaimConstants.Scp);
// Fallback to Scope claim name
if (scopeClaim == null)
{
- scopeClaim = context.HttpContext.User.FindFirst(ClaimConstants.Scope);
+ scopeClaim = user.FindFirst(ClaimConstants.Scope);
}
if (scopeClaim == null || !scopeClaim.Value.Split(' ').Intersect(_effectiveAcceptedScopes).Any())
{
- context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
string message = string.Format(
CultureInfo.InvariantCulture,
IDWebErrorMessage.MissingScopes,
string.Join(",", _effectiveAcceptedScopes));
- context.HttpContext.Response.WriteAsync(message);
- context.HttpContext.Response.CompleteAsync();
+
+ lock (httpContext)
+ {
+ httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
+ httpContext.Response.WriteAsync(message);
+ httpContext.Response.CompleteAsync();
+ }
+
throw new UnauthorizedAccessException(message);
}
}
diff --git a/src/Microsoft.Identity.Web/Resource/RolesRequiredHttpContextExtensions.cs b/src/Microsoft.Identity.Web/Resource/RolesRequiredHttpContextExtensions.cs
index c1ab2bb0c..6aaec4c59 100644
--- a/src/Microsoft.Identity.Web/Resource/RolesRequiredHttpContextExtensions.cs
+++ b/src/Microsoft.Identity.Web/Resource/RolesRequiredHttpContextExtensions.cs
@@ -7,6 +7,7 @@
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Security.Claims;
using Microsoft.AspNetCore.Http;
namespace Microsoft.Identity.Web.Resource
@@ -36,24 +37,44 @@ public static void ValidateAppRole(this HttpContext context, params string[] acc
{
throw new ArgumentNullException(nameof(context));
}
- else if (context.User == null || context.User.Claims == null || !context.User.Claims.Any())
+
+ IEnumerable userClaims;
+ ClaimsPrincipal user;
+
+ // Need to lock due to https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?#do-not-access-httpcontext-from-multiple-threads
+ lock (context)
+ {
+ user = context.User;
+ userClaims = user.Claims;
+ }
+
+ if (user == null || userClaims == null || !userClaims.Any())
{
- context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
+ lock (context)
+ {
+ context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
+ }
+
throw new UnauthorizedAccessException(IDWebErrorMessage.UnauthenticatedUser);
}
else
{
// Attempt with Roles claim
- IEnumerable rolesClaim = context.User.Claims.Where(
+ IEnumerable rolesClaim = userClaims.Where(
c => c.Type == ClaimConstants.Roles || c.Type == ClaimConstants.Role)
.SelectMany(c => c.Value.Split(' '));
if (!rolesClaim.Intersect(acceptedRoles).Any())
{
- context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
string message = string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.MissingRoles, string.Join(", ", acceptedRoles));
- context.Response.WriteAsync(message);
- context.Response.CompleteAsync();
+
+ lock (context)
+ {
+ context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
+ context.Response.WriteAsync(message);
+ context.Response.CompleteAsync();
+ }
+
throw new UnauthorizedAccessException(message);
}
}
diff --git a/src/Microsoft.Identity.Web/Resource/ScopesRequiredHttpContextExtensions.cs b/src/Microsoft.Identity.Web/Resource/ScopesRequiredHttpContextExtensions.cs
index 9045b31b2..5d973590b 100644
--- a/src/Microsoft.Identity.Web/Resource/ScopesRequiredHttpContextExtensions.cs
+++ b/src/Microsoft.Identity.Web/Resource/ScopesRequiredHttpContextExtensions.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
+using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
@@ -43,28 +44,48 @@ public static void VerifyUserHasAnyAcceptedScope(this HttpContext context, param
{
throw new ArgumentNullException(nameof(context));
}
- else if (context.User == null || context.User.Claims == null || !context.User.Claims.Any())
+
+ IEnumerable userClaims;
+ ClaimsPrincipal user;
+
+ // Need to lock due to https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?#do-not-access-httpcontext-from-multiple-threads
+ lock (context)
+ {
+ user = context.User;
+ userClaims = user.Claims;
+ }
+
+ if (user == null || userClaims == null || !userClaims.Any())
{
- context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
+ lock (context)
+ {
+ context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
+ }
+
throw new UnauthorizedAccessException(IDWebErrorMessage.UnauthenticatedUser);
}
else
{
// Attempt with Scp claim
- Claim? scopeClaim = context.User.FindFirst(ClaimConstants.Scp);
+ Claim? scopeClaim = user.FindFirst(ClaimConstants.Scp);
// Fallback to Scope claim name
if (scopeClaim == null)
{
- scopeClaim = context.User.FindFirst(ClaimConstants.Scope);
+ scopeClaim = user.FindFirst(ClaimConstants.Scope);
}
if (scopeClaim == null || !scopeClaim.Value.Split(' ').Intersect(acceptedScopes).Any())
{
- context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
string message = string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.MissingScopes, string.Join(",", acceptedScopes));
- context.Response.WriteAsync(message);
- context.Response.CompleteAsync();
+
+ lock (context)
+ {
+ context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
+ context.Response.WriteAsync(message);
+ context.Response.CompleteAsync();
+ }
+
throw new UnauthorizedAccessException(message);
}
}
diff --git a/src/Microsoft.Identity.Web/TokenAcquisition.cs b/src/Microsoft.Identity.Web/TokenAcquisition.cs
index 5cd0f5b00..0920ada52 100644
--- a/src/Microsoft.Identity.Web/TokenAcquisition.cs
+++ b/src/Microsoft.Identity.Web/TokenAcquisition.cs
@@ -33,6 +33,11 @@ internal class TokenAcquisition : ITokenAcquisitionInternal
private readonly ConfidentialClientApplicationOptions _applicationOptions;
private readonly IMsalTokenCacheProvider _tokenCacheProvider;
+ private readonly object _applicationSyncObj = new object();
+
+ ///
+ /// Please call GetOrBuildConfidentialClientApplication instead of accessing this field directly.
+ ///
private IConfidentialClientApplication? _application;
private readonly IHttpContextAccessor _httpContextAccessor;
private HttpContext? CurrentHttpContext => _httpContextAccessor.HttpContext;
@@ -144,12 +149,12 @@ public async Task AddAccountToCacheFromAuthorizationCodeAsync(
try
{
- _application = GetOrBuildConfidentialClientApplication();
+ var application = GetOrBuildConfidentialClientApplication();
// Do not share the access token with ASP.NET Core otherwise ASP.NET will cache it and will not send the OAuth 2.0 request in
// case a further call to AcquireTokenByAuthorizationCodeAsync in the future is required for incremental consent (getting a code requesting more scopes)
// Share the ID token though
- var builder = _application
+ var builder = application
.AcquireTokenByAuthorizationCode(scopes.Except(_scopesRequestedByMsal), context.ProtocolMessage.Code)
.WithSendX5C(_microsoftIdentityOptions.SendX5C);
@@ -210,17 +215,15 @@ public async Task GetAuthenticationResultForUserAsync(
user = await GetAuthenticatedUserAsync(user).ConfigureAwait(false);
- _application = GetOrBuildConfidentialClientApplication();
+ var application = GetOrBuildConfidentialClientApplication();
- string authority = CreateAuthorityBasedOnTenantIfProvided(_application, tenantId);
-
- AuthenticationResult? authenticationResult;
+ string authority = CreateAuthorityBasedOnTenantIfProvided(application, tenantId);
try
{
// Access token will return if call is from a web API
- authenticationResult = await GetAuthenticationResultForWebApiToCallDownstreamApiAsync(
- _application,
+ var authenticationResult = await GetAuthenticationResultForWebApiToCallDownstreamApiAsync(
+ application,
authority,
scopes,
tokenAcquisitionOptions).ConfigureAwait(false);
@@ -232,7 +235,7 @@ public async Task GetAuthenticationResultForUserAsync(
// If access token is null, this is a web app
return await GetAuthenticationResultForWebAppWithAccountFromCacheAsync(
- _application,
+ application,
user,
scopes,
authority,
@@ -289,10 +292,10 @@ public Task GetAuthenticationResultForAppAsync(
}
// Use MSAL to get the right token to call the API
- _application = GetOrBuildConfidentialClientApplication();
- string authority = CreateAuthorityBasedOnTenantIfProvided(_application, tenant);
+ var application = GetOrBuildConfidentialClientApplication();
+ string authority = CreateAuthorityBasedOnTenantIfProvided(application, tenant);
- var builder = _application
+ var builder = application
.AcquireTokenForClient(new string[] { scope }.Except(_scopesRequestedByMsal))
.WithSendX5C(_microsoftIdentityOptions.SendX5C)
.WithAuthority(authority);
@@ -381,14 +384,13 @@ await GetAuthenticationResultForUserAsync(
/// Scopes to consent to.
/// The that triggered the challenge.
/// The to update.
-#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
- public async Task ReplyForbiddenWithWwwAuthenticateHeaderAsync(
+ public Task ReplyForbiddenWithWwwAuthenticateHeaderAsync(
IEnumerable scopes,
MsalUiRequiredException msalServiceException,
HttpResponse? httpResponse = null)
-#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{
ReplyForbiddenWithWwwAuthenticateHeader(scopes, msalServiceException, httpResponse);
+ return Task.CompletedTask;
}
///
@@ -411,10 +413,10 @@ public void ReplyForbiddenWithWwwAuthenticateHeader(
throw msalServiceException;
}
- _application = GetOrBuildConfidentialClientApplication();
+ var application = GetOrBuildConfidentialClientApplication();
- string consentUrl = $"{_application.Authority}/oauth2/v2.0/authorize?client_id={_applicationOptions.ClientId}"
- + $"&response_type=code&redirect_uri={_application.AppConfig.RedirectUri}"
+ string consentUrl = $"{application.Authority}/oauth2/v2.0/authorize?client_id={_applicationOptions.ClientId}"
+ + $"&response_type=code&redirect_uri={application.AppConfig.RedirectUri}"
+ $"&response_mode=query&scope=offline_access%20{string.Join("%20", scopes)}";
IDictionary parameters = new Dictionary()
@@ -472,14 +474,31 @@ public async Task RemoveAccountAsync(RedirectContext context)
}
}
- ///
- /// Creates an MSAL confidential client application, if needed.
- ///
+ private string BuildCurrentUriFromRequest(HttpContext httpContext, HttpRequest request)
+ {
+ // need to lock to avoid threading issues with code outside of this library
+ // https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?#do-not-access-httpcontext-from-multiple-threads
+ lock (httpContext)
+ {
+ return UriHelper.BuildAbsolute(
+ request.Scheme,
+ request.Host,
+ request.PathBase,
+ _microsoftIdentityOptions.CallbackPath.Value ?? string.Empty);
+ }
+ }
+
internal /* for testing */ IConfidentialClientApplication GetOrBuildConfidentialClientApplication()
{
if (_application == null)
{
- return BuildConfidentialClientApplication();
+ lock (_applicationSyncObj)
+ {
+ if (_application == null)
+ {
+ _application = BuildConfidentialClientApplication();
+ }
+ }
}
return _application;
@@ -490,7 +509,8 @@ public async Task RemoveAccountAsync(RedirectContext context)
///
private IConfidentialClientApplication BuildConfidentialClientApplication()
{
- var request = CurrentHttpContext?.Request;
+ var httpContext = CurrentHttpContext;
+ var request = httpContext?.Request;
string? currentUri = null;
if (!string.IsNullOrEmpty(_applicationOptions.RedirectUri))
@@ -500,11 +520,7 @@ private IConfidentialClientApplication BuildConfidentialClientApplication()
if (request != null && string.IsNullOrEmpty(currentUri))
{
- currentUri = UriHelper.BuildAbsolute(
- request.Scheme,
- request.Host,
- request.PathBase,
- _microsoftIdentityOptions.CallbackPath.Value ?? string.Empty);
+ currentUri = BuildCurrentUriFromRequest(httpContext!, request);
}
PrepareAuthorityInstanceForMsal();
@@ -595,11 +611,11 @@ private void PrepareAuthorityInstanceForMsal()
string tokenUsedToCallTheWebApi = validatedToken.InnerToken == null ? validatedToken.RawData
: validatedToken.InnerToken.RawData;
var builder = application
- .AcquireTokenOnBehalfOf(
- scopes.Except(_scopesRequestedByMsal),
- new UserAssertion(tokenUsedToCallTheWebApi))
- .WithSendX5C(_microsoftIdentityOptions.SendX5C)
- .WithAuthority(authority);
+ .AcquireTokenOnBehalfOf(
+ scopes.Except(_scopesRequestedByMsal),
+ new UserAssertion(tokenUsedToCallTheWebApi))
+ .WithSendX5C(_microsoftIdentityOptions.SendX5C)
+ .WithAuthority(authority);
if (tokenAcquisitionOptions != null)
{
@@ -686,7 +702,7 @@ private async Task GetAuthenticationResultForWebAppWithAcc
/// on behalf of the user.
/// Azure AD B2C user flow.
/// Options passed-in to create the token acquisition object which calls into MSAL .NET.
- private async Task GetAuthenticationResultForWebAppWithAccountFromCacheAsync(
+ private Task GetAuthenticationResultForWebAppWithAccountFromCacheAsync(
IConfidentialClientApplication application,
IAccount? account,
IEnumerable scopes,
@@ -730,8 +746,7 @@ private async Task GetAuthenticationResultForWebAppWithAcc
builder.WithAuthority(authority);
}
- return await builder.ExecuteAsync()
- .ConfigureAwait(false);
+ return builder.ExecuteAsync();
}
private static bool AcceptedTokenVersionMismatch(MsalUiRequiredException msalServiceException)
@@ -745,11 +760,26 @@ private static bool AcceptedTokenVersionMismatch(MsalUiRequiredException msalSer
StringComparison.InvariantCulture);
}
+ private ClaimsPrincipal? GetUserFromHttpContext()
+ {
+ var httpContext = CurrentHttpContext;
+ if (httpContext != null)
+ {
+ // Need to lock due to https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?#do-not-access-httpcontext-from-multiple-threads
+ lock (httpContext)
+ {
+ return httpContext.User;
+ }
+ }
+
+ return null;
+ }
+
private async Task GetAuthenticatedUserAsync(ClaimsPrincipal? user)
{
- if (user == null && _httpContextAccessor.HttpContext?.User != null)
+ if (user == null)
{
- user = _httpContextAccessor.HttpContext.User;
+ user = GetUserFromHttpContext();
}
if (user == null)