/// <summary>
        /// Represents an event called for each validated userinfo request
        /// to allow the user code to decide how the request should be handled.
        /// </summary>
        /// <param name="context">The context instance associated with this event.</param>
        /// <returns>A <see cref="T:System.Threading.Tasks.Task" /> that can be used to monitor the asynchronous operation.</returns>
        public override Task HandleUserinfoRequest(HandleUserinfoRequestContext context)
        {
            var result   = base.HandleUserinfoRequest(context);
            var clientId = context.Ticket?.Identity?.GetClaim("client_id");
            var userName = context.Ticket?.Identity?.GetClaim("username");

            if (clientId.IsNullOrWhiteSpace() || userName.IsNullOrWhiteSpace())
            {
                return(result);
            }

            // Populate requested/allowed claims
            // See https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/issues/543
            using (var rockContext = new RockContext())
            {
                var user = new UserLoginService(rockContext).GetByUserName(userName);
                if (user == null)
                {
                    return(result);
                }

                var requestedScopes     = context.Ticket?.GetScopes();
                var clientAllowedScopes = RockIdentityHelper.NarrowRequestedScopesToApprovedScopes(rockContext, clientId, requestedScopes);
                var clientAllowedClaims = RockIdentityHelper.GetAllowedClientClaims(rockContext, clientId, clientAllowedScopes);
                var claimsIdentity      = RockIdentityHelper.GetRockClaimsIdentity(user, clientAllowedClaims, clientId);

                foreach (var claim in claimsIdentity?.Claims)
                {
                    context.Claims.Add(claim.Type, claim.Value);
                }
            }

            return(result);
        }
        /// <summary>
        /// Represents an event called for each validated configuration request
        /// to allow the user code to decide how the request should be handled.
        /// </summary>
        /// <param name="context">The context instance associated with this event.</param>
        /// <returns>A <see cref="T:System.Threading.Tasks.Task" /> that can be used to monitor the asynchronous operation.</returns>
        public override Task HandleConfigurationRequest(HandleConfigurationRequestContext context)
        {
            var result = base.HandleConfigurationRequest(context);

            using (var rockContext = new RockContext())
            {
                var activeScopes = RockIdentityHelper.GetActiveAuthScopes(rockContext);
                context.Scopes.UnionWith(activeScopes);

                var activeClaims = RockIdentityHelper.GetActiveAuthClaims(rockContext, activeScopes);
                context.Claims.UnionWith(activeClaims);
            }

            return(result);
        }
        /// <summary>
        /// Represents an event called for each validated token request
        /// to allow the user code to decide how the request should be handled.
        /// </summary>
        /// <param name="context">The context instance associated with this event.</param>
        public override Task HandleTokenRequest(HandleTokenRequestContext context)
        {
            // Only handle grant_type=password requests and let ASOS
            // process grant_type=refresh_token requests automatically.
            if (context.Request.IsPasswordGrantType())
            {
                UserLogin            user                = null;
                ClaimsIdentity       identity            = null;
                IEnumerable <string> allowedClientScopes = null;
                var loginValid = false;

                // Do all the data access here so we can dispose of the rock context asap.
                using (var rockContext = new RockContext())
                {
                    var userLoginService = new UserLoginService(rockContext);
                    user = userLoginService.GetByUserName(context.Request.Username);

                    // Populate the entity type for use later.
                    _ = user.EntityType;

                    allowedClientScopes = RockIdentityHelper.NarrowRequestedScopesToApprovedScopes(rockContext, context.Request.ClientId, context.Request.GetScopes()).ToList();
                    var allowedClientClaims = RockIdentityHelper.GetAllowedClientClaims(rockContext, context.Request.ClientId, allowedClientScopes);
                    identity = RockIdentityHelper.GetRockClaimsIdentity(user, allowedClientClaims, context.Request.ClientId);

                    var component = AuthenticationContainer.GetComponent(user.EntityType.Name);
                    if (component != null && component.IsActive && !component.RequiresRemoteAuthentication)
                    {
                        loginValid = component.AuthenticateAndTrack(user, context.Request.Password);
                        rockContext.SaveChanges();
                    }
                }

                if (identity == null || allowedClientScopes == null)
                {
                    context.Reject(
                        error: OpenIdConnectConstants.Errors.InvalidClient,
                        description: "Invalid client configuration.");
                    return(Task.FromResult(0));
                }

                if (user == null || !loginValid)
                {
                    context.Reject(
                        error: OpenIdConnectConstants.Errors.InvalidGrant,
                        description: "Invalid credentials.");
                    return(Task.FromResult(0));
                }

                // Ensure the user is allowed to sign in.
                if (!user.IsConfirmed.HasValue || !user.IsConfirmed.Value || (user.IsPasswordChangeRequired != null && user.IsPasswordChangeRequired.Value))
                {
                    context.Reject(
                        error: OpenIdConnectConstants.Errors.InvalidGrant,
                        description: "The specified user is not allowed to sign in.");
                    return(Task.FromResult(0));
                }

                // Create a new authentication ticket holding the user identity.
                var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());

                // Set the list of scopes granted to the client application.
                ticket.SetScopes(allowedClientScopes);

                // Set the resource servers the access token should be issued for.
                ticket.SetResources("resource_server");
                context.Validate(ticket);
            }

            if (context.Request.IsClientCredentialsGrantType())
            {
                // We don't need to validate the client id here because it was already validated in the ValidateTokenRequest method.
                var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationType);

                identity.AddClaim(OpenIdConnectConstants.Claims.Subject, context.Request.ClientId, OpenIdConnectConstants.Destinations.AccessToken);

                // Create a new authentication ticket holding the user identity.
                var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());

                context.Validate(ticket);
            }

            return(Task.FromResult(0));
        }