diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/LoggingExtensions.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/LoggingExtensions.cs index 7f2519f5fd..aa6d0cbaa7 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/LoggingExtensions.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/LoggingExtensions.cs @@ -37,7 +37,8 @@ namespace Microsoft.Extensions.Logging private static Action _invalidLogoutQueryStringRedirectUrl; private static Action _nullOrEmptyAuthorizationResponseState; private static Action _unableToReadAuthorizationResponseState; - private static Action _authorizationResponseError; + private static Action _responseError; + private static Action _responseErrorWithStatusCode; private static Action _exceptionProcessingMessage; private static Action _accessTokenNotAvailable; private static Action _retrievingClaims; @@ -106,10 +107,14 @@ namespace Microsoft.Extensions.Logging eventId: 11, logLevel: LogLevel.Debug, formatString: "Unable to read the message.State."); - _authorizationResponseError = LoggerMessage.Define( + _responseError = LoggerMessage.Define( eventId: 12, logLevel: LogLevel.Error, formatString: "Message contains error: '{Error}', error_description: '{ErrorDescription}', error_uri: '{ErrorUri}'."); + _responseErrorWithStatusCode = LoggerMessage.Define( + eventId: 49, + logLevel: LogLevel.Error, + formatString: "Message contains error: '{Error}', error_description: '{ErrorDescription}', error_uri: '{ErrorUri}', status code '{StatusCode}'."); _updatingConfiguration = LoggerMessage.Define( eventId: 13, logLevel: LogLevel.Debug, @@ -380,9 +385,14 @@ namespace Microsoft.Extensions.Logging _unableToReadAuthorizationResponseState(logger, null); } - public static void AuthorizationResponseError(this ILogger logger, string error, string errorDescription, string errorUri) + public static void ResponseError(this ILogger logger, string error, string errorDescription, string errorUri) { - _authorizationResponseError(logger, error, errorDescription, errorUri, null); + _responseError(logger, error, errorDescription, errorUri, null); + } + + public static void ResponseErrorWithStatusCode(this ILogger logger, string error, string errorDescription, string errorUri, int statusCode) + { + _responseErrorWithStatusCode(logger, error, errorDescription, errorUri, statusCode, null); } public static void ExceptionProcessingMessage(this ILogger logger, Exception ex) diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs index e5dcbbdeb7..2039a76e90 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs @@ -507,14 +507,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect // if any of the error fields are set, throw error null if (!string.IsNullOrEmpty(authorizationResponse.Error)) { - Logger.AuthorizationResponseError( - authorizationResponse.Error, - authorizationResponse.ErrorDescription ?? "ErrorDecription null", - authorizationResponse.ErrorUri ?? "ErrorUri null"); - - return AuthenticateResult.Fail(new OpenIdConnectProtocolException( - string.Format(CultureInfo.InvariantCulture, Resources.MessageContainsError, authorizationResponse.Error, - authorizationResponse.ErrorDescription ?? "ErrorDecription null", authorizationResponse.ErrorUri ?? "ErrorUri null"))); + return AuthenticateResult.Fail(CreateOpenIdConnectProtocolException(authorizationResponse, response: null)); } if (_configuration == null && Options.ConfigurationManager != null) @@ -590,6 +583,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect { return result; } + authorizationResponse = tokenResponseReceivedContext.ProtocolMessage; tokenEndpointResponse = tokenResponseReceivedContext.TokenEndpointResponse; @@ -684,20 +678,50 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect } /// - /// Redeems the authorization code for tokens at the token endpoint + /// Redeems the authorization code for tokens at the token endpoint. /// /// The request that will be sent to the token endpoint and is available for customization. /// OpenIdConnect message that has tokens inside it. protected virtual async Task RedeemAuthorizationCodeAsync(OpenIdConnectMessage tokenEndpointRequest) { Logger.RedeemingCodeForTokens(); + var requestMessage = new HttpRequestMessage(HttpMethod.Post, _configuration.TokenEndpoint); requestMessage.Content = new FormUrlEncodedContent(tokenEndpointRequest.Parameters); + var responseMessage = await Backchannel.SendAsync(requestMessage); - responseMessage.EnsureSuccessStatusCode(); - var tokenResonse = await responseMessage.Content.ReadAsStringAsync(); - var jsonTokenResponse = JObject.Parse(tokenResonse); - return new OpenIdConnectMessage(jsonTokenResponse); + + var contentMediaType = responseMessage.Content.Headers.ContentType?.MediaType; + if (string.IsNullOrEmpty(contentMediaType)) + { + Logger.LogDebug($"Unexpected token response format. Status Code: {(int)responseMessage.StatusCode}. Content-Type header is missing."); + } + else if (!string.Equals(contentMediaType, "application/json", StringComparison.OrdinalIgnoreCase)) + { + Logger.LogDebug($"Unexpected token response format. Status Code: {(int)responseMessage.StatusCode}. Content-Type {responseMessage.Content.Headers.ContentType}."); + } + + // Error handling: + // 1. If the response body can't be parsed as json, throws. + // 2. If the response's status code is not in 2XX range, throw OpenIdConnectProtocolException. If the body is correct parsed, + // pass the error information from body to the exception. + OpenIdConnectMessage message; + try + { + var responseContent = await responseMessage.Content.ReadAsStringAsync(); + message = new OpenIdConnectMessage(responseContent); + } + catch (Exception ex) + { + throw new OpenIdConnectProtocolException($"Failed to parse token response body as JSON. Status Code: {(int)responseMessage.StatusCode}. Content-Type: {responseMessage.Content.Headers.ContentType}", ex); + } + + if (!responseMessage.IsSuccessStatusCode) + { + throw CreateOpenIdConnectProtocolException(message, responseMessage); + } + + return message; } /// @@ -1016,7 +1040,10 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect return authorizationCodeReceivedContext; } - private async Task RunTokenResponseReceivedEventAsync(OpenIdConnectMessage message, OpenIdConnectMessage tokenEndpointResponse, AuthenticationProperties properties) + private async Task RunTokenResponseReceivedEventAsync( + OpenIdConnectMessage message, + OpenIdConnectMessage tokenEndpointResponse, + AuthenticationProperties properties) { Logger.TokenResponseReceived(); var eventContext = new TokenResponseReceivedContext(Context, Options, properties) @@ -1157,5 +1184,27 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect return BuildRedirectUri(uri); } + + private OpenIdConnectProtocolException CreateOpenIdConnectProtocolException(OpenIdConnectMessage message, HttpResponseMessage response) + { + var description = message.ErrorDescription ?? "error_description is null"; + var errorUri = message.ErrorUri ?? "error_uri is null"; + + if (response != null) + { + Logger.ResponseErrorWithStatusCode(message.Error, description, errorUri, (int)response.StatusCode); + } + else + { + Logger.ResponseError(message.Error, description, errorUri); + } + + return new OpenIdConnectProtocolException(string.Format( + CultureInfo.InvariantCulture, + Resources.MessageContainsError, + message.Error, + description, + errorUri)); + } } }