Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue using Refresh Token flow #75

Closed
BenWoodford opened this issue Jun 20, 2021 · 21 comments
Closed

Issue using Refresh Token flow #75

BenWoodford opened this issue Jun 20, 2021 · 21 comments
Labels
bug This issue is a bug. duplicate This issue is a duplicate. module/cognito-ext

Comments

@BenWoodford
Copy link

BenWoodford commented Jun 20, 2021

I'm using the snippet from this flow and can successfully retrieve an access token and refresh token from the AuthenticationResult value, but upon saving the refresh token and putting it back through the aforementioned snippet I get Invalid Refresh Token as a response.

Am I missing some key AWS-side config setting here or something like that? Is the client secret required for refresh keys maybe? (there isn't one set currently)

This is using the SDK 3.7.57.0 and Extensions 2.21 in Unity

@BenWoodford BenWoodford added guidance Question that needs advice or information. needs-triage This issue or PR still needs to be triaged. labels Jun 20, 2021
@ashishdhingra
Copy link
Contributor

Hi @BenWoodford,

Good morning.

Thanks for posting guidance question.

Please refer the below working code sample that has capability to use RefreshToken. Kindly note that this is a sample (console) application and you might want to move the secrets to a configuration file. You should have the correct username that exists in CognitoUserPool to use the RefreshToken. Kindly note that this sample uses Amazon.Extensions.CognitoAuthentication package.

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

namespace CognitoStartWithRefreshTokenAuthAsync
{
    class Program
    {
        private static string userPoolId = "<<userpool_id>>";
        private static string clientId = "<<client_id>>";
        private static string clientSecret = "<<client_secret>>";
        private static RegionEndpoint regionEndpoint = RegionEndpoint.USEast2; // Change the region appropriately. Credentials are loaded from default profile.

        static void Main(string[] args)
        {
            Console.WriteLine("User Name: ");
            string userName = Console.ReadLine();
            while (string.IsNullOrWhiteSpace(userName))
            {
                Console.WriteLine("Please enter a valid User Name.");
                userName = Console.ReadLine();
            }

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

            Console.WriteLine("Password: ");
            string password = Console.ReadLine();
            while (string.IsNullOrWhiteSpace(password) && !hasRefreshToken)
            {
                Console.WriteLine("Please enter a valid Password.");
                password = Console.ReadLine();
            }

            Console.WriteLine("Existing Refresh Token: ");
            string refreshToken = Console.ReadLine();
            while (string.IsNullOrWhiteSpace(refreshToken) && hasRefreshToken)
            {
                Console.WriteLine("Please enter a valid Refresh Token.");
                refreshToken = Console.ReadLine();
            }

            AuthFlowResponse authFlowResponse = (!string.IsNullOrWhiteSpace(refreshToken) ? GetCredsFromRefreshAsync(userName, refreshToken).GetAwaiter().GetResult() : GetCredentials(userName, password).GetAwaiter().GetResult());
        }

        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);
        }
    }
}

NOTE: In case you app client has client secret as well, you would need to pass them to CognitoUserPool and CognitoUser constructors.

This sample takes guidance from Amazon CognitoAuthentication Extension Library Examples. Hope this helps.

Thanks,
Ashish

@ashishdhingra ashishdhingra added response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. and removed needs-triage This issue or PR still needs to be triaged. labels Jun 21, 2021
@BenWoodford
Copy link
Author

BenWoodford commented Jun 23, 2021

So just to confirm, there's no server-side settings I should be using, no requirement to use a client secret, etc? I just need a refresh token and a username.

@ashishdhingra
Copy link
Contributor

So just to confirm, there's no server-side settings I should be using, no requirement to use a client secret, etc? I just need a refresh token and a username.

@BenWoodford You are not required to configure client secret for an app client. If you configured client secret for app client, then you need to would need to pass them to CognitoUserPool and CognitoUser constructors. Please try using the above sample code for testing and let me know if it works for you.

@ashishdhingra ashishdhingra added response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. and removed response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. labels Jun 23, 2021
@github-actions
Copy link

github-actions bot commented Jul 1, 2021

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

@github-actions github-actions bot added the closing-soon This issue will automatically close in 4 days unless further comments are made. label Jul 1, 2021
@BenWoodford
Copy link
Author

Keeping open until I have a chance to properly test

(it also hasn’t been two weeks…?)

@github-actions github-actions bot removed closing-soon This issue will automatically close in 4 days unless further comments are made. response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. labels Jul 2, 2021
@ashishdhingra ashishdhingra added the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Jul 2, 2021
@BenWoodford
Copy link
Author

Unfortunately I still get an Invalid Refresh Token error with that code.

To confirm, I'm getting the token from response.AuthenticationResult.RefreshToken, is this correct?

@github-actions github-actions bot removed the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Jul 6, 2021
@ashishdhingra
Copy link
Contributor

@BenWoodford In case you app client has client secret as well, you would need to pass them to CognitoUserPool and CognitoUser constructors.

@ashishdhingra ashishdhingra added the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Jul 7, 2021
@BenWoodford
Copy link
Author

@BenWoodford In case you app client has client secret as well, you would need to pass them to CognitoUserPool and CognitoUser constructors.

There’s no client secret (presumably username/password login wouldn’t work in that case anyway?)

