public void PropertyGetter_ReturnsExpectedParameter(string property, string name, OpenIdConnectParameter value)
        {
            // Arrange
            var response = new OpenIdConnectResponse();

            response.SetParameter(name, value);

            // Act and assert
            Assert.Equal(value.Value, typeof(OpenIdConnectResponse).GetProperty(property).GetValue(response));
        }
예제 #2
0
        private async Task <bool> InvokeIntrospectionEndpointAsync()
        {
            OpenIdConnectRequest request;

            // See https://tools.ietf.org/html/rfc7662#section-2.1
            // and https://tools.ietf.org/html/rfc7662#section-4
            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.IsNullOrEmpty(Request.ContentType))
                {
                    Logger.LogError("The introspection request was rejected because " +
                                    "the mandatory 'Content-Type' header was missing.");

                    return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "The mandatory 'Content-Type' header must be specified."
                    }));
                }

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

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

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

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

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

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

            // Store the introspection request in the ASP.NET context.
            Context.SetOpenIdConnectRequest(request);

            var @event = new ExtractIntrospectionRequestContext(Context, Options, request);
            await Options.Provider.ExtractIntrospectionRequest(@event);

            if (@event.HandledResponse)
            {
                Logger.LogDebug("The introspection request was handled in user code.");

                return(true);
            }

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

                return(false);
            }

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

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

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

            if (string.IsNullOrEmpty(request.Token))
            {
                return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'token' parameter is missing."
                }));
            }

            // Try to resolve the client credentials specified in the 'Authorization' header.
            // If they cannot be extracted, fallback to the client_id/client_secret parameters.
            var credentials = Request.Headers.GetClientCredentials();

            if (credentials != null)
            {
                // Reject requests that use multiple client authentication methods.
                // See https://tools.ietf.org/html/rfc6749#section-2.3 for more information.
                if (!string.IsNullOrEmpty(request.ClientSecret))
                {
                    Logger.LogError("The introspection request was rejected because " +
                                    "multiple client credentials were specified.");

                    return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "Multiple client credentials cannot be specified."
                    }));
                }

                request.ClientId     = credentials?.Key;
                request.ClientSecret = credentials?.Value;
            }

            var context = new ValidateIntrospectionRequestContext(Context, Options, request);
            await Options.Provider.ValidateIntrospectionRequest(context);

            // If the validation context was set as fully validated,
            // mark the OpenID Connect request as confidential.
            if (context.IsValidated)
            {
                request.SetProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel,
                                    OpenIdConnectConstants.ConfidentialityLevels.Private);
            }

            if (context.HandledResponse)
            {
                Logger.LogDebug("The introspection request was handled in user code.");

                return(true);
            }

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

                return(false);
            }

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

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

            // Store the validated client_id as a request property.
            request.SetProperty(OpenIdConnectConstants.Properties.ValidatedClientId, context.ClientId);

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

            AuthenticationTicket ticket = null;

            // Note: use the "token_type_hint" parameter to determine
            // the type of the token sent by the client application.
            // See https://tools.ietf.org/html/rfc7662#section-2.1
            switch (request.TokenTypeHint)
            {
            case OpenIdConnectConstants.TokenTypeHints.AccessToken:
                ticket = await DeserializeAccessTokenAsync(request.Token, request);

                break;

            case OpenIdConnectConstants.TokenTypeHints.AuthorizationCode:
                ticket = await DeserializeAuthorizationCodeAsync(request.Token, request);

                break;

            case OpenIdConnectConstants.TokenTypeHints.IdToken:
                ticket = await DeserializeIdentityTokenAsync(request.Token, request);

                break;

            case OpenIdConnectConstants.TokenTypeHints.RefreshToken:
                ticket = await DeserializeRefreshTokenAsync(request.Token, request);

                break;
            }

            // Note: if the token can't be found using "token_type_hint",
            // the search must be extended to all supported token types.
            // See https://tools.ietf.org/html/rfc7662#section-2.1
            if (ticket == null)
            {
                ticket = await DeserializeAccessTokenAsync(request.Token, request) ??
                         await DeserializeAuthorizationCodeAsync(request.Token, request) ??
                         await DeserializeIdentityTokenAsync(request.Token, request) ??
                         await DeserializeRefreshTokenAsync(request.Token, request);
            }

            if (ticket == null)
            {
                Logger.LogInformation("The introspection request was rejected because the token was invalid.");

                return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                {
                    [OpenIdConnectConstants.Parameters.Active] = false
                }));
            }

            // Note: unlike refresh or identity tokens that can only be validated by client applications,
            // access tokens can be validated by either resource servers or client applications:
            // in both cases, the caller must be authenticated if the ticket is marked as confidential.
            if (context.IsSkipped && ticket.IsConfidential())
            {
                Logger.LogError("The introspection request was rejected because the caller was not authenticated.");

                return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                {
                    [OpenIdConnectConstants.Parameters.Active] = false
                }));
            }

            // If the ticket is already expired, directly return active=false.
            if (ticket.Properties.ExpiresUtc.HasValue &&
                ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow)
            {
                Logger.LogInformation("The introspection request was rejected because the token was expired.");

                return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                {
                    [OpenIdConnectConstants.Parameters.Active] = false
                }));
            }

            // When a client_id can be inferred from the introspection request,
            // ensure that the client application is a valid audience/presenter.
            if (!string.IsNullOrEmpty(context.ClientId))
            {
                if (ticket.IsAuthorizationCode() && ticket.HasPresenter() && !ticket.HasPresenter(context.ClientId))
                {
                    Logger.LogError("The introspection request was rejected because the " +
                                    "authorization code was issued to a different client.");

                    return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                    {
                        [OpenIdConnectConstants.Parameters.Active] = false
                    }));
                }

                // Ensure the caller is listed as a valid audience or authorized presenter.
                else if (ticket.IsAccessToken() && ticket.HasAudience() && !ticket.HasAudience(context.ClientId) &&
                         ticket.HasPresenter() && !ticket.HasPresenter(context.ClientId))
                {
                    Logger.LogError("The introspection request was rejected because the access token " +
                                    "was issued to a different client or for another resource server.");

                    return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                    {
                        [OpenIdConnectConstants.Parameters.Active] = false
                    }));
                }

                // Reject the request if the caller is not listed as a valid audience.
                else if (ticket.IsIdentityToken() && ticket.HasAudience() && !ticket.HasAudience(context.ClientId))
                {
                    Logger.LogError("The introspection request was rejected because the " +
                                    "identity token was issued to a different client.");

                    return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                    {
                        [OpenIdConnectConstants.Parameters.Active] = false
                    }));
                }

                // Reject the introspection request if the caller doesn't
                // correspond to the client application the token was issued to.
                else if (ticket.IsRefreshToken() && ticket.HasPresenter() && !ticket.HasPresenter(context.ClientId))
                {
                    Logger.LogError("The introspection request was rejected because the " +
                                    "refresh token was issued to a different client.");

                    return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                    {
                        [OpenIdConnectConstants.Parameters.Active] = false
                    }));
                }
            }

            var notification = new HandleIntrospectionRequestContext(Context, Options, request, ticket)
            {
                Active     = true,
                Issuer     = Context.GetIssuer(Options),
                TokenId    = ticket.GetTokenId(),
                TokenUsage = ticket.GetProperty(OpenIdConnectConstants.Properties.TokenUsage),
                Subject    = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject)
            };

            // Note: only set "token_type" when the received token is an access token.
            // See https://tools.ietf.org/html/rfc7662#section-2.2
            // and https://tools.ietf.org/html/rfc6749#section-5.1
            if (ticket.IsAccessToken())
            {
                notification.TokenType = OpenIdConnectConstants.TokenTypes.Bearer;
            }

            notification.IssuedAt  = ticket.Properties.IssuedUtc;
            notification.NotBefore = ticket.Properties.IssuedUtc;
            notification.ExpiresAt = ticket.Properties.ExpiresUtc;

            // Infer the audiences/client_id claims from the properties stored in the authentication ticket.
            // Note: the client_id claim must be a unique string so multiple presenters cannot be returned.
            // To work around this limitation, only the first one is returned if multiple values are listed.
            notification.Audiences.UnionWith(ticket.GetAudiences());
            notification.ClientId = ticket.GetPresenters().FirstOrDefault();

            // Note: non-metadata claims are only added if the caller's client_id is known
            // AND is in the specified audiences, unless there's no explicit audience.
            if (!ticket.HasAudience() || (!string.IsNullOrEmpty(context.ClientId) && ticket.HasAudience(context.ClientId)))
            {
                notification.Username = ticket.Principal.Identity?.Name;
                notification.Scopes.UnionWith(ticket.GetScopes());

                // Potentially sensitive claims are only exposed if the client was authenticated
                // and if the authentication ticket corresponds to an identity or access token.
                if (context.IsValidated && (ticket.IsAccessToken() || ticket.IsIdentityToken()))
                {
                    foreach (var grouping in ticket.Principal.Claims.GroupBy(claim => claim.Type))
                    {
                        // Exclude standard claims, that are already handled via strongly-typed properties.
                        // Make sure to always update this list when adding new built-in claim properties.
                        var type = grouping.Key;
                        switch (type)
                        {
                        case OpenIdConnectConstants.Claims.Audience:
                        case OpenIdConnectConstants.Claims.ExpiresAt:
                        case OpenIdConnectConstants.Claims.IssuedAt:
                        case OpenIdConnectConstants.Claims.Issuer:
                        case OpenIdConnectConstants.Claims.NotBefore:
                        case OpenIdConnectConstants.Claims.Scope:
                        case OpenIdConnectConstants.Claims.Subject:
                        case OpenIdConnectConstants.Claims.TokenType:
                        case OpenIdConnectConstants.Claims.TokenUsage:
                            continue;
                        }

                        var claims = grouping.ToArray();
                        switch (claims.Length)
                        {
                        case 0: continue;

                        // When there's only one claim with the same type, directly
                        // convert the claim as an OpenIdConnectParameter instance,
                        // whose token type is determined from the claim value type.
                        case 1:
                        {
                            notification.Claims[type] = claims[0].AsParameter();

                            continue;
                        }

                        // When multiple claims share the same type, convert all the claims
                        // to OpenIdConnectParameter instances, retrieve the underlying
                        // JSON values and add everything to a new JSON array.
                        default:
                        {
                            notification.Claims[type] = new JArray(claims.Select(claim => claim.AsParameter().Value));

                            continue;
                        }
                        }
                    }
                }
            }

            await Options.Provider.HandleIntrospectionRequest(notification);

            if (notification.HandledResponse)
            {
                Logger.LogDebug("The introspection request was handled in user code.");

                return(true);
            }

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

                return(false);
            }

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

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

            var response = new OpenIdConnectResponse
            {
                [OpenIdConnectConstants.Claims.Active] = notification.Active
            };

            // Only add the other properties if
            // the token is considered as active.
            if (notification.Active)
            {
                response[OpenIdConnectConstants.Claims.Issuer]     = notification.Issuer;
                response[OpenIdConnectConstants.Claims.Username]   = notification.Username;
                response[OpenIdConnectConstants.Claims.Subject]    = notification.Subject;
                response[OpenIdConnectConstants.Claims.Scope]      = string.Join(" ", notification.Scopes);
                response[OpenIdConnectConstants.Claims.JwtId]      = notification.TokenId;
                response[OpenIdConnectConstants.Claims.TokenType]  = notification.TokenType;
                response[OpenIdConnectConstants.Claims.TokenUsage] = notification.TokenUsage;
                response[OpenIdConnectConstants.Claims.ClientId]   = notification.ClientId;

                if (notification.IssuedAt != null)
                {
                    response[OpenIdConnectConstants.Claims.IssuedAt] =
                        EpochTime.GetIntDate(notification.IssuedAt.Value.UtcDateTime);
                }

                if (notification.NotBefore != null)
                {
                    response[OpenIdConnectConstants.Claims.NotBefore] =
                        EpochTime.GetIntDate(notification.NotBefore.Value.UtcDateTime);
                }

                if (notification.ExpiresAt != null)
                {
                    response[OpenIdConnectConstants.Claims.ExpiresAt] =
                        EpochTime.GetIntDate(notification.ExpiresAt.Value.UtcDateTime);
                }

                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 SendIntrospectionResponseAsync(response));
        }
        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.");

                return(await SendUserinfoResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidToken,
                    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.");

                return(await SendUserinfoResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidToken,
                    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));
        }
        private async Task <bool> InvokeConfigurationEndpointAsync()
        {
            // Metadata requests must be made via GET.
            // See http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
            if (!string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
            {
                Logger.LogError("The configuration request was rejected because an invalid " +
                                "HTTP method was specified: {Method}.", Request.Method);

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

            var request = new OpenIdConnectRequest(Request.Query);

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

            // Store the configuration request in the OWIN context.
            Context.SetOpenIdConnectRequest(request);

            var @event = new ExtractConfigurationRequestContext(Context, Options, request);
            await Options.Provider.ExtractConfigurationRequest(@event);

            if (@event.HandledResponse)
            {
                Logger.LogDebug("The configuration request was handled in user code.");

                return(true);
            }

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

                return(false);
            }

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

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

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

            var context = new ValidateConfigurationRequestContext(Context, Options, request);
            await Options.Provider.ValidateConfigurationRequest(context);

            if (context.HandledResponse)
            {
                Logger.LogDebug("The configuration request was handled in user code.");

                return(true);
            }

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

                return(false);
            }

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

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

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

            var notification = new HandleConfigurationRequestContext(Context, Options, request)
            {
                Issuer = Context.GetIssuer(Options)
            };

            if (Options.AuthorizationEndpointPath.HasValue)
            {
                notification.AuthorizationEndpoint = notification.Issuer.AddPath(Options.AuthorizationEndpointPath);
            }

            if (Options.CryptographyEndpointPath.HasValue)
            {
                notification.CryptographyEndpoint = notification.Issuer.AddPath(Options.CryptographyEndpointPath);
            }

            if (Options.IntrospectionEndpointPath.HasValue)
            {
                notification.IntrospectionEndpoint = notification.Issuer.AddPath(Options.IntrospectionEndpointPath);

                notification.IntrospectionEndpointAuthenticationMethods.Add(
                    OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretBasic);
                notification.IntrospectionEndpointAuthenticationMethods.Add(
                    OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretPost);
            }

            if (Options.LogoutEndpointPath.HasValue)
            {
                notification.LogoutEndpoint = notification.Issuer.AddPath(Options.LogoutEndpointPath);
            }

            if (Options.RevocationEndpointPath.HasValue)
            {
                notification.RevocationEndpoint = notification.Issuer.AddPath(Options.RevocationEndpointPath);

                notification.RevocationEndpointAuthenticationMethods.Add(
                    OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretBasic);
                notification.RevocationEndpointAuthenticationMethods.Add(
                    OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretPost);
            }

            if (Options.TokenEndpointPath.HasValue)
            {
                notification.TokenEndpoint = notification.Issuer.AddPath(Options.TokenEndpointPath);

                notification.TokenEndpointAuthenticationMethods.Add(
                    OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretBasic);
                notification.TokenEndpointAuthenticationMethods.Add(
                    OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretPost);
            }

            if (Options.UserinfoEndpointPath.HasValue)
            {
                notification.UserinfoEndpoint = notification.Issuer.AddPath(Options.UserinfoEndpointPath);
            }

            if (Options.AuthorizationEndpointPath.HasValue)
            {
                notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Implicit);

                if (Options.TokenEndpointPath.HasValue)
                {
                    // Only expose the code grant type and the code challenge methods
                    // if both the authorization and the token endpoints are enabled.
                    notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.AuthorizationCode);

                    // Note: supporting S256 is mandatory for authorization servers that implement PKCE.
                    // See https://tools.ietf.org/html/rfc7636#section-4.2 for more information.
                    notification.CodeChallengeMethods.Add(OpenIdConnectConstants.CodeChallengeMethods.Plain);
                    notification.CodeChallengeMethods.Add(OpenIdConnectConstants.CodeChallengeMethods.Sha256);
                }
            }

            if (Options.TokenEndpointPath.HasValue)
            {
                notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.RefreshToken);
                notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.ClientCredentials);
                notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Password);
            }

            // Only populate response_modes_supported and response_types_supported
            // if the authorization endpoint is available.
            if (Options.AuthorizationEndpointPath.HasValue)
            {
                notification.ResponseModes.Add(OpenIdConnectConstants.ResponseModes.FormPost);
                notification.ResponseModes.Add(OpenIdConnectConstants.ResponseModes.Fragment);
                notification.ResponseModes.Add(OpenIdConnectConstants.ResponseModes.Query);

                notification.ResponseTypes.Add(OpenIdConnectConstants.ResponseTypes.Token);

                // Only expose response types containing code when
                // the token endpoint has not been explicitly disabled.
                if (Options.TokenEndpointPath.HasValue)
                {
                    notification.ResponseTypes.Add(OpenIdConnectConstants.ResponseTypes.Code);

                    notification.ResponseTypes.Add(
                        OpenIdConnectConstants.ResponseTypes.Code + ' ' +
                        OpenIdConnectConstants.ResponseTypes.Token);
                }

                // Only expose the response types containing id_token if an asymmetric signing key is available.
                if (Options.SigningCredentials.Any(credentials => credentials.Key is AsymmetricSecurityKey))
                {
                    notification.ResponseTypes.Add(OpenIdConnectConstants.ResponseTypes.IdToken);

                    notification.ResponseTypes.Add(
                        OpenIdConnectConstants.ResponseTypes.IdToken + ' ' +
                        OpenIdConnectConstants.ResponseTypes.Token);

                    // Only expose response types containing code when
                    // the token endpoint has not been explicitly disabled.
                    if (Options.TokenEndpointPath.HasValue)
                    {
                        notification.ResponseTypes.Add(
                            OpenIdConnectConstants.ResponseTypes.Code + ' ' +
                            OpenIdConnectConstants.ResponseTypes.IdToken);

                        notification.ResponseTypes.Add(
                            OpenIdConnectConstants.ResponseTypes.Code + ' ' +
                            OpenIdConnectConstants.ResponseTypes.IdToken + ' ' +
                            OpenIdConnectConstants.ResponseTypes.Token);
                    }
                }
            }

            notification.Scopes.Add(OpenIdConnectConstants.Scopes.OpenId);

            notification.Claims.Add(OpenIdConnectConstants.Claims.Audience);
            notification.Claims.Add(OpenIdConnectConstants.Claims.ExpiresAt);
            notification.Claims.Add(OpenIdConnectConstants.Claims.IssuedAt);
            notification.Claims.Add(OpenIdConnectConstants.Claims.Issuer);
            notification.Claims.Add(OpenIdConnectConstants.Claims.JwtId);
            notification.Claims.Add(OpenIdConnectConstants.Claims.Subject);

            notification.SubjectTypes.Add(OpenIdConnectConstants.SubjectTypes.Public);

            foreach (var credentials in Options.SigningCredentials)
            {
                // If the signing key is not an asymmetric key, ignore it.
                if (!(credentials.Key is AsymmetricSecurityKey))
                {
                    continue;
                }

                // Try to resolve the JWA algorithm short name. If a null value is returned, ignore it.
                var algorithm = OpenIdConnectServerHelpers.GetJwtAlgorithm(credentials.Algorithm);
                if (string.IsNullOrEmpty(algorithm))
                {
                    continue;
                }

                notification.IdTokenSigningAlgorithms.Add(algorithm);
            }

            await Options.Provider.HandleConfigurationRequest(notification);

            if (notification.HandledResponse)
            {
                Logger.LogDebug("The configuration request was handled in user code.");

                return(true);
            }

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

                return(false);
            }

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

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

            var response = new OpenIdConnectResponse
            {
                [OpenIdConnectConstants.Metadata.Issuer] = notification.Issuer,
                [OpenIdConnectConstants.Metadata.AuthorizationEndpoint] = notification.AuthorizationEndpoint,
                [OpenIdConnectConstants.Metadata.TokenEndpoint]         = notification.TokenEndpoint,
                [OpenIdConnectConstants.Metadata.IntrospectionEndpoint] = notification.IntrospectionEndpoint,
                [OpenIdConnectConstants.Metadata.EndSessionEndpoint]    = notification.LogoutEndpoint,
                [OpenIdConnectConstants.Metadata.RevocationEndpoint]    = notification.RevocationEndpoint,
                [OpenIdConnectConstants.Metadata.UserinfoEndpoint]      = notification.UserinfoEndpoint,
                [OpenIdConnectConstants.Metadata.JwksUri]                                   = notification.CryptographyEndpoint,
                [OpenIdConnectConstants.Metadata.GrantTypesSupported]                       = new JArray(notification.GrantTypes),
                [OpenIdConnectConstants.Metadata.ResponseTypesSupported]                    = new JArray(notification.ResponseTypes),
                [OpenIdConnectConstants.Metadata.ResponseModesSupported]                    = new JArray(notification.ResponseModes),
                [OpenIdConnectConstants.Metadata.ScopesSupported]                           = new JArray(notification.Scopes),
                [OpenIdConnectConstants.Metadata.ClaimsSupported]                           = new JArray(notification.Claims),
                [OpenIdConnectConstants.Metadata.IdTokenSigningAlgValuesSupported]          = new JArray(notification.IdTokenSigningAlgorithms),
                [OpenIdConnectConstants.Metadata.CodeChallengeMethodsSupported]             = new JArray(notification.CodeChallengeMethods),
                [OpenIdConnectConstants.Metadata.SubjectTypesSupported]                     = new JArray(notification.SubjectTypes),
                [OpenIdConnectConstants.Metadata.TokenEndpointAuthMethodsSupported]         = new JArray(notification.TokenEndpointAuthenticationMethods),
                [OpenIdConnectConstants.Metadata.IntrospectionEndpointAuthMethodsSupported] = new JArray(notification.IntrospectionEndpointAuthenticationMethods),
                [OpenIdConnectConstants.Metadata.RevocationEndpointAuthMethodsSupported]    = new JArray(notification.RevocationEndpointAuthenticationMethods)
            };

            foreach (var metadata in notification.Metadata)
            {
                response.SetParameter(metadata.Key, metadata.Value);
            }

            return(await SendConfigurationResponseAsync(response));
        }
        /// <summary>
        /// Sends a generic OpenID Connect request to the given endpoint and
        /// converts the returned response to an OpenID Connect response.
        /// </summary>
        /// <param name="method">The HTTP method used to send the OpenID Connect request.</param>
        /// <param name="uri">The endpoint to which the request is sent.</param>
        /// <param name="request">The OpenID Connect request to send.</param>
        /// <returns>The OpenID Connect response returned by the server.</returns>
        public virtual async Task<OpenIdConnectResponse> SendAsync(
            [NotNull] HttpMethod method, [NotNull] Uri uri,
            [NotNull] OpenIdConnectRequest request) {
            if (method == null) {
                throw new ArgumentNullException(nameof(method));
            }

            if (uri == null) {
                throw new ArgumentNullException(nameof(uri));
            }

            if (request == null) {
                throw new ArgumentNullException(nameof(request));
            }

            if (HttpClient.BaseAddress == null && !uri.IsAbsoluteUri) {
                throw new ArgumentException("The address cannot be a relative URI when no base address " +
                                            "is associated with the HTTP client.", nameof(uri));
            }

            var parameters = new Dictionary<string, string>();

            foreach (var parameter in request.GetParameters()) {
                var value = parameter.Value as JValue;
                if (value == null) {
                    continue;
                }

                parameters.Add(parameter.Key, (string) parameter.Value);
            }

            if (method == HttpMethod.Get && parameters.Count != 0) {
                var builder = new StringBuilder();

                foreach (var parameter in parameters) {
                    if (builder.Length != 0) {
                        builder.Append('&');
                    }

                    builder.Append(UrlEncoder.Default.Encode(parameter.Key));
                    builder.Append('=');
                    builder.Append(UrlEncoder.Default.Encode(parameter.Value));
                }

                if (!uri.IsAbsoluteUri) {
                    uri = new Uri(HttpClient.BaseAddress, uri);
                }

                uri = new UriBuilder(uri) { Query = builder.ToString() }.Uri;
            }

            var message = new HttpRequestMessage(method, uri);

            if (method != HttpMethod.Get) {
                message.Content = new FormUrlEncodedContent(parameters);
            }

            var response = await HttpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead);

            if (response.Headers.Location != null) {
                var payload = response.Headers.Location.Fragment;
                if (string.IsNullOrEmpty(payload)) {
                    payload = response.Headers.Location.Query;
                }

                if (string.IsNullOrEmpty(payload)) {
                    return new OpenIdConnectResponse();
                }

                var result = new OpenIdConnectResponse();

                using (var tokenizer = new StringTokenizer(payload, OpenIdConnectConstants.Separators.Ampersand).GetEnumerator()) {
                    while (tokenizer.MoveNext()) {
                        var parameter = tokenizer.Current;
                        if (parameter.Length == 0) {
                            continue;
                        }

                        // Always skip the first char (# or ?).
                        if (parameter.Offset == 0) {
                            parameter = parameter.Subsegment(1, parameter.Length - 1);
                        }

                        var index = parameter.IndexOf('=');
                        if (index == -1) {
                            continue;
                        }

                        var name = parameter.Substring(0, index);
                        if (string.IsNullOrEmpty(name)) {
                            continue;
                        }

                        var value = parameter.Substring(index + 1, parameter.Length - (index + 1));
                        if (string.IsNullOrEmpty(value)) {
                            continue;
                        }

                        result.SetParameter(
                            Uri.UnescapeDataString(name.Replace('+', ' ')),
                            Uri.UnescapeDataString(value.Replace('+', ' ')));
                    }
                }

                return result;
            }

            else if (string.Equals(response.Content?.Headers?.ContentType?.MediaType, "application/json", StringComparison.OrdinalIgnoreCase)) {
                using (var stream = await response.Content.ReadAsStreamAsync())
                using (var reader = new JsonTextReader(new StreamReader(stream))) {
                    var payload = JToken.ReadFrom(reader) as JObject;
                    if (payload == null) {
                        throw new InvalidOperationException("The JSON payload returned by the server was invalid.");
                    }

                    return new OpenIdConnectResponse(payload);
                }
            }

            else if (string.Equals(response.Content?.Headers?.ContentType?.MediaType, "text/html", StringComparison.OrdinalIgnoreCase)) {
                using (var stream = await response.Content.ReadAsStreamAsync()) {
                    var result = new OpenIdConnectResponse();

                    var document = await new HtmlParser().ParseAsync(stream);

                    foreach (var element in document.Body.GetElementsByTagName("input")) {
                        var name = element.GetAttribute("name");
                        if (string.IsNullOrEmpty(name)) {
                            continue;
                        }

                        var value = element.GetAttribute("value");
                        if (string.IsNullOrEmpty(value)) {
                            continue;
                        }

                        result.SetParameter(name, value);
                    }

                    return result;
                }
            }

            else if (string.Equals(response.Content?.Headers?.ContentType?.MediaType, "text/plain", StringComparison.OrdinalIgnoreCase)) {
                using (var stream = await response.Content.ReadAsStreamAsync())
                using (var reader = new StreamReader(stream)) {
                    var result = new OpenIdConnectResponse();

                    for (var line = await reader.ReadLineAsync(); line != null; line = await reader.ReadLineAsync()) {
                        var index = line.IndexOf(':');
                        if (index == -1) {
                            continue;
                        }

                        result.SetParameter(line.Substring(0, index), line.Substring(index + 1));
                    }

                    return result;
                }
            }

            throw new InvalidOperationException("The server returned an unexpected response.");
        }
예제 #6
0
        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());
            }

            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.UserinfoRequest);

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

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

            if (@event.HandledResponse)
            {
                Logger.LogDebug("The userinfo request was handled in user code.");

                return(true);
            }

            else if (@event.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;

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

            else
            {
                var header = Request.Headers.Get("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)
            {
                Logger.LogDebug("The userinfo request was handled in user code.");

                return(true);
            }

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

                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
                }));
            }

            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 = "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.Issuer  = Context.GetIssuer(Options);
            notification.Subject = ticket.Identity.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.Identity.GetClaim(OpenIdConnectConstants.Claims.FamilyName);
                notification.GivenName  = ticket.Identity.GetClaim(OpenIdConnectConstants.Claims.GivenName);
                notification.BirthDate  = ticket.Identity.GetClaim(OpenIdConnectConstants.Claims.Birthdate);
            }

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

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

            await Options.Provider.HandleUserinfoRequest(notification);

            if (notification.HandledResponse)
            {
                Logger.LogDebug("The userinfo request was handled in user code.");

                return(true);
            }

            else if (notification.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));
        }