Boto3 `initiate_auth` 为有效的刷新令牌引发 `NotAuthorizedException`

Boto3 `initiate_auth` Raises `NotAuthorizedException` for Valid Refresh Tokens

我们已经使用 Cognito 授权方保护我们的 Chalice 端点,并且能够通过在 Authorization header.

中传递有效的 ID 令牌来访问它

下面是我们保护端点的代码:

authorizer = CognitoUserPoolAuthorizer(
    'USER_POOL_NAME', provider_arns=['USER_POOL_ARN'])

@app.route('/ping', methods=['GET'], authorizer=authorizer)
def ping() -> str:
    return 'The site is up!'

下面是我们如何在给定已在我们的用户池中注册并确认的电子邮件和密码的情况下获取 ID 令牌:

_USER_POOL_ID: Final[str] = ''
_USER_POOL_REGION: Final[str] = 'ap-southeast-1'
_APP_CLIENT_ID: Final[str] = ''
_APP_CLIENT_SECRET: Final[str] = ''

_USERNAME_PASSWORD_AUTH_FLOW = 'USER_PASSWORD_AUTH'

_aws_client = boto3.client('cognito-idp', config=Config(region_name=_USER_POOL_REGION))


def _get_secret_hash(username, cognito_client_id, cognito_secret) -> str:
    msg = username + cognito_client_id

    dig = hmac.new(str(cognito_secret).encode('utf-8'),
                   msg=str(msg).encode('utf-8'),
                   digestmod=hashlib.sha256).digest()

    return base64.b64encode(dig).decode()


@app.route('/authenticate', methods=['POST'], content_types=['application/json', 'text/plain'])
def authenticate():
    # Callers will pass an email and password to this endpoint.
    return _aws_client.initiate_auth(ClientId=_APP_CLIENT_ID,
                                     AuthFlow=_USERNAME_PASSWORD_AUTH_FLOW,
                                     AuthParameters={
                                         'USERNAME': 'email',
                                         'SECRET_HASH': _get_secret_hash('email',
                                                                         _APP_CLIENT_ID,
                                                                         _APP_CLIENT_SECRET),
                                         'PASSWORD': 'password'
                                     })

以下是来自 initiate_authdocumentation 的响应格式:

{
    'ChallengeName': 'SMS_MFA'|'SOFTWARE_TOKEN_MFA'|'SELECT_MFA_TYPE'|'MFA_SETUP'|'PASSWORD_VERIFIER'|'CUSTOM_CHALLENGE'|'DEVICE_SRP_AUTH'|'DEVICE_PASSWORD_VERIFIER'|'ADMIN_NO_SRP_AUTH'|'NEW_PASSWORD_REQUIRED',
    'Session': 'string',
    'ChallengeParameters': {
        'string': 'string'
    },
    'AuthenticationResult': {
        'AccessToken': 'string',
        'ExpiresIn': 123,
        'TokenType': 'string',
        'RefreshToken': 'string',
        'IdToken': 'string',
        'NewDeviceMetadata': {
            'DeviceKey': 'string',
            'DeviceGroupKey': 'string'
        }
    }
}

我们使用 AuthenticationResult.IdToken 作为 Authorization header 的值。

我们的问题是当我们尝试通过 AuthenticationResult.RefreshToken 获取新的 ID 令牌时,我们得到 NotAuthorizedException

我们已将我们的用户池配置为 NOT 记住用户设备,因此我们假设我们不必通过 DEVICE_KEY 使用刷新令牌时。我们还配置了我们的应用程序客户端,以便刷新令牌持续 180 天,而 ID 和访问令牌仅持续 1 小时。

我们遵循 this tutorial 下面是我们的代码:

_REFRESH_TOKEN_AUTH_FLOW = 'REFRESH_TOKEN_AUTH'

@app.route('/refreshTokens', methods=['POST'], content_types=['application/json', 'text/plain'])
def refresh_tokens():
    # Callers will pass an email and refresh token to this endpoint.
    return _aws_client.initiate_auth(ClientId=_APP_CLIENT_ID,
                                     AuthFlow=_REFRESH_TOKEN_AUTH_FLOW,
                                     AuthParameters={
                                         'USERNAME': 'email',
                                         'SECRET_HASH': _getSecretHash(email,
                                                                       _APP_CLIENT_ID,
                                                                       _APP_CLIENT_SECRET),
                                         'REFRESH_TOKEN': 'refresh token'
                                     })

令我们困惑的是,刷新令牌应该是有效的,因为它来自我们从中获得有效 ID 令牌的相同响应。

我们做错了什么吗?

事实证明我们无法使用用户的电子邮件(关于'@'字符导致问题)。

解决方案是同时传递 ID 令牌,这样我们就可以提取用户的 Cognito 用户名(某种 UUID)并使用它代替电子邮件。

另外,显然,这个问题