@github-actions github-actions bot removed the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Jul 8, 2021
@ashishdhingra
Copy link
Contributor

@BenWoodford In case you app client has client secret as well, you would need to pass them to CognitoUserPool and CognitoUser constructors.

There’s no client secret (presumably username/password login wouldn’t work in that case anyway?)

@BenWoodford Thanks for your persistence. It appears that you are affected by the issue mentioned in #77 in Amazon.Extensions.CognitoAuthentication 2.2.1. Please use the below workaround for using refresh token for now (notice the use of DateTime.Now.AddHours(1).ToUniversalTime() for expiration time):

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).ToUniversalTime());

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

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

There is another related issue #76 which states that refresh token auth flow should not check for expiration time of access token.

Once the fix for #77 and #76 is implemented, I will update here accordingly. This is not longer a guidance issue and I will change the label to bug.

Thanks,
Ashish

@ashishdhingra ashishdhingra added bug This issue is a bug. and removed guidance Question that needs advice or information. labels Jul 8, 2021
@BenWoodford
Copy link
Author

@ashishdhingra thanks, I tried that out but still no avail unfortunately. Same "Invalid Refresh Token" error

To confirm, Cognito's Client settings on the AWS Console shows "(no client secret)" - so presumably this means there is definitely not one set?

To double-check, you don't need a client secret to use a refresh token do you?

@ashishdhingra
Copy link
Contributor

@ashishdhingra thanks, I tried that out but still no avail unfortunately. Same "Invalid Refresh Token" error

To confirm, Cognito's Client settings on the AWS Console shows "(no client secret)" - so presumably this means there is definitely not one set?

To double-check, you don't need a client secret to use a refresh token do you?

@BenWoodford No, you do not need client secret. To troubleshoot, could you please share screenshot of user pool and app client settings (with sensitive information masked). It works perfectly fine at my end.

@BenWoodford
Copy link
Author

BenWoodford commented Jul 8, 2021

Will do tomorrow morning, thanks!

Is there an email address I can send that to instead though? Just to avoid any information mishaps as it’s a client’s AWS account.

@ashishdhingra
Copy link
Contributor

Will do tomorrow morning, thanks!

Is there an email address I can send that to instead though? Just to avoid any information mishaps as it’s a client’s AWS account.

@BenWoodford You can upload the screenshots here. However, please make sure that you mask out sensitive information.

@ashishdhingra ashishdhingra added the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Jul 9, 2021
@BenWoodford
Copy link
Author

image

image

image

image

@BenWoodford
Copy link
Author

BenWoodford commented Jul 10, 2021

I've narrowed this down: if I disable device tracking I can use the refresh token, so the endpoint error isn't terribly helpful at all as the token itself is fine.

However there's no documentation on how I ensure the device key is provided for the refresh flow, I've tried creating a device like the below using a remembered device key but it still returns an invalid refresh token:

        user.Device = new CognitoDevice(new Amazon.CognitoIdentityProvider.Model.DeviceType()
        {
            DeviceKey = deviceKey
        }, user);

As a side question, what is the appropriate way to handle refresh tokens and the user not being connected to the internet (in the case of a mobile app that isn't currently online)? Should I just assume that the refresh token is okay and let them use the app offline, or should I be tracking when I acquired the token? Given it doesn't seem to refresh when authenticating with a refresh token (or does using it extend the expiration?) I can't really go "Oh the expiration is up, I'll just log them out" as that would always be 30 days after the last user/pass auth

@github-actions github-actions bot removed the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Jul 11, 2021
@ashishdhingra
Copy link
Contributor

Hi @BenWoodford,

The refresh toke auth flow fix was implemented in Amazon.Extensions.CognitoAuthentication 2.2.2. Please verify if your scenario is working now in the latest version.

Thanks,
Ashish

@ashishdhingra ashishdhingra added the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Jul 15, 2021
@BenWoodford
Copy link
Author

Will do if I get a chance, though I’ve since switched Hosted UI and talking to the OAuth2 refresh token endpoint directly which seems to be working without any problems thankfully.

However while I’m here: how does one refresh a refresh token…? As otherwise after 30 days it’s just going to log the user out, but you don’t get new refresh tokens when doing refresh login

@ashishdhingra
Copy link
Contributor

ashishdhingra commented Jul 16, 2021

@BenWoodford If the refresh token is expired, your app user must re-authenticate by signing in again to your user pool. You may find more details on Using the Refresh Token.

There are multiple open issues related to device tracking #73, #28 and #10. Since the refresh token auth flow works for you with device tracking disabled and you have a workaround, I would close this issue as duplicate of these other issues. I would see how I can get these issues prioritized by development team.

@ashishdhingra ashishdhingra added response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. duplicate This issue is a duplicate. and removed response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. labels Jul 16, 2021
@github-actions
Copy link

⚠️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.

@Welchen
Copy link

Welchen commented Feb 3, 2022

@ashishdhingra I'm running into this same issue and as I see that the original poster never confirmed that the issue was fixed. When device tracking is turned on, the user of a refresh token is failing. I don't see any documentation about what to do in this scenario if it does even work.

@LoopIssuer
Copy link

LoopIssuer commented Oct 3, 2022

Hi,
I had a similar issue as mentioned above, but 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This issue is a bug. duplicate This issue is a duplicate. module/cognito-ext
Projects
None yet
Development

No branches or pull requests

4 participants