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 50 forks source link

Invalid Refresh Token when using Refresh Token with Device Tracking #85

Closed Welchen closed 2 years ago

Welchen commented 2 years ago

I followed the examples for Authentication and I was able to get it to retrieve an access token and refresh token. When trying to use toe refresh token to reauthenticate, it is failing if I have device tracking turned on. I came across #75 and it looks like someone else had the same issue but never replied with their result from testing. Seems to still be broken if Device Tracking is turned on and returns Amazon.CognitoIdentityProvider.Model.NotAuthorizedException: Invalid Refresh Token.

ashishdhingra commented 2 years ago

Appears to be an issue which is not yet fixed (e.g. https://github.com/aws/aws-sdk-net-extensions-cognito/issues/73). This needs to be prioritized by the team.

Welchen commented 2 years ago

@ashishdhingra thank you for the response. This issue was logged about 8 months ago, how does one get it to be prioritized?

Welchen commented 2 years ago

Really curious if this is being worked on or if it will be worked on?

erikkanderson commented 2 years ago

I had a similar issue that is described here and in #75. In my case, our user pool is tracking devices. To get the Refresh Token to re-authenticate, I can to confirm the device id first, and attach it to the user, before calling .StartWithRefreshTokenAuthAsync. Here is a sample of the code I used with comments.

` public async Task LoginTest() {

        //See:https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/cognito-authentication-extension.html
        //See: https://github.com/aws/aws-sdk-net-extensions-cognito#authenticating-with-secure-remote-protocol-srp

        //Create anonymous credentials to create a Cognito Identify Provider
        AmazonCognitoIdentityProviderClient provider =
            new AmazonCognitoIdentityProviderClient( new Amazon.Runtime.AnonymousAWSCredentials() );

        //Set up the initial request when we don't have a refresh token. Obviously assumes an existing user.
        CognitoUserPool userPool = new CognitoUserPool( poolID, clientID, provider );
        CognitoUser user = new CognitoUser( username, clientID, userPool, provider );
        //This is the only time we need the user's password (assuming the soon to be obtained refresh token stays valid)
        InitiateSrpAuthRequest authRequest = new InitiateSrpAuthRequest() {
            Password = password
        };

        //Send the request to Cognito for authentication
        AuthFlowResponse authResponse = await user.StartWithSrpAuthAsync( authRequest ).ConfigureAwait( false );

        var accessToken = authResponse.AuthenticationResult.AccessToken;
        var refreshToken = authResponse.AuthenticationResult.RefreshToken;
        var deviceKey = authResponse.AuthenticationResult.NewDeviceMetadata.DeviceKey;

        //Now we can get AWS Credentials for the user
        CognitoAWSCredentials credentials =
            user.GetCognitoAWSCredentials( identityPoolID, RegionEndpoint.USEast1 );

        //Above, when we made the StartWithSrpAuthAsync Call, the call also generates a temporary 'Device'
        //Now need to tell Cognito to confirm this temporary device.

        //Device Verification happens locally. the Salt and password verifier gets sent to Cognito in the next .ConfirmDeviceAsync() call
        var deviceVerifier = user.GenerateDeviceVerifier(
            authResponse.AuthenticationResult.NewDeviceMetadata.DeviceGroupKey,
            myRandomDevicePassword,
            username );

        ConfirmDeviceResponse confirmDeviceResponse = await user.ConfirmDeviceAsync(
            accessToken,
            deviceKey,
            "Bob", //this is as creative as I got in naming my device
            deviceVerifier.PasswordVerifier,
            deviceVerifier.Salt );

        //Now attach the newly attached Device to the user. We need this step to successfully complete the StartWithRefreshTokenAuthAsync() call
        //NOTE There may be a better way to do this, but method here is to first create a generic Device, with the known 
        //device key. Then Use GetDeviceAsync() to pull the real details from Cognito
        CognitoDevice device = new CognitoDevice(
            deviceKey,
            new Dictionary<string, string>(),
            DateTime.Today,
            DateTime.Today,
            DateTime.Today,
            user );
        await device.GetDeviceAsync();
        user.Device = device;

        //Now pretend we need to fast foward in time and refresh the tokens

        //See: https://github.com/aws/aws-sdk-net-extensions-cognito#authenticating-using-a-refresh-token-from-a-previous-session

        //I'm actually not sure what this step does ..
        user.SessionTokens = new CognitoUserSession( null, null, refreshToken, DateTime.UtcNow, DateTime.UtcNow.AddHours( 1 ) );

        //Create the refresh request object
        InitiateRefreshTokenAuthRequest refreshRequest = new InitiateRefreshTokenAuthRequest() {
            AuthFlowType = AuthFlowType.REFRESH_TOKEN_AUTH
        };

        //CAll Cognito to refresh the token
        AuthFlowResponse authResponse2 = await user.StartWithRefreshTokenAuthAsync( refreshRequest ).ConfigureAwait( false );

        //Now we have a new accessToken and a new refreshToken, both of which need to be re-saved
        var accessToken2 = authResponse2.AuthenticationResult.AccessToken;
        var refreshToken2 = authResponse2.AuthenticationResult.RefreshToken;

        //Again, we can get new credentials for signing API requests
        CognitoAWSCredentials credentials2 =
            user.GetCognitoAWSCredentials( identityPoolID, RegionEndpoint.USEast1 );

        var restAPICredntials2 = await GetRestAPICredentials( user.SessionTokens.IdToken );
    }

`

dscpinheiro commented 2 years ago

Hi @Welchen,

Sorry for the delay in the response, but we're investigating this issue now.

The Invalid Refresh Token message seems to be happening because of this method: https://github.com/aws/aws-sdk-net-extensions-cognito/blob/36719bcdf5bd99e8220c07cfdb049cfc6334accf/src/Amazon.Extensions.CognitoAuthentication/CognitoUserAuthentication.cs#L586-L615

It checks for a device key, but the documentation (https://github.com/aws/aws-sdk-net-extensions-cognito#authenticating-using-a-refresh-token-from-a-previous-session) does not mention that at all.

I've tried this (attaching the device to the user before initiating the REFRESH_TOKEN_AUTH and it returns a valid AccessToken:

using var provider = new AmazonCognitoIdentityProviderClient();
var userPool = new CognitoUserPool(POOL_ID, CLIENT_ID, provider);

var user = new CognitoUser(USER_NAME, CLIENT_ID, userPool, provider, CLIENT_SECRET)
{
    SessionTokens = new CognitoUserSession(null, null, refreshToken, DateTime.UtcNow, DateTime.UtcNow.AddHours(1))
};
user.Device = new CognitoDevice(new DeviceType { DeviceKey = deviceKey }, user);

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

Console.WriteLine(authResponse.AuthenticationResult.AccessToken);

Can you let me know if that works for you? If it does, we'll update the documentation to mention there's an extra step when device tracking is enabled.

erikkanderson commented 2 years ago

Yes, attaching the device before initiating the refresh token works on our end. My apologizes for late response.

github-actions[bot] commented 2 years 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.