7

Problem

"Unable to verify secret hash for client ..." at REFRESH_TOKEN_AUTH auth flow.

{
    "Error": {
        "Code": "NotAuthorizedException",
        "Message": "Unable to verify secret hash for client 3tjdt39cq4lodrn60kjmsb****"
    },
    "ResponseMetadata": {
        "HTTPHeaders": {
            "connection": "keep-alive",
            "content-length": "114",
            "content-type": "application/x-amz-json-1.1",
            "date": "Tue, 29 Jan 2019 22:22:35 GMT",
            "x-amzn-errormessage": "Unable to verify secret hash for client 3tjdt39cq4lodrn60kjmsbv3jq",
            "x-amzn-errortype": "NotAuthorizedException:",
            "x-amzn-requestid": "610368ec-2414-11e9-9671-f11a8cac1e43"
        },
        "HTTPStatusCode": 400,
        "RequestId": "610368ec-2414-11e9-9671-f11a8cac1e43",
        "RetryAttempts": 0
    }
}

Boto3 code for REFRESH_TOKEN_AUTH

Followed the AWS documentation (as in the references below).

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

response = get_client().admin_initiate_auth(
    UserPoolId=USER_POOL_ID,
    ClientId=CLIENT_ID,
    AuthFlow='REFRESH_TOKEN_AUTH',
    AuthParameters={
        'REFRESH_TOKEN': refresh_token,
        'SECRET_HASH': get_secret_hash(username)
    }
)

It does not happen at ADMIN_NO_SRP_AUTH auth flow with the same secret hash value.

Boto3 code for ADMIN_NO_SRP_AUTH

response = get_client().admin_initiate_auth(
    UserPoolId=USER_POOL_ID,
    ClientId=CLIENT_ID,
    AuthFlow='ADMIN_NO_SRP_AUTH',
    AuthParameters={
        'USERNAME': username,
        'SECRET_HASH': get_secret_hash(username),
        'PASSWORD': password
    },
    ClientMetadata={
        'username': username,
        'password': password
    }
)

The same secret hash works with 200.

{
    "AuthenticationResult": {
        "AccessToken": ...,
        "TokenType": "Bearer"
    },
    "ChallengeParameters": {},
    "ResponseMetadata": {
        "HTTPHeaders": {
            "connection": "keep-alive",
            "content-length": "3865",
            "content-type": "application/x-amz-json-1.1",
            "date": "Tue, 29 Jan 2019 22:25:33 GMT",
            "x-amzn-requestid": "cadf53cf-2414-11e9-bba9-4b60b3285418"
        },
        "HTTPStatusCode": 200,
        "RequestId": "cadf53cf-2414-11e9-bba9-4b60b3285418",
        "RetryAttempts": 0
    }
}

Both uses the same logic to generate the secret hash.

def get_secret_hash(username):
    msg = username + CLIENT_ID
    digest = hmac.new(
        str(CLIENT_SECRET).encode('utf-8'),
        msg = str(msg).encode('utf-8'),
        digestmod=hashlib.sha256
    ).digest()
    hash = base64.b64encode(digest).decode()

    log_debug("secret hash for cognito UP is [{0}]".format(hash))
    return hash

The value is the same:

secret hash for cognito UP is [6kvmKb8almXpYKvfEbE9q4r1Iq/SuQvP8H**********].

Environment

  • Cognito User Pool with client secret enabled.

    print boto.Version 2.49.0

Research

AWS Javascript JDK

AWS Amplify Javascript JDK does not support client secret as stated in Github but no report found so far on Boto3.

When creating the App, the generate client secret box must be unchecked because the JavaScript SDK doesn't support apps that have a client secret.

Related issues

References

Vladimir Venegas
  • 3,894
  • 5
  • 25
  • 45
mon
  • 18,789
  • 22
  • 112
  • 205

1 Answers1

14

Whether the behaviour is as expected or not is to be confirmed. For the moment, to work-around the problem.

From AWS

The cause and work-around identified by an AWS guy.

when you have an “@” in the username you get that error on the REFRESH_TOKEN_AUTH call. Cognito generates a UUID-style username for them. And you have to use that during the refresh call.

Sample code provided to refresh the tokens.

import boto3
import hmac
import hashlib
import base64
import time
import jwt

Region = "us-east-1"
UserPoolId = "Your userpool ID"
AppClientId = "yyyy"
AppClientSecret = "zzzz"
Username = "james@bond.com"
Password = "shakennotstirred"

Signature = hmac.new(AppClientSecret, Username+AppClientId,digestmod=hashlib.sha256)
Hash = base64.b64encode(Signature.digest())

Cognito = boto3.client("cognito-idp", region_name=Region)

AuthResponse = Cognito.admin_initiate_auth(
    AuthFlow="ADMIN_NO_SRP_AUTH",
    ClientId=AppClientId,
    UserPoolId=UserPoolId,
    AuthParameters={"USERNAME":Username, "PASSWORD":Password, "SECRET_HASH":Hash})

IdToken = AuthResponse["AuthenticationResult"]["IdToken"]
RefreshToken = AuthResponse["AuthenticationResult"]["RefreshToken"]

Decoded = jwt.decode(IdToken, verify=False)
DecodedUsername = Decoded["cognito:username"]

NewSignature = hmac.new(AppClientSecret, DecodedUsername+AppClientId, digestmod=hashlib.sha256) #!! Generate new signature and hash
NewHash = base64.b64encode(NewSignature.digest())

RefreshResponse = Cognito.admin_initiate_auth(
    AuthFlow="REFRESH_TOKEN_AUTH",
    ClientId=AppClientId,
    UserPoolId=UserPoolId,
    AuthParameters={"REFRESH_TOKEN":RefreshToken, "SECRET_HASH":NewHash}) #!! Use the new hash

NewIdToken = RefreshResponse["AuthenticationResult"]["IdToken"]


print("NewIdToken: "+NewIdToken)

The example uses Python2. To install the packages required.

pip2 install cryptography -t .
pip2 install PyJWT -t .
mon
  • 18,789
  • 22
  • 112
  • 205
  • 4
    So the summary is: when calling REFRESH_TOKEN_AUTH, use the Cognito assigned UUID username when calculating the secret hash, and not the email address or other ID used to create the account and which is used with the other types of calls. – simpleuser Feb 18 '20 at 22:43