private async Task <TokenRequestValidationResult> ValidateAuthorizationCodeRequestAsync(NameValueCollection parameters)
        {
            _logger.LogDebug("Start validation of authorization code token request");

            /////////////////////////////////////////////
            // check if client is authorized for grant type
            /////////////////////////////////////////////
            if (!_validatedRequest.Client.AllowedGrantTypes.ToList().Contains(GrantType.AuthorizationCode) &&
                !_validatedRequest.Client.AllowedGrantTypes.ToList().Contains(GrantType.Hybrid))
            {
                LogError("Client not authorized for code flow");
                return(Invalid(OidcConstants.TokenErrors.UnauthorizedClient));
            }

            /////////////////////////////////////////////
            // validate authorization code
            /////////////////////////////////////////////
            var code = parameters.Get(OidcConstants.TokenRequest.Code);

            if (code.IsMissing())
            {
                var error = "Authorization code is missing";
                LogError(error);
                await RaiseFailedAuthorizationCodeRedeemedEventAsync(null, error);

                return(Invalid(OidcConstants.TokenErrors.InvalidGrant));
            }

            if (code.Length > _options.InputLengthRestrictions.AuthorizationCode)
            {
                var error = "Authorization code is too long";
                LogError(error);
                await RaiseFailedAuthorizationCodeRedeemedEventAsync(null, error);

                return(Invalid(OidcConstants.TokenErrors.InvalidGrant));
            }

            _validatedRequest.AuthorizationCodeHandle = code;

            var authZcode = await _grants.GetAuthorizationCodeAsync(code);

            if (authZcode == null)
            {
                LogError("Authorization code cannot be found in the store: {code}", code);
                await RaiseFailedAuthorizationCodeRedeemedEventAsync(code, "Invalid handle");

                return(Invalid(OidcConstants.TokenErrors.InvalidGrant));
            }

            await _grants.RemoveAuthorizationCodeAsync(code);

            /////////////////////////////////////////////
            // populate session id
            /////////////////////////////////////////////
            if (authZcode.SessionId.IsPresent())
            {
                _validatedRequest.SessionId = authZcode.SessionId;
            }

            /////////////////////////////////////////////
            // validate client binding
            /////////////////////////////////////////////
            if (authZcode.ClientId != _validatedRequest.Client.ClientId)
            {
                LogError("Client {clientId} is trying to use a code from client {clientId}", _validatedRequest.Client.ClientId, authZcode.ClientId);
                await RaiseFailedAuthorizationCodeRedeemedEventAsync(code, "Invalid client binding");

                return(Invalid(OidcConstants.TokenErrors.InvalidGrant));
            }

            /////////////////////////////////////////////
            // validate code expiration
            /////////////////////////////////////////////
            if (authZcode.CreationTime.HasExceeded(_validatedRequest.Client.AuthorizationCodeLifetime))
            {
                var error = "Authorization code is expired";
                LogError(error);
                await RaiseFailedAuthorizationCodeRedeemedEventAsync(code, error);

                return(Invalid(OidcConstants.TokenErrors.InvalidGrant));
            }

            _validatedRequest.AuthorizationCode = authZcode;

            /////////////////////////////////////////////
            // validate redirect_uri
            /////////////////////////////////////////////
            var redirectUri = parameters.Get(OidcConstants.TokenRequest.RedirectUri);

            if (redirectUri.IsMissing())
            {
                var error = "Redirect URI is missing";
                LogError(error);
                await RaiseFailedAuthorizationCodeRedeemedEventAsync(code, error);

                return(Invalid(OidcConstants.TokenErrors.UnauthorizedClient));
            }

            if (redirectUri.Equals(_validatedRequest.AuthorizationCode.RedirectUri, StringComparison.Ordinal) == false)
            {
                LogError("Invalid redirect_uri: {redirectUri}", redirectUri);
                var error = "Invalid redirect_uri: " + redirectUri;
                await RaiseFailedAuthorizationCodeRedeemedEventAsync(code, error);

                return(Invalid(OidcConstants.TokenErrors.UnauthorizedClient));
            }

            /////////////////////////////////////////////
            // validate scopes are present
            /////////////////////////////////////////////
            if (_validatedRequest.AuthorizationCode.RequestedScopes == null ||
                !_validatedRequest.AuthorizationCode.RequestedScopes.Any())
            {
                var error = "Authorization code has no associated scopes";
                LogError(error);
                await RaiseFailedAuthorizationCodeRedeemedEventAsync(code, error);

                return(Invalid(OidcConstants.TokenErrors.InvalidRequest));
            }

            /////////////////////////////////////////////
            // validate PKCE parameters
            /////////////////////////////////////////////
            var codeVerifier = parameters.Get(OidcConstants.TokenRequest.CodeVerifier);

            if (_validatedRequest.Client.RequirePkce)
            {
                _logger.LogDebug("Client required a proof key for code exchange. Starting PKCE validation");

                var proofKeyResult = ValidateAuthorizationCodeWithProofKeyParameters(codeVerifier, _validatedRequest.AuthorizationCode);
                if (proofKeyResult.IsError)
                {
                    return(proofKeyResult);
                }

                _validatedRequest.CodeVerifier = codeVerifier;
            }
            else
            {
                if (codeVerifier.IsPresent())
                {
                    LogError("Unexpected code_verifier: {codeVerifier}", codeVerifier);
                    return(Invalid(OidcConstants.TokenErrors.InvalidGrant));
                }
            }

            /////////////////////////////////////////////
            // make sure user is enabled
            /////////////////////////////////////////////
            var isActiveCtx = new IsActiveContext(_validatedRequest.AuthorizationCode.Subject, _validatedRequest.Client, IdentityServerConstants.ProfileIsActiveCallers.AuthorizationCodeValidation);
            await _profile.IsActiveAsync(isActiveCtx);

            if (isActiveCtx.IsActive == false)
            {
                LogError("User has been disabled: {subjectId}", _validatedRequest.AuthorizationCode.Subject.GetSubjectId());
                var error = "User has been disabled: " + _validatedRequest.AuthorizationCode.Subject.GetSubjectId();
                await RaiseFailedAuthorizationCodeRedeemedEventAsync(code, error);

                return(Invalid(OidcConstants.TokenErrors.InvalidGrant));
            }

            _logger.LogDebug("Validation of authorization code token request success");
            await RaiseSuccessfulAuthorizationCodeRedeemedEventAsync();

            return(Valid());
        }