How to create a SECRET_HASH for AWS Cognito using boto3?

Viewed 8556

I want to create/calculate a SECRET_HASH for AWS Cognito using boto3 and python. This will be incorporated in to my fork of warrant.

I configured my cognito app client to use an app client secret. However, this broke the following code.

def renew_access_token(self):
    """
    Sets a new access token on the User using the refresh token.

    NOTE:
    Does not work if "App client secret" is enabled. 'SECRET_HASH' is needed in AuthParameters.
    'SECRET_HASH' requires HMAC calculations.

    Does not work if "Device Tracking" is turned on.
    https://stackoverflow.com/a/40875783/1783439

    'DEVICE_KEY' is needed in AuthParameters. See AuthParameters section.
    https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html
    """
    refresh_response = self.client.initiate_auth(
        ClientId=self.client_id,
        AuthFlow='REFRESH_TOKEN',
        AuthParameters={
            'REFRESH_TOKEN': self.refresh_token
            # 'SECRET_HASH': How to generate this?
        },
    )

    self._set_attributes(
        refresh_response,
        {
            'access_token': refresh_response['AuthenticationResult']['AccessToken'],
            'id_token': refresh_response['AuthenticationResult']['IdToken'],
            'token_type': refresh_response['AuthenticationResult']['TokenType']
        }
    )

When I run this I receive the following exception:

botocore.errorfactory.NotAuthorizedException: 
An error occurred (NotAuthorizedException) when calling the InitiateAuth operation: 
Unable to verify secret hash for client <client id echoed here>.

This answer informed me that a SECRET_HASH is required to use the cognito client secret.

The aws API reference docs AuthParameters section states the following:

For REFRESH_TOKEN_AUTH/REFRESH_TOKEN: USERNAME (required), SECRET_HASH (required if the app client is configured with a client secret), REFRESH_TOKEN (required), DEVICE_KEY

The boto3 docs state that a SECRET_HASH is

A keyed-hash message authentication code (HMAC) calculated using the secret key of a user pool client and username plus the client ID in the message.

The docs explain what is needed, but not how to achieve this.

2 Answers

I also got a TypeError when I tried the above solution. Here is the solution that worked for me:

import hmac
import hashlib
import base64

# Function used to calculate SecretHash value for a given client
def calculateSecretHash(client_id, client_secret, username):
    key = bytes(client_secret, 'utf-8')
    message = bytes(f'{username}{client_id}', 'utf-8')
    return base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()

# Usage example
calculateSecretHash(client_id, client_secret, username)

Related