aws / aws-sdk-net-extensions-cognito

An extension library to assist in the Amazon Cognito User Pools authentication process
Apache License 2.0
102 stars 49 forks source link

NotAuthorizedException: SecretHash does not match for the client: xxxxxxxxxxxxxxxxxxx when trying refresh token flow #94

Closed LoopIssuer closed 1 year ago

LoopIssuer commented 1 year ago

Describe the bug

Hi, I had an issue when trying to use RefreshToken flow. I get error: NotAuthorizedException: SecretHash does not match for the client: xxxxxxxxxxxxxxxxxxx I tried: -using secret directly -using GetSecretHash with userName, userEmail, USerID, User Sub Id Always the same issue.

I'm trying:

 public async void GetCredsFromRefreshAsync_(string refreshToken, string accessToken, string idToken, string userName, string userId)
{
  var secretHash = GetSecretHash(userId + _cognitoCredentials.AppClientId);
            CognitoUserPool userPool = new CognitoUserPool(_cognitoCredentials.UserPoolId, _cognitoCredentials.AppClientId, _provider, _cognitoCredentials.Secret);
            CognitoUser user = new CognitoUser(userName, _cognitoCredentials.AppClientId, userPool, _provider, _cognitoCredentials.Secret);
            user.SessionTokens = new CognitoUserSession( null, null, refreshToken, DateTime.Now, DateTime.Now.AddHours(1));
            InitiateRefreshTokenAuthRequest refreshRequest = new InitiateRefreshTokenAuthRequest()
            {               
                AuthFlowType = AuthFlowType.REFRESH_TOKEN_AUTH
            };

            AuthFlowResponse authResponse = await user.StartWithRefreshTokenAuthAsync(refreshRequest).ConfigureAwait(false);`
}

        private string GetSecretHash(string value)
        {
            var key = _cognitoCredentials.Secret;
            using (var hmacsha256 = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
            {
                var hash = hmacsha256.ComputeHash(Encoding.UTF8.GetBytes(value));
                return Convert.ToBase64String(hash);
            }
        }

Also tried with the same result:

       public async void GetCredsFromRefreshAsync(string refreshToken, string accessToken, string idToken, string userName, string userId)
        {
            var secretHash = GetSecretHash(userId + _cognitoCredentials.AppClientId);
            CognitoUserPool userPool = new CognitoUserPool(_cognitoCredentials.UserPoolId, _cognitoCredentials.AppClientId, _provider);
            CognitoUser user = new CognitoUser(userName, _cognitoCredentials.AppClientId, userPool, _provider);

            user.SessionTokens = new CognitoUserSession(idToken, accessToken, refreshToken, DateTime.Now, DateTime.Now.AddHours(1));

            var refreshReq = new InitiateAuthRequest();
            refreshReq.ClientId = _cognitoCredentials.AppClientId;

            refreshReq.AuthFlow = AuthFlowType.REFRESH_TOKEN_AUTH;
            refreshReq.AuthParameters.Add("SECRET_HASH", secretHash);
            refreshReq.AuthParameters.Add("REFRESH_TOKEN", refreshToken);

            var clientResp = await _provider.InitiateAuthAsync(refreshReq).ConfigureAwait(false);

            InitiateRefreshTokenAuthRequest refreshRequest = new InitiateRefreshTokenAuthRequest()
            {
                AuthFlowType = AuthFlowType.REFRESH_TOKEN_AUTH
            };
        }

Please help.

Expected Behavior

Get Refresh Token without error: NotAuthorizedException: SecretHash does not match for the client

Current Behavior

Cannot get Refresh Token, error occurs: NotAuthorizedException: SecretHash does not match for the client

Reproduction Steps

Use this code:

public async void GetCredsFromRefreshAsync_(string refreshToken, string accessToken, string idToken, string userName, string userId)
{
  var secretHash = GetSecretHash(userId + _cognitoCredentials.AppClientId);
            CognitoUserPool userPool = new CognitoUserPool(_cognitoCredentials.UserPoolId, _cognitoCredentials.AppClientId, _provider, _cognitoCredentials.Secret);
            CognitoUser user = new CognitoUser(userName, _cognitoCredentials.AppClientId, userPool, _provider, _cognitoCredentials.Secret);
            user.SessionTokens = new CognitoUserSession( null, null, refreshToken, DateTime.Now, DateTime.Now.AddHours(1));
            InitiateRefreshTokenAuthRequest refreshRequest = new InitiateRefreshTokenAuthRequest()
            {               
                AuthFlowType = AuthFlowType.REFRESH_TOKEN_AUTH
            };

            AuthFlowResponse authResponse = await user.StartWithRefreshTokenAuthAsync(refreshRequest).ConfigureAwait(false);`
}

        private string GetSecretHash(string value)
        {
            var key = _cognitoCredentials.Secret;
            using (var hmacsha256 = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
            {
                var hash = hmacsha256.ComputeHash(Encoding.UTF8.GetBytes(value));
                return Convert.ToBase64String(hash);
            }
        }

Possible Solution

Please give me information, what is wrong with my code, or maybe there is some bug?

Additional Information/Context

No response

AWS .NET SDK and/or Package version used

Amazon.Extensions.CognitoAuthentication 2.2.1 Unity 2021.3.9f1

Targeted .NET Platform

.Net 4.x

Operating System and version

Windows 11

ashishdhingra commented 1 year ago

Hi @TomaszWozniakMosina,

Good morning.

Please have a look at the code in comment https://github.com/aws/aws-sdk-net-extensions-cognito/issues/35#issuecomment-712406376 for guidance. The _cognitoCredentials.Secret in your code should be pointing to the value of secret configured in App Client.

(Just FYI, I do not see you using secretHash, so that might be redundant)

Thanks, Ashish

LoopIssuer commented 1 year ago

Hi @ashishdhingra Good morning. Thanks for your reply. However, when I use the code form https://github.com/aws/aws-sdk-net-extensions-cognito/issues/35#issuecomment-712406376 I still get error: SecretHash does not match for the client: xxxxxxxxxxxxxxxxxxx.

I tried use SecretHash computed and not computed with the below method - always get same error.

        private string GetSecretHash(string value)
        {
            var key = _cognitoCredentials.Secret;
            using (var hmacsha256 = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
            {
                var hash = hmacsha256.ComputeHash(Encoding.UTF8.GetBytes(value));
                return Convert.ToBase64String(hash);
            }
        }
ashishdhingra commented 1 year ago

@TomaszWozniakMosina I'm unsure what you are doing wrong. The below customized code works properly (using Amazon.Extensions.CognitoAuthentication 2.2.3 on .NET Framework 4.8):

NOTE: The code below uses FallbackRegionFactory.GetRegionEndpoint() to determine region. You might want to set region value instead if the region is not configured in the default profile chain.

using Amazon;
using Amazon.CognitoIdentityProvider;
using Amazon.Extensions.CognitoAuthentication;
using Amazon.Runtime;
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace CognitoRefreshTokenTest
{
    internal class Program
    {
        private static string userPoolId;
        private static string clientId;
        private static string clientSecret;

        const int BufferSize = 10000;

        static void Main(string[] args)
        {
            Console.SetIn(new StreamReader(Console.OpenStandardInput(), Encoding.UTF8, false, BufferSize));

            Console.Write("Enter User Pool ID: ");
            userPoolId = Console.ReadLine();
            Console.Write("Enter Client ID: ");
            clientId = Console.ReadLine();
            Console.Write("Enter Client Secret: ");
            clientSecret = Console.ReadLine();

            bool reexecuteFlow = false;

            do
            {
                Console.Write("User Name: ");
                string userName = Console.ReadLine();

                while (string.IsNullOrWhiteSpace(userName))
                {
                    Console.Write("Please enter a valid User Name: ");
                    userName = Console.ReadLine();
                }

                Console.Write("Do you have a Refresh Token (Y/N): ");
                char hasRefreshTokenResponse = Convert.ToChar(Console.ReadLine());
                bool hasRefreshToken = (char.ToLower(hasRefreshTokenResponse) == 'y');
                Console.WriteLine();

                string passwordOrRefreshToken;

                if (!hasRefreshToken)
                {
                    Console.Write("Password: ");
                    passwordOrRefreshToken = Console.ReadLine();

                    while (string.IsNullOrWhiteSpace(passwordOrRefreshToken))
                    {
                        Console.Write("Please enter a valid Password: ");
                        passwordOrRefreshToken = Console.ReadLine();
                    }
                }
                else
                {
                    Console.Write("Existing Refresh Token: ");
                    passwordOrRefreshToken = Console.ReadLine();

                    while (string.IsNullOrWhiteSpace(passwordOrRefreshToken))
                    {
                        Console.Write("Please enter a valid Refresh Token: ");
                        passwordOrRefreshToken = Console.ReadLine();
                    }
                }

                Console.WriteLine("\nExecuting {0} auth flow.", (hasRefreshToken ? "Refresh Token" : "Username/Password"));
                AuthFlowResponse authFlowResponse = (hasRefreshToken ? GetCredsFromRefreshAsync(userName, passwordOrRefreshToken).GetAwaiter().GetResult() : GetCredentials(userName, passwordOrRefreshToken).GetAwaiter().GetResult());
                Console.WriteLine("ID Token: {0}\nAccess Token {1}\nRefresh Token: {2}", authFlowResponse.AuthenticationResult.IdToken, authFlowResponse.AuthenticationResult.AccessToken, authFlowResponse.AuthenticationResult.RefreshToken);

                Console.Write("\n\nRe-execute Flow (Y/N): ");
                char reexecuteFlowResponse = Convert.ToChar(Console.ReadLine());
                reexecuteFlow = (char.ToLower(reexecuteFlowResponse) == 'y');
            } while (reexecuteFlow);
        }

        public static async Task<AuthFlowResponse> GetCredentials(string userName, string password)
        {
            var provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), FallbackRegionFactory.GetRegionEndpoint());
            var userPool = new CognitoUserPool(userPoolId, clientId, provider, clientSecret);
            var user = new CognitoUser(userName, clientId, userPool, provider, clientSecret);

            AuthFlowResponse authResponse = await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest()
            {
                Password = password
            }).ConfigureAwait(false);

            while (authResponse.AuthenticationResult == null)
            {
                if (authResponse.ChallengeName == ChallengeNameType.NEW_PASSWORD_REQUIRED)
                {
                    Console.WriteLine("Enter your desired new password: ");
                    string newPassword = Console.ReadLine();

                    authResponse = await user.RespondToNewPasswordRequiredAsync(new RespondToNewPasswordRequiredRequest()
                    {
                        SessionID = authResponse.SessionID,
                        NewPassword = newPassword
                    });
                }
                else if (authResponse.ChallengeName == ChallengeNameType.SMS_MFA)
                {
                    Console.WriteLine("Enter the MFA Code sent to your device: ");
                    string mfaCode = Console.ReadLine();

                    authResponse = await user.RespondToSmsMfaAuthAsync(new RespondToSmsMfaRequest()
                    {
                        SessionID = authResponse.SessionID,
                        MfaCode = mfaCode

                    }).ConfigureAwait(false);
                }
                else
                {
                    Console.WriteLine("Unrecognized authentication challenge.");
                    return null;
                }
            }

            return authResponse;
        }

        public static async Task<AuthFlowResponse> GetCredsFromRefreshAsync(string userName, string refreshToken)
        {
            AmazonCognitoIdentityProviderClient provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), FallbackRegionFactory.GetRegionEndpoint());
            CognitoUserPool userPool = new CognitoUserPool(userPoolId, clientId, provider, clientSecret);

            CognitoUser user = new CognitoUser(userName, clientId, userPool, provider, clientSecret);

            user.SessionTokens = new CognitoUserSession(null, null, refreshToken, DateTime.Now, DateTime.Now.AddHours(1));

            InitiateRefreshTokenAuthRequest refreshRequest = new InitiateRefreshTokenAuthRequest()
            {
                AuthFlowType = AuthFlowType.REFRESH_TOKEN_AUTH
            };

            return await user.StartWithRefreshTokenAuthAsync(refreshRequest).ConfigureAwait(false);
        }
    }
}

Output:

Enter User Pool ID: us-east-2_<<removed>>
Enter Client ID: 22<<removed>>
Enter Client Secret: gu8m<<removed>>
User Name: testuser
Do you have a Refresh Token (Y/N): n

Password: 14@Dec@1983
Executing Username/Password auth flow.
ID Token: eyJraWQiOiJ6cG9HODdOOHQ4XC9mMFp6cE<<removed>>
Access Token eyJraWQiOiI5Z0I4dzRUNFdrMWRMdThcL2U0<<removed>>
Refresh Token: eyJjdHkiOiJKV1QiLCJlbmMiOiJB<<removed>>

Re-execute Flow (Y/N):
y
User Name: testuser
Do you have a Refresh Token (Y/N): y

Existing Refresh Token: eyJjdHkiOiJKV1QiLCJlbmMiOiJB<<removed>>
Executing Refresh Token auth flow.
ID Token: eyJraWQiOiJ6cG9HODdOOHQ4XC9mMFp6cEJySVFXa<<removed>>
Access Token eyJraWQiOiI5Z0I4dzRUNFdrMWRMdThcL2U0THJsVGN<<removed>>
Refresh Token: eyJjdHkiOiJKV1QiLCJlbmMiOiJB<<removed>>

Re-execute Flow (Y/N):

You need to use the value of client secret configured in App Client from Cognito user pool console (refer below as an example):

Screen Shot 2022-10-04 at 10 37 00 AM

Thanks, Ashish

github-actions[bot] commented 1 year ago

This issue has not received a response in 5 days. If you want to keep this issue open, please just leave a comment below and auto-close will be canceled.