Skip to content

The same SecretHash does not match for the client when refreshing the token.

0

Hi everyone,

I am working on implementing AWS Cognito authentication in my project and I encountered an issue while using the refresh token flow. Here's the breakdown of the problem:

Sign-In Flow

I first attempt to sign in using the USER_PASSWORD_AUTH flow with the following request: { "email": "user_email", "password": "string" }

My calculate_secret_hash and sign-in functions:

def calculate_secret_hash(self, email: EmailStr) -> str:
        message = email + settings.cognito_client_id
        secret = settings.cognito_client_secret.encode('utf-8')
        message = message.encode('utf-8')

        dig = hmac.new(secret, message, hashlib.sha256)
        return base64.b64encode(dig.digest()).decode()
async def signin(self, data: SignInRequest):
        try:
            secret_hash = self.calculate_secret_hash(data.email)
            response = self.cognito_client.initiate_auth(
                AuthFlow="USER_PASSWORD_AUTH",
                AuthParameters={
                    "USERNAME": data.email,
                    "PASSWORD": data.password,
                    "SECRET_HASH": secret_hash
                },
                ClientId=settings.cognito_client_id
            )
            return {
                "access_token": response["AuthenticationResult"]["AccessToken"],
                "refresh_token": response["AuthenticationResult"]["RefreshToken"],
                "id_token": response["AuthenticationResult"]["IdToken"]
            }

        except ClientError as create_error:
            raise HTTPException(status_code=500, detail=f"Error creating user: {str(create_error)}")

The sign-in flow works correctly and Cognito successfully returns the following tokens: access_token, refresh_token, id_token

After receiving the tokens from the sign-in process, I try to use the refresh_token to get new tokens by sending the following request: { "email": "user_email", "refresh_token": "<refresh_token_received>" }

My refresh_token function:

async def refresh_token(self, email: EmailStr, refresh_token: str):
        try:
            secret_hash = self.calculate_secret_hash(email)
            response = self.cognito_client.initiate_auth(
                AuthFlow='REFRESH_TOKEN_AUTH',
                AuthParameters={
                    'REFRESH_TOKEN': refresh_token,
                    'SECRET_HASH': secret_hash
                },
                ClientId=settings.cognito_client_id,
            )
            return {
                "access_token": response["AuthenticationResult"]["AccessToken"],
                "refresh_token": response["AuthenticationResult"]["RefreshToken"],
                "id_token": response["AuthenticationResult"]["IdToken"]
            }
        except ClientError as refresh_error:
            return refresh_error

However, I am getting the following error during the refresh token flow:

NotAuthorizedException: An error occurred (NotAuthorizedException) when calling the InitiateAuth operation: SecretHash does not match for the client: <client_id>.

Details:

I am using the boto3 version: 1.35.63.

My OS is Windows.

The client_secret and cognito_client_id are correct and match the values configured in the Amazon Cognito console for the app client.

I would appreciate it if anyone could provide insights on:

  • What could be causing the same SecretHash does not match error when refreshing the token, even though it works correctly during the sign-in flow.

Thanks in advance!

2 Answers
0

The issue you're experiencing with the SecretHash not matching during the refresh token flow, despite working correctly during sign-in, is likely due to how you're calculating the SecretHash for the refresh token request.

In your refresh_token function, you're calculating the SecretHash using only the email and client ID. However, for the REFRESH_TOKEN_AUTH flow, the SecretHash should be calculated differently.

For the refresh token flow, you should use the USERNAME parameter in place of the email when calculating the SecretHash. The USERNAME in this case should be the actual username of the user, which might be different from their email address depending on how your user pool is configured.

To fix this, you should modify your refresh_token function to calculate the SecretHash using the username instead of the email. You may need to retrieve the username associated with the refresh token before making the refresh token request.

Additionally, make sure that you're including the CLIENT_ID in your AuthParameters for the refresh token request. Your current implementation doesn't include it, which might also contribute to the error.

Here's a suggested modification to your refresh_token function:

async def refresh_token(self, username: str, refresh_token: str):
    try:
        secret_hash = self.calculate_secret_hash(username)
        response = self.cognito_client.initiate_auth(
            AuthFlow='REFRESH_TOKEN_AUTH',
            AuthParameters={
                'REFRESH_TOKEN': refresh_token,
                'SECRET_HASH': secret_hash,
                'USERNAME': username,
                'CLIENT_ID': settings.cognito_client_id
            },
            ClientId=settings.cognito_client_id,
        )
        return {
            "access_token": response["AuthenticationResult"]["AccessToken"],
            "id_token": response["AuthenticationResult"]["IdToken"]
        }
    except ClientError as refresh_error:
        return refresh_error

Note that the refresh token flow typically doesn't return a new refresh token, so you may want to remove that from your return statement.

By making these changes, you should be able to resolve the SecretHash mismatch error during the refresh token flow.
Sources
Unable to sign-in (SecretHash does not match for the client) | AWS re:Post
Understanding the refresh token - Amazon Cognito

answered 2 years ago

0

Currently, in my sign-in request, I return the AccessToken, RefreshToken, and IDToken to the frontend. I assumed that when the AccessToken expires, the frontend would send me the RefreshToken, and I could generate a new AccessToken using the RefreshToken and the email address.

From your response, it seems that I also need the username/sub for the refresh token flow to work correctly. Does this mean I need to include the username/sub in the response to the frontend during the sign-in flow so it can send it back to me during the refresh process like code below?

    async def signin(self, data: SignInRequest):
        try:
            secret_hash = self.calculate_secret_hash(data.email)
            response = self.cognito_client.initiate_auth(
                AuthFlow="USER_PASSWORD_AUTH",
                AuthParameters={
                    "USERNAME": data.email,
                    "PASSWORD": data.password,
                    "SECRET_HASH": secret_hash
                },
                ClientId=settings.cognito_client_id
            )
            access_token = response["AuthenticationResult"]["AccessToken"]
            id_token = response["AuthenticationResult"]["IdToken"]
            refresh_token = response["AuthenticationResult"]["RefreshToken"]

            user_sub = self.get_user_sub(access_token)

            return {
                "access_token": access_token,
                "id_token": id_token,
                "refresh_token": refresh_token,
                "sub": user_sub
            }

        except ClientError as e:
            raise HTTPException(status_code=401, detail=f"Authentication failed: {str(e)}")

Or is there another way to retrieve the username/sub during the refresh token flow without requiring the frontend to store and resend it?

answered 2 years ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.