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

Refresh Token error - SecretHash does not match for the client #122

Closed w5922xd closed 1 year ago

w5922xd commented 1 year ago

Describe the bug

When calling StartWithRefreshTokenAuthAsync, I am receiving an error Amazon.CognitoIdentityProvider.Model.NotAuthorizedException: 'SecretHash does not match for the client: xxxxxxxxxxxx.

I have double-checked the calculation of the SecretHash and it looks correct for me. When I use the SRP AuthFlow it works just fine using the exact same SecretHash value. I captured the web traffic for the POST request sent to the api and the request syntax matches the InitiateAuth documentation.

I can see that others have reported this problem in this repo here and here. I looked through it and used this code to try and reproduce the bug. I get the same error regarding the SecretHash.

Expected Behavior

I am expecting a response from the API.

Current Behavior

I am receiving an error Amazon.CognitoIdentityProvider.Model.NotAuthorizedException: 'SecretHash does not match for the client: xxxxxxxxxxxx.

Reproduction Steps

I looked through it and used this code to try and reproduce the bug. I get the same error regarding the SecretHash.

Possible Solution

No response

Additional Information/Context

I also have seen a comment on stack overflow that instead of user email being used in the secret hash you actually need to use the sub value. In this case the SecretHash value differs from the SRP auth flow but the result is the same: 'SecretHash does not match for the client:`.

AWS .NET SDK and/or Package version used

Amazon.Extensions.CognitoAuthentication v2.4.2

Targeted .NET Platform

.net6

Operating System and version

Windows 10

ashishdhingra commented 1 year ago

@w5922xd Good morning. Thanks for reporting the issue. Could you please share the following:

I will try recreating to issue using to code shared in https://github.com/aws/aws-sdk-net-extensions-cognito/issues/94#issuecomment-1267336763 to rule out any regression.

EDIT: Unfortunately, I'm unable to reproduce the issue using code mentioned in https://github.com/aws/aws-sdk-net-extensions-cognito/issues/94#issuecomment-1267336763. I'm unsure if you have Client Secret configured for you app client. BTW, to compute secret hash, this library uses the logic mentioned here.

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.

w5922xd commented 1 year ago

Hello @ashishdhingra. Apologies for the delayed response.

Here is my output:

Enter User Pool ID: us-west-2_<<removed>>
Enter Client ID: 2nm<<removed>>
Enter Client Secret: c7iq<<removed>>
User Name: <<removed>>
Do you have a Refresh Token (Y/N): n

Password: <<removed>>

Executing Username/Password auth flow.
ID Token: <<removed>>
Access Token <<removed>>
Refresh Token: <<removed>>

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

Existing Refresh Token: eyJjd<<removed>>

Executing Refresh Token auth flow.

I then error with this exception: Amazon.CognitoIdentityProvider.Model.NotAuthorizedException: 'SecretHash does not match for the client: 2nm<<removed>>.

At the moment I am just reading them in from a file and/or setting them in the code because I'm just trying to prototype a working example. I have no issues with SRP authflow and it uses the same SecretHash.

Coginto user pool sign-in options: User name, Email Multi-factor authentication: Optional MFA (not currently configured for my accounts) Self-service account recovery: Enabled

ashishdhingra commented 1 year ago

@w5922xd The code shared in https://github.com/aws/aws-sdk-net-extensions-cognito/issues/94#issuecomment-1267336763 works fine at my end. I suspect this has to do something with your user pool and/or app client settings. Please share the screenshots of the settings (masking the sensitive details) and the sample code solution to reproduce the issue.

Thanks, Ashish

w5922xd commented 1 year ago

Hello @ashishdhingra and thanks again for looking at this problem. In the process of gathering these screenshots I believe we've uncovered our solution.

We have been logging in with our email address. This works just fine for SRP Auth flow and we get our Id, access, and refresh token. However, to redeem that refresh token, we cannot use the email address. My mistake was thinking our username and email address are the same credential since it seemed to be working fine for authentication.

I now see this isn't true, that either email or username are acceptable for SRP auth but NOT for the refresh token. After making this realization I am now able to use the refresh token and exchange it for a new set of Id, access, and refresh tokens.

I appreciate your time spent working with me on this issue with me and apologize for any time wasted. Hopefully something within this issue will be useful to someone else. Have a great weekend.

github-actions[bot] commented 1 year ago

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see. If you need more assistance, please either tag a team member or open a new issue that references this one. If you wish to keep having a conversation with other community members under this issue feel free to do so.

darcy-vitacca commented 10 months ago

@w5922xd running into this issue, any chance you could provide code of how you are manually refreshing the token?

w5922xd commented 10 months ago

@darcy-vitacca I can give it a shot. I was tasked with prototyping cognito in a WPF application so this code is slapped together to show proof of concept. It might help though.

Authenticating first with srp flow using an email address:

public async Task<AuthFlowResponse> AuthenticateWithSrpAsync()
{            
    var userPool = new CognitoUserPool(UserPoolId, ClientId, _provider);
    var user = new CognitoUser(_username, ClientId, userPool, _provider, ClientSecret);

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

Then getting user attributes so I can find the actual username. This returns a tuple but the first value is the username we need to use for the refresh token.

public async Task<(string Username, List<AttributeType> Attributes)> GetUserAttributesAsync(string accessToken)
{
    var getUserRequest = new GetUserRequest()
    {
        AccessToken = accessToken
    };
    var getUserResponse = await _provider.GetUserAsync(getUserRequest);
    return (getUserResponse.Username, getUserResponse.UserAttributes);
}

Finally, use the refresh token. We're going to pass in the username (the value from getUserResponse.Username) as a param.

public async Task<AuthFlowResponse> GetCredsFromRefreshAsync(string userId, string refreshToken)
{
    var userPool = new CognitoUserPool(UserPoolId, ClientId, _provider);

    var user = new CognitoUser(userId, ClientId, userPool, _provider, ClientSecret)
    {
        SessionTokens = new CognitoUserSession(null, null, refreshToken, DateTime.UtcNow, DateTime.UtcNow.AddHours(1))
    };

    var authResponse = await user.StartWithRefreshTokenAuthAsync(new InitiateRefreshTokenAuthRequest
    {
        AuthFlowType = AuthFlowType.REFRESH_TOKEN_AUTH
    });
    return authResponse;
}

Hope that helps.

darcy-vitacca commented 10 months ago

@w5922xd Thanks for getting back to me much appreciated