private async Task <bool> InvokeUserinfoEndpointAsync()
        {
            OpenIdConnectRequest request;

            if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
            {
                request = new OpenIdConnectRequest(Request.Query);
            }

            else if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase))
            {
                // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization
                if (string.IsNullOrWhiteSpace(Request.ContentType))
                {
                    Logger.LogError("The userinfo request was rejected because " +
                                    "the mandatory 'Content-Type' header was missing.");

                    return(await SendUserinfoResponseAsync(new OpenIdConnectResponse {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "A malformed userinfo request has been received: " +
                                           "the mandatory 'Content-Type' header was missing from the POST request."
                    }));
                }

                // May have media/type; charset=utf-8, allow partial match.
                if (!Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase))
                {
                    Logger.LogError("The userinfo request was rejected because an invalid 'Content-Type' " +
                                    "header was received: {ContentType}.", Request.ContentType);

                    return(await SendUserinfoResponseAsync(new OpenIdConnectResponse {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "A malformed userinfo request has been received: " +
                                           "the 'Content-Type' header contained an unexcepted value. " +
                                           "Make sure to use 'application/x-www-form-urlencoded'."
                    }));
                }

                request = new OpenIdConnectRequest(await Request.ReadFormAsync(Context.RequestAborted));
            }

            else
            {
                Logger.LogError("The userinfo request was rejected because an invalid " +
                                "HTTP method was received: {Method}.", Request.Method);

                return(await SendUserinfoResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "A malformed userinfo request has been received: " +
                                       "make sure to use either GET or POST."
                }));
            }

            // Note: set the message type before invoking the ExtractUserinfoRequest event.
            request.SetProperty(OpenIdConnectConstants.Properties.MessageType,
                                OpenIdConnectConstants.MessageTypes.Userinfo);

            // Insert the userinfo request in the ASP.NET context.
            Context.SetOpenIdConnectRequest(request);

            var @event = new ExtractUserinfoRequestContext(Context, Options, request);
            await Options.Provider.ExtractUserinfoRequest(@event);

            if (@event.HandledResponse)
            {
                return(true);
            }

            else if (@event.Skipped)
            {
                return(false);
            }

            else if (@event.IsRejected)
            {
                Logger.LogError("The userinfo request was rejected with the following error: {Error} ; {Description}",
                                /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                /* Description: */ @event.ErrorDescription);

                return(await SendUserinfoResponseAsync(new OpenIdConnectResponse {
                    Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = @event.ErrorDescription,
                    ErrorUri = @event.ErrorUri
                }));
            }

            string token;

            if (!string.IsNullOrEmpty(request.AccessToken))
            {
                token = request.AccessToken;
            }

            else
            {
                string header = Request.Headers[HeaderNames.Authorization];
                if (string.IsNullOrEmpty(header))
                {
                    Logger.LogError("The userinfo request was rejected because " +
                                    "the 'Authorization' header was missing.");

                    return(await SendUserinfoResponseAsync(new OpenIdConnectResponse {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "A malformed userinfo request has been received."
                    }));
                }

                if (!header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
                {
                    Logger.LogError("The userinfo request was rejected because the " +
                                    "'Authorization' header was invalid: {Header}.", header);

                    return(await SendUserinfoResponseAsync(new OpenIdConnectResponse {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "A malformed userinfo request has been received."
                    }));
                }

                token = header.Substring("Bearer ".Length);
                if (string.IsNullOrEmpty(token))
                {
                    Logger.LogError("The userinfo request was rejected because the access token was missing.");

                    return(await SendUserinfoResponseAsync(new OpenIdConnectResponse {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "A malformed userinfo request has been received."
                    }));
                }
            }

            var context = new ValidateUserinfoRequestContext(Context, Options, request);
            await Options.Provider.ValidateUserinfoRequest(context);

            if (context.HandledResponse)
            {
                return(true);
            }

            else if (context.Skipped)
            {
                return(false);
            }

            else if (!context.IsValidated)
            {
                Logger.LogError("The userinfo request was rejected with the following error: {Error} ; {Description}",
                                /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                /* Description: */ context.ErrorDescription);

                return(await SendUserinfoResponseAsync(new OpenIdConnectResponse {
                    Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = context.ErrorDescription,
                    ErrorUri = context.ErrorUri
                }));
            }

            var ticket = await DeserializeAccessTokenAsync(token, request);

            if (ticket == null)
            {
                Logger.LogError("The userinfo request was rejected because the access token was invalid.");

                // Note: an invalid token should result in an unauthorized response
                // but returning a 401 status would invoke the previously registered
                // authentication middleware and potentially replace it by a 302 response.
                // To work around this limitation, a 400 error is returned instead.
                // See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoError
                return(await SendUserinfoResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidGrant,
                    ErrorDescription = "Invalid token."
                }));
            }

            if (ticket.Properties.ExpiresUtc.HasValue &&
                ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow)
            {
                Logger.LogError("The userinfo request was rejected because the access token was expired.");

                // Note: an invalid token should result in an unauthorized response
                // but returning a 401 status would invoke the previously registered
                // authentication middleware and potentially replace it by a 302 response.
                // To work around this limitation, a 400 error is returned instead.
                // See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoError
                return(await SendUserinfoResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidGrant,
                    ErrorDescription = "Expired token."
                }));
            }

            var notification = new HandleUserinfoRequestContext(Context, Options, request, ticket);

            notification.Subject = ticket.Principal.GetClaim(ClaimTypes.NameIdentifier);
            notification.Issuer  = Context.GetIssuer(Options);

            // Note: when receiving an access token, its audiences list cannot be used for the "aud" claim
            // as the client application is not the intented audience but only an authorized presenter.
            // See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
            foreach (var presenter in ticket.GetPresenters())
            {
                notification.Audiences.Add(presenter);
            }

            // The following claims are all optional and should be excluded when
            // no corresponding value has been found in the authentication ticket.
            if (ticket.HasScope(OpenIdConnectConstants.Scopes.Profile))
            {
                notification.FamilyName = ticket.Principal.GetClaim(ClaimTypes.Surname);
                notification.GivenName  = ticket.Principal.GetClaim(ClaimTypes.GivenName);
                notification.BirthDate  = ticket.Principal.GetClaim(ClaimTypes.DateOfBirth);
            }

            if (ticket.HasScope(OpenIdConnectConstants.Scopes.Email))
            {
                notification.Email = ticket.Principal.GetClaim(ClaimTypes.Email);
            }
            ;

            if (ticket.HasScope(OpenIdConnectConstants.Scopes.Phone))
            {
                notification.PhoneNumber = ticket.Principal.GetClaim(ClaimTypes.HomePhone) ??
                                           ticket.Principal.GetClaim(ClaimTypes.MobilePhone) ??
                                           ticket.Principal.GetClaim(ClaimTypes.OtherPhone);
            }
            ;

            await Options.Provider.HandleUserinfoRequest(notification);

            if (notification.HandledResponse)
            {
                return(true);
            }

            else if (notification.Skipped)
            {
                return(false);
            }

            else if (notification.IsRejected)
            {
                Logger.LogError("The userinfo request was rejected with the following error: {Error} ; {Description}",
                                /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                /* Description: */ notification.ErrorDescription);

                return(await SendUserinfoResponseAsync(new OpenIdConnectResponse {
                    Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = notification.ErrorDescription,
                    ErrorUri = notification.ErrorUri
                }));
            }

            // Ensure the "sub" claim has been correctly populated.
            if (string.IsNullOrEmpty(notification.Subject))
            {
                Logger.LogError("The mandatory 'sub' claim was missing from the userinfo response.");

                Response.StatusCode = 500;

                return(await SendUserinfoResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.ServerError,
                    ErrorDescription = "The mandatory 'sub' claim was missing."
                }));
            }

            return(await SendUserinfoResponseAsync(new OpenIdConnectResponse(notification.Claims)));
        }
        private async Task <bool> InvokeUserinfoEndpointAsync()
        {
            OpenIdConnectRequest request;

            if (HttpMethods.IsGet(Request.Method))
            {
                request = new OpenIdConnectRequest(Request.Query);
            }

            else if (HttpMethods.IsPost(Request.Method))
            {
                // Note: if no Content-Type header was specified, assume the userinfo request
                // doesn't contain any parameter and create an empty OpenIdConnectRequest.
                if (string.IsNullOrEmpty(Request.ContentType))
                {
                    request = new OpenIdConnectRequest();
                }

                else
                {
                    // May have media/type; charset=utf-8, allow partial match.
                    if (!Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase))
                    {
                        Logger.LogError("The userinfo request was rejected because an invalid 'Content-Type' " +
                                        "header was specified: {ContentType}.", Request.ContentType);

                        return(await SendUserinfoResponseAsync(new OpenIdConnectResponse
                        {
                            Error = OpenIdConnectConstants.Errors.InvalidRequest,
                            ErrorDescription = "The specified 'Content-Type' header is not valid."
                        }));
                    }

                    request = new OpenIdConnectRequest(await Request.ReadFormAsync());
                }
            }

            else
            {
                Logger.LogError("The userinfo request was rejected because an invalid " +
                                "HTTP method was specified: {Method}.", Request.Method);

                return(await SendUserinfoResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The specified HTTP method is not valid."
                }));
            }

            // Note: set the message type before invoking the ExtractUserinfoRequest event.
            request.SetProperty(OpenIdConnectConstants.Properties.MessageType,
                                OpenIdConnectConstants.MessageTypes.UserinfoRequest);

            // Insert the userinfo request in the ASP.NET context.
            Context.SetOpenIdConnectRequest(request);

            var @event = new ExtractUserinfoRequestContext(Context, Scheme, Options, request);
            await Provider.ExtractUserinfoRequest(@event);

            if (@event.Result != null)
            {
                if (@event.Result.Handled)
                {
                    Logger.LogDebug("The userinfo request was handled in user code.");

                    return(true);
                }

                else if (@event.Result.Skipped)
                {
                    Logger.LogDebug("The default userinfo request handling was skipped from user code.");

                    return(false);
                }
            }

            else if (@event.IsRejected)
            {
                Logger.LogError("The userinfo request was rejected with the following error: {Error} ; {Description}",
                                /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                /* Description: */ @event.ErrorDescription);

                return(await SendUserinfoResponseAsync(new OpenIdConnectResponse
                {
                    Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = @event.ErrorDescription,
                    ErrorUri = @event.ErrorUri
                }));
            }

            Logger.LogInformation("The userinfo request was successfully extracted " +
                                  "from the HTTP request: {Request}.", request);

            string token = null;

            if (!string.IsNullOrEmpty(request.AccessToken))
            {
                token = request.AccessToken;
            }

            else
            {
                string header = Request.Headers[HeaderNames.Authorization];
                if (!string.IsNullOrEmpty(header))
                {
                    if (!header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
                    {
                        Logger.LogError("The userinfo request was rejected because the " +
                                        "'Authorization' header was invalid: {Header}.", header);

                        return(await SendUserinfoResponseAsync(new OpenIdConnectResponse
                        {
                            Error = OpenIdConnectConstants.Errors.InvalidRequest,
                            ErrorDescription = "The specified 'Authorization' header is invalid."
                        }));
                    }

                    token = header.Substring("Bearer ".Length);
                }
            }

            if (string.IsNullOrEmpty(token))
            {
                Logger.LogError("The userinfo request was rejected because the access token was missing.");

                return(await SendUserinfoResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'access_token' parameter is missing."
                }));
            }

            var context = new ValidateUserinfoRequestContext(Context, Scheme, Options, request);
            await Provider.ValidateUserinfoRequest(context);

            if (context.Result != null)
            {
                if (context.Result.Handled)
                {
                    Logger.LogDebug("The userinfo request was handled in user code.");

                    return(true);
                }

                else if (context.Result.Skipped)
                {
                    Logger.LogDebug("The default userinfo request handling was skipped from user code.");

                    return(false);
                }
            }

            else if (context.IsRejected)
            {
                Logger.LogError("The userinfo request was rejected with the following error: {Error} ; {Description}",
                                /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                /* Description: */ context.ErrorDescription);

                return(await SendUserinfoResponseAsync(new OpenIdConnectResponse
                {
                    Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = context.ErrorDescription,
                    ErrorUri = context.ErrorUri
                }));
            }

            Logger.LogInformation("The userinfo request was successfully validated.");

            var ticket = await DeserializeAccessTokenAsync(token, request);

            if (ticket == null)
            {
                Logger.LogError("The userinfo request was rejected because the access token was invalid.");

                // Note: an invalid token should result in an unauthorized response
                // but returning a 401 status would invoke the previously registered
                // authentication middleware and potentially replace it by a 302 response.
                // To work around this limitation, a 400 error is returned instead.
                // See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoError
                return(await SendUserinfoResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidGrant,
                    ErrorDescription = "The specified access token is not valid."
                }));
            }

            if (ticket.Properties.ExpiresUtc.HasValue &&
                ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow)
            {
                Logger.LogError("The userinfo request was rejected because the access token was expired.");

                // Note: an invalid token should result in an unauthorized response
                // but returning a 401 status would invoke the previously registered
                // authentication middleware and potentially replace it by a 302 response.
                // To work around this limitation, a 400 error is returned instead.
                // See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoError
                return(await SendUserinfoResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidGrant,
                    ErrorDescription = "The specified access token is no longer valid."
                }));
            }

            var notification = new HandleUserinfoRequestContext(Context, Scheme, Options, request, ticket)
            {
                Issuer  = Context.GetIssuer(Options),
                Subject = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject)
            };

            // Note: when receiving an access token, its audiences list cannot be used for the "aud" claim
            // as the client application is not the intented audience but only an authorized presenter.
            // See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
            notification.Audiences.UnionWith(ticket.GetPresenters());

            // The following claims are all optional and should be excluded when
            // no corresponding value has been found in the authentication ticket.
            if (ticket.HasScope(OpenIdConnectConstants.Scopes.Profile))
            {
                notification.FamilyName = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.FamilyName);
                notification.GivenName  = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.GivenName);
                notification.BirthDate  = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Birthdate);
            }

            if (ticket.HasScope(OpenIdConnectConstants.Scopes.Email))
            {
                notification.Email = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Email);
            }

            if (ticket.HasScope(OpenIdConnectConstants.Scopes.Phone))
            {
                notification.PhoneNumber = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.PhoneNumber);
            }

            await Provider.HandleUserinfoRequest(notification);

            if (notification.Result != null)
            {
                if (notification.Result.Handled)
                {
                    Logger.LogDebug("The userinfo request was handled in user code.");

                    return(true);
                }

                else if (notification.Result.Skipped)
                {
                    Logger.LogDebug("The default userinfo request handling was skipped from user code.");

                    return(false);
                }
            }

            else if (notification.IsRejected)
            {
                Logger.LogError("The userinfo request was rejected with the following error: {Error} ; {Description}",
                                /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                /* Description: */ notification.ErrorDescription);

                return(await SendUserinfoResponseAsync(new OpenIdConnectResponse
                {
                    Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = notification.ErrorDescription,
                    ErrorUri = notification.ErrorUri
                }));
            }

            // Ensure the "sub" claim has been correctly populated.
            if (string.IsNullOrEmpty(notification.Subject))
            {
                throw new InvalidOperationException("The subject claim cannot be null or empty.");
            }

            var response = new OpenIdConnectResponse
            {
                [OpenIdConnectConstants.Claims.Subject]             = notification.Subject,
                [OpenIdConnectConstants.Claims.Address]             = notification.Address,
                [OpenIdConnectConstants.Claims.Birthdate]           = notification.BirthDate,
                [OpenIdConnectConstants.Claims.Email]               = notification.Email,
                [OpenIdConnectConstants.Claims.EmailVerified]       = notification.EmailVerified,
                [OpenIdConnectConstants.Claims.FamilyName]          = notification.FamilyName,
                [OpenIdConnectConstants.Claims.GivenName]           = notification.GivenName,
                [OpenIdConnectConstants.Claims.Issuer]              = notification.Issuer,
                [OpenIdConnectConstants.Claims.PhoneNumber]         = notification.PhoneNumber,
                [OpenIdConnectConstants.Claims.PhoneNumberVerified] = notification.PhoneNumberVerified,
                [OpenIdConnectConstants.Claims.PreferredUsername]   = notification.PreferredUsername,
                [OpenIdConnectConstants.Claims.Profile]             = notification.Profile,
                [OpenIdConnectConstants.Claims.Website]             = notification.Website
            };

            switch (notification.Audiences.Count)
            {
            case 0: break;

            case 1:
                response[OpenIdConnectConstants.Claims.Audience] = notification.Audiences.ElementAt(0);
                break;

            default:
                response[OpenIdConnectConstants.Claims.Audience] = new JArray(notification.Audiences);
                break;
            }

            foreach (var claim in notification.Claims)
            {
                response.SetParameter(claim.Key, claim.Value);
            }

            return(await SendUserinfoResponseAsync(response));
        }
예제 #3
0
 /// <summary>
 /// Called for each request to the userinfo endpoint to determine if the request is valid and should continue.
 /// </summary>
 /// <param name="context">The context of the event carries information in and results out.</param>
 /// <returns>Task to enable asynchronous execution</returns>
 public virtual Task ValidateUserinfoRequest(ValidateUserinfoRequestContext context) => OnValidateUserinfoRequest(context);
예제 #4
0
        private async Task <bool> InvokeUserinfoEndpointAsync()
        {
            OpenIdConnectMessage request;

            if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
            {
                request = new OpenIdConnectMessage(Request.Query.ToDictionary());
            }

            else if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase))
            {
                // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization
                if (string.IsNullOrWhiteSpace(Request.ContentType))
                {
                    Logger.LogError("The userinfo request was rejected because " +
                                    "the mandatory 'Content-Type' header was missing.");

                    return(await SendErrorPayloadAsync(new OpenIdConnectMessage {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "A malformed userinfo request has been received: " +
                                           "the mandatory 'Content-Type' header was missing from the POST request."
                    }));
                }

                // May have media/type; charset=utf-8, allow partial match.
                if (!Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase))
                {
                    Logger.LogError("The userinfo request was rejected because an invalid 'Content-Type' " +
                                    "header was received: {ContentType}.", Request.ContentType);

                    return(await SendErrorPayloadAsync(new OpenIdConnectMessage {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "A malformed userinfo request has been received: " +
                                           "the 'Content-Type' header contained an unexcepted value. " +
                                           "Make sure to use 'application/x-www-form-urlencoded'."
                    }));
                }

                var form = await Request.ReadFormAsync(Context.RequestAborted);

                request = new OpenIdConnectMessage(form.ToDictionary());
            }

            else
            {
                Logger.LogError("The userinfo request was rejected because an invalid " +
                                "HTTP method was received: {Method}.", Request.Method);

                return(await SendErrorPageAsync(new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "A malformed userinfo request has been received: " +
                                       "make sure to use either GET or POST."
                }));
            }

            // Insert the userinfo request in the ASP.NET context.
            Context.SetOpenIdConnectRequest(request);

            string token;

            if (!string.IsNullOrEmpty(request.AccessToken))
            {
                token = request.AccessToken;
            }

            else
            {
                string header = Request.Headers[HeaderNames.Authorization];
                if (string.IsNullOrEmpty(header))
                {
                    Logger.LogError("The userinfo request was rejected because " +
                                    "the 'Authorization' header was missing.");

                    return(await SendErrorPayloadAsync(new OpenIdConnectMessage {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "A malformed userinfo request has been received."
                    }));
                }

                if (!header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
                {
                    Logger.LogError("The userinfo request was rejected because the " +
                                    "'Authorization' header was invalid: {Header}.", header);

                    return(await SendErrorPayloadAsync(new OpenIdConnectMessage {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "A malformed userinfo request has been received."
                    }));
                }

                token = header.Substring("Bearer ".Length);
                if (string.IsNullOrEmpty(token))
                {
                    Logger.LogError("The userinfo request was rejected because the access token was missing.");

                    return(await SendErrorPayloadAsync(new OpenIdConnectMessage {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "A malformed userinfo request has been received."
                    }));
                }
            }

            var ticket = await DeserializeAccessTokenAsync(token, request);

            if (ticket == null)
            {
                Logger.LogError("The userinfo request was rejected because access token was invalid.");

                // Note: an invalid token should result in an unauthorized response
                // but returning a 401 status would invoke the previously registered
                // authentication middleware and potentially replace it by a 302 response.
                // To work around this limitation, a 400 error is returned instead.
                // See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoError
                return(await SendErrorPayloadAsync(new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.InvalidGrant,
                    ErrorDescription = "Invalid token."
                }));
            }

            if (!ticket.Properties.ExpiresUtc.HasValue ||
                ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow)
            {
                Logger.LogError("The userinfo request was rejected because access token was expired.");

                // Note: an invalid token should result in an unauthorized response
                // but returning a 401 status would invoke the previously registered
                // authentication middleware and potentially replace it by a 302 response.
                // To work around this limitation, a 400 error is returned instead.
                // See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoError
                return(await SendErrorPayloadAsync(new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.InvalidGrant,
                    ErrorDescription = "Expired token."
                }));
            }

            var validatingContext = new ValidateUserinfoRequestContext(Context, Options, request);
            await Options.Provider.ValidateUserinfoRequest(validatingContext);

            if (!validatingContext.IsValidated)
            {
                Logger.LogInformation("The userinfo request was rejected by application code.");

                return(await SendErrorPayloadAsync(new OpenIdConnectMessage {
                    Error = validatingContext.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = validatingContext.ErrorDescription,
                    ErrorUri = validatingContext.ErrorUri
                }));
            }

            var notification = new HandleUserinfoRequestContext(Context, Options, request, ticket);

            notification.Subject = ticket.Principal.GetClaim(ClaimTypes.NameIdentifier);
            notification.Issuer  = Context.GetIssuer(Options);

            // Note: when receiving an access token, its audiences list cannot be used for the "aud" claim
            // as the client application is not the intented audience but only an authorized presenter.
            // See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
            foreach (var presenter in ticket.GetPresenters())
            {
                notification.Audiences.Add(presenter);
            }

            // The following claims are all optional and should be excluded when
            // no corresponding value has been found in the authentication ticket.
            if (ticket.HasScope(OpenIdConnectConstants.Scopes.Profile))
            {
                notification.FamilyName = ticket.Principal.GetClaim(ClaimTypes.Surname);
                notification.GivenName  = ticket.Principal.GetClaim(ClaimTypes.GivenName);
                notification.BirthDate  = ticket.Principal.GetClaim(ClaimTypes.DateOfBirth);
            }

            if (ticket.HasScope(OpenIdConnectConstants.Scopes.Email))
            {
                notification.Email = ticket.Principal.GetClaim(ClaimTypes.Email);
            }
            ;

            if (ticket.HasScope(OpenIdConnectConstants.Scopes.Phone))
            {
                notification.PhoneNumber = ticket.Principal.GetClaim(ClaimTypes.HomePhone) ??
                                           ticket.Principal.GetClaim(ClaimTypes.MobilePhone) ??
                                           ticket.Principal.GetClaim(ClaimTypes.OtherPhone);
            }
            ;

            await Options.Provider.HandleUserinfoRequest(notification);

            if (notification.HandledResponse)
            {
                return(true);
            }

            else if (notification.Skipped)
            {
                return(false);
            }

            // Ensure the "sub" claim has been correctly populated.
            if (string.IsNullOrEmpty(notification.Subject))
            {
                Logger.LogError("The mandatory 'sub' claim was missing from the userinfo response.");

                Response.StatusCode = 500;

                await SendErrorPayloadAsync(new OpenIdConnectMessage {
                    Error            = OpenIdConnectConstants.Errors.ServerError,
                    ErrorDescription = "The mandatory 'sub' claim was missing."
                });

                return(true);
            }

            var payload = new JObject {
                [JwtRegisteredClaimNames.Sub] = notification.Subject
            };

            if (notification.Address != null)
            {
                payload[OpenIdConnectConstants.Claims.Address] = notification.Address;
            }

            if (!string.IsNullOrEmpty(notification.BirthDate))
            {
                payload[JwtRegisteredClaimNames.Birthdate] = notification.BirthDate;
            }

            if (!string.IsNullOrEmpty(notification.Email))
            {
                payload[JwtRegisteredClaimNames.Email] = notification.Email;
            }

            if (notification.EmailVerified.HasValue)
            {
                payload[OpenIdConnectConstants.Claims.EmailVerified] = notification.EmailVerified.Value;
            }

            if (!string.IsNullOrEmpty(notification.FamilyName))
            {
                payload[JwtRegisteredClaimNames.FamilyName] = notification.FamilyName;
            }

            if (!string.IsNullOrEmpty(notification.GivenName))
            {
                payload[JwtRegisteredClaimNames.GivenName] = notification.GivenName;
            }

            if (!string.IsNullOrEmpty(notification.Issuer))
            {
                payload[JwtRegisteredClaimNames.Iss] = notification.Issuer;
            }

            if (!string.IsNullOrEmpty(notification.PhoneNumber))
            {
                payload[OpenIdConnectConstants.Claims.PhoneNumber] = notification.PhoneNumber;
            }

            if (notification.PhoneNumberVerified.HasValue)
            {
                payload[OpenIdConnectConstants.Claims.PhoneNumberVerified] = notification.PhoneNumberVerified.Value;
            }

            if (!string.IsNullOrEmpty(notification.PreferredUsername))
            {
                payload[OpenIdConnectConstants.Claims.PreferredUsername] = notification.PreferredUsername;
            }

            if (!string.IsNullOrEmpty(notification.Profile))
            {
                payload[OpenIdConnectConstants.Claims.Profile] = notification.Profile;
            }

            if (!string.IsNullOrEmpty(notification.Website))
            {
                payload[OpenIdConnectConstants.Claims.Website] = notification.Website;
            }

            switch (notification.Audiences.Count)
            {
            case 0: break;

            case 1:
                payload.Add(JwtRegisteredClaimNames.Aud, notification.Audiences[0]);
                break;

            default:
                payload.Add(JwtRegisteredClaimNames.Aud, JArray.FromObject(notification.Audiences));
                break;
            }

            foreach (var claim in notification.Claims)
            {
                // Ignore claims whose value is null.
                if (claim.Value == null)
                {
                    continue;
                }

                payload.Add(claim.Key, claim.Value);
            }

            var context = new ApplyUserinfoResponseContext(Context, Options, request, payload);
            await Options.Provider.ApplyUserinfoResponse(context);

            if (context.HandledResponse)
            {
                return(true);
            }

            else if (context.Skipped)
            {
                return(false);
            }

            using (var buffer = new MemoryStream())
                using (var writer = new JsonTextWriter(new StreamWriter(buffer))) {
                    payload.WriteTo(writer);
                    writer.Flush();

                    Response.ContentLength = buffer.Length;
                    Response.ContentType   = "application/json;charset=UTF-8";

                    Response.Headers[HeaderNames.CacheControl] = "no-cache";
                    Response.Headers[HeaderNames.Pragma]       = "no-cache";
                    Response.Headers[HeaderNames.Expires]      = "-1";

                    buffer.Seek(offset: 0, loc: SeekOrigin.Begin);
                    await buffer.CopyToAsync(Response.Body, 4096, Context.RequestAborted);
                }

            return(true);
        }