From bd0c266b0d6f42b84dab3cfb9fb1bc830097658b Mon Sep 17 00:00:00 2001 From: pmaytak <34331512+pmaytak@users.noreply.github.com> Date: Thu, 6 Aug 2020 12:23:57 -0700 Subject: [PATCH] Add optional HttpResponse parameter to ReplyForbiddenWithWwwAuthenticateHeaderAsync. Handle null context. (#412) --- .../Constants/IDWebErrorMessage.cs | 3 ++- .../ITokenAcquisition.cs | 6 +++-- .../Microsoft.Identity.Web.xml | 14 +++++++++--- .../TokenAcquisition.cs | 22 +++++++++++-------- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.Identity.Web/Constants/IDWebErrorMessage.cs b/src/Microsoft.Identity.Web/Constants/IDWebErrorMessage.cs index 85874b392..a11689b57 100644 --- a/src/Microsoft.Identity.Web/Constants/IDWebErrorMessage.cs +++ b/src/Microsoft.Identity.Web/Constants/IDWebErrorMessage.cs @@ -9,7 +9,8 @@ namespace Microsoft.Identity.Web internal static class IDWebErrorMessage { // General IDW10000 = "IDW10000:" - public const string HttpContextIsNull = "IDW10000: HttpContext is null. "; + public const string HttpContextIsNull = "IDW10001: HttpContext is null. "; + public const string HttpContextAndHttpResponseAreNull = "IDW10002: Current HttpContext and HttpResponse argument are null. Pass an HttpResponse argument. "; // Configuration IDW10100 = "IDW10100:" public const string ProvideEitherScopeKeySectionOrScopes = "IDW10101: Either provide the '{0}' or the '{1}' to the 'AuthorizeForScopes'. "; diff --git a/src/Microsoft.Identity.Web/ITokenAcquisition.cs b/src/Microsoft.Identity.Web/ITokenAcquisition.cs index 599f21b9d..f47d11659 100644 --- a/src/Microsoft.Identity.Web/ITokenAcquisition.cs +++ b/src/Microsoft.Identity.Web/ITokenAcquisition.cs @@ -50,10 +50,12 @@ Task GetAccessTokenForUserAsync( /// the client can trigger an interaction with the user so the user can consent to more scopes. /// /// Scopes to consent to. - /// triggering the challenge. + /// triggering the challenge. + /// The to update. /// A representing the asynchronous operation. Task ReplyForbiddenWithWwwAuthenticateHeaderAsync( IEnumerable scopes, - MsalUiRequiredException msalSeviceException); + MsalUiRequiredException msalServiceException, + HttpResponse? httpResponse = null); } } diff --git a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml index b80c2e436..ec818ef3e 100644 --- a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml +++ b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml @@ -669,14 +669,15 @@ in the portal, and cannot be overridden in the application. An access token for the app itself, based on its scopes. - + Used in Web APIs (which therefore cannot have an interaction with the user). Replies to the client through the HttpResponse by sending a 403 (forbidden) and populating wwwAuthenticateHeaders so that the client can trigger an interaction with the user so the user can consent to more scopes. Scopes to consent to. - triggering the challenge. + triggering the challenge. + The to update. A representing the asynchronous operation. @@ -742,6 +743,12 @@ and conditional access. + + + Initializes a new instance of the class. + + Accessor for the current HttpContext, when available. + Boolean to determine if server is Blazor. @@ -1564,7 +1571,7 @@ on behalf of the user. Azure AD B2C user flow. - + Used in web APIs (which therefore cannot have an interaction with the user). Replies to the client through the HTTP response by sending a 403 (forbidden) and populating 'WWW-Authenticate' header so that @@ -1572,6 +1579,7 @@ Scopes to consent to. The that triggered the challenge. + The to update. A representing the asynchronous operation. diff --git a/src/Microsoft.Identity.Web/TokenAcquisition.cs b/src/Microsoft.Identity.Web/TokenAcquisition.cs index 487ce60c5..39bcc2d6a 100644 --- a/src/Microsoft.Identity.Web/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web/TokenAcquisition.cs @@ -533,8 +533,9 @@ private async Task GetAccessTokenForWebAppWithAccountFromCacheAsync( /// /// Scopes to consent to. /// The that triggered the challenge. + /// The to update. /// A representing the asynchronous operation. - public async Task ReplyForbiddenWithWwwAuthenticateHeaderAsync(IEnumerable scopes, MsalUiRequiredException msalServiceException) + public async Task ReplyForbiddenWithWwwAuthenticateHeaderAsync(IEnumerable scopes, MsalUiRequiredException msalServiceException, HttpResponse? httpResponse = null) { // A user interaction is required, but we are in a web API, and therefore, we need to report back to the client through a 'WWW-Authenticate' header https://tools.ietf.org/html/rfc6750#section-3.1 string proposedAction = Constants.Consent; @@ -562,23 +563,26 @@ public async Task ReplyForbiddenWithWwwAuthenticateHeaderAsync(IEnumerable $"{p.Key}=\"{p.Value}\"")); - if (CurrentHttpContext != null) - { - var httpResponse = CurrentHttpContext.Response; - var headers = httpResponse.Headers; - httpResponse.StatusCode = (int)HttpStatusCode.Forbidden; + httpResponse ??= CurrentHttpContext?.Response; - headers[HeaderNames.WWWAuthenticate] = new StringValues($"{Constants.Bearer} {parameterString}"); + if (httpResponse == null) + { + throw new InvalidOperationException(IDWebErrorMessage.HttpContextAndHttpResponseAreNull); } + + var headers = httpResponse.Headers; + httpResponse.StatusCode = (int)HttpStatusCode.Forbidden; + + headers[HeaderNames.WWWAuthenticate] = new StringValues($"{Constants.Bearer} {parameterString}"); } - private static bool AcceptedTokenVersionMismatch(MsalUiRequiredException msalSeviceException) + private static bool AcceptedTokenVersionMismatch(MsalUiRequiredException msalServiceException) { // Normally app developers should not make decisions based on the internal AAD code // however until the STS sends sub-error codes for this error, this is the only // way to distinguish the case. // This is subject to change in the future - return msalSeviceException.Message.Contains(ErrorCodes.B2CPasswordResetErrorCode, StringComparison.InvariantCulture); + return msalServiceException.Message.Contains(ErrorCodes.B2CPasswordResetErrorCode, StringComparison.InvariantCulture); } private async Task GetAuthenticatedUserAsync(ClaimsPrincipal? user)