6
votes

I am trying to write an API test in Python for my web service. I would like to avoid using the password of the test user from my AWS Cognito pool. My strategy for this, and let me know if there's a better way here, is to require that the API test be run with Cognito admin privileges. Then use the boto3 library to get the JWT AccessToken for the user which I will add to the header of every request for the API test.

The documentation doesn't seem to give me a way to get the AccessToken. I'm trying to use this here: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cognito-idp.html#CognitoIdentityProvider.Client.admin_initiate_auth

admin_initiate_auth needs one of three auth modes. USER_PASSWORD_AUTH requires the password, USER_SRP_AUTH requires a client secret, CUSTOM_AUTH requires a secret hash. I'm hoping to find a way to write this script so that I just need to have the right IAM privileges and not need to check in a public test user password.

Or... I guess... be told that this is not a great way to be doing this and that another way is more appropriate. The end goal is to have an API black box test for a service that is secured by Cognito.

2
Re-read the docs, there are more than three valid auth modes for AdminInitiateAuth. The one you want is ADMIN_NO_SRP_AUTH, but it still requires the password. Is the API you're trying to test on API Gateway? - xlem
Are you sure this will work? It says "ADMIN_NO_SRP_AUTH : Non-SRP authentication flow; you can pass in the USERNAME and PASSWORD directly if the flow is enabled for calling the app client." It looks like it also requires a password. - mmachenry

2 Answers

3
votes

If the API test must be secured using Cognito, you're always going to need some kind of password. The best way I can think of to avoid storing it is to create a temporary user before running the test suite, and then delete it when finished.

You can use AdminCreateUser to accomplish this. Generate a new password at runtime and pass it as the temporary password for the user, along with SUPRESS specified for MessageAction. The temporary password is good for one login, which is all you need in this use case. Then you can run AdminInitiateAuth with the ADMIN_NO_SRP_AUTH auth mode, specifying your generated password. Cleanup with AdminDeleteUser after the tests have finished.

6
votes

For my own project, I was also thinking a similar strategy to test Cognito-protected APIs.

I think making a temporary user with a random password for each test run is a fair approach.

To create a user from command line, I think there are simpler cognito API calls, which are sign-up and admin-confirm-sign-up provided in cognito-idp CLI tool. With this, you can skip the steps to resolve the challenges and the user is ready to use.

If you want to use boto3, here is a simple function to create a new user:

def create_user(username: str, password: str, 
                user_pool_id: str, app_client_id: str) -> None:
    client = boto3.client('cognito-idp')

    # initial sign up
    resp = client.sign_up(
        ClientId=app_client_id,
        Username=username,
        Password=password,
        UserAttributes=[
            {
                'Name': 'email',
                'Value': '[email protected]'
            },
        ]
    )

    # then confirm signup
    resp = client.admin_confirm_sign_up(
        UserPoolId=user_pool_id,
        Username=username
    )

    print("User successfully created.")

Then, to obtain JWT,

def authenticate_and_get_token(username: str, password: str, 
                               user_pool_id: str, app_client_id: str) -> None:
    client = boto3.client('cognito-idp')

    resp = client.admin_initiate_auth(
        UserPoolId=user_pool_id,
        ClientId=app_client_id,
        AuthFlow='ADMIN_NO_SRP_AUTH',
        AuthParameters={
            "USERNAME": username,
            "PASSWORD": password
        }
    )

    print("Log in success")
    print("Access token:", resp['AuthenticationResult']['AccessToken'])
    print("ID token:", resp['AuthenticationResult']['IdToken'])