Example code for AWS Cognito User Pool InitiateAuth with Username and Password via HTTPS call?

Viewed 9055

I am trying to use Cognito User Pool to authenticate with a PC application using an HTTPS call. I want to obtain the various tokens that I can then use to access the AWS resources without storing AWS secrets in the PC application.

The AWS documentation documents the InitiateAuth method and shows the AWS Endpoints, but it is not immediately apparent how to make the call over HTTPS. Most calls would require an AWS signature, but the InitiateAuth call should not, if I am just submitting Username and Password.

2 Answers

After some poking around, I was able to use the AWS CLI to successfully obtain tokens with this command:

aws cognito-idp initiate-auth --auth-flow USER_PASSWORD_AUTH --client-id the_cognito_client_id --auth-parameters USERNAME=the_users_email,PASSWORD=the_users_password

Where the_cognito_client_id is an approximately 26 character long string shown as App client id under General Settings / App clients.

Note that the USER_PASSWORD_AUTH flow is not enabled by default, so you will initially get an error with this. Go to the Cognito Console for the specific User Pool and look for General Settings/App Clients, click "Show Details" for your specific app client, and check the "Enable username password based authentication (ALLOW_USER_PASSWORD_AUTH)" and save.

Once you get back tokens and you know your call is working, you can use the aws history show command to show you the details of the actual https call. The first time you call

aws history show

You will get a message

Could not locate history. Make sure cli_history is set to enabled in the ~/.aws/config file

Go to that file and add

cli_history=enabled

Then, run your initiate-auth call again with the cli. Then, when you run

aws history show  

You will get back the details of how the call was made. (At this point, you might consider removing the cli_history setting so you don't log all of your calls, with all of the credentials, in the future.) You will see

to URL: https://cognito-idp.us-east-1.amazonaws.com/

which tells you the URL to use, and you will see that it is a POST. You will note that "InitiateAuth" is not anywhere in that URL. However, you will see the headers include:

"X-Amz-Target": "AWSCognitoIdentityProviderService.InitiateAuth"

and

"Content-Type": "application/x-amz-json-1.1"

You need both of those headers, including the non-standard Content-Type, to make the HTTPS call work. You can use Postman to put the call together, although Postman does not like the non-standard Content-Type, so you have to turn off the standard Content-Type and manually add these two headers to the call. At that point, Postman is able to obtain the tokens as well.

Postman also provides an export to CURL function (click the link that says "Code"), which gives you:

curl --location --request POST 'https://cognito-idp.us-east-1.amazonaws.com/' \
--header 'X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth' \
--header 'Content-Type: application/x-amz-json-1.1' \
--data-raw '{
    "AuthFlow": "USER_PASSWORD_AUTH",
    "AuthParameters": {
        "PASSWORD": "the_users_password",
        "USERNAME": "the_users_email"
    },
    "ClientId": "the_cognito_client_id"
}'

Submitting that on the command line also gives you the tokens you need.

To refresh using the refresh token, just use InitiateAuth, but the AuthFlow is REFRESH_TOKEN_AUTH and the only member of AuthParameters is REFRESH_TOKEN (which is, of course, the RefreshToken)

Now, I just need to figure out how to do USER_SRP_AUTH using HTTPS.

The other answer explains how to get the Tokens using the Username and Password. Next, we need to get the temporary credentials from the Cognito Identity Pool. This appears to require two steps. First, we need to call cognito-identity get-id and then cognito-identity get-credentials-for-identity

The get-id call requires the Identity Pool ID, which can be obtained from the Cognito Console for the Identity Pool. The identity-pool-id is available under "edit identity pool" as "identity pool ID." The Login key name comes from the User pool ID and is available under Authentication Providers / Cognito under the Edit Identity Pool. The Login key name is actually provided as "iss" in the encoded ID Token. You can use a service like https://jwt.io/ to decode the ID Token, and it will include an entry like

"iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_zAgxxxxxx"

You can use that, without the https://, as the key for Login. The value for Login is the JWT returned as "IdToken" from InitiateAuth.

Therefore, using the CLI, the first call is

aws cognito-identity get-id --identity-pool-id "us-east-1:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" --logins "{\"cognito-idp.us-east-1.amazonaws.com/us-east-1_zAgxxxxxx\": \"ThisIsTheVeryLong.IDTokenReturneFrom.TheCognitoUserPool\"}"

Which returns the ID:

{
    "IdentityId": "us-east-1:11111111-2222-3333-4444-555555555555"
}

Then, we need to get the temporary credentials using get-credentials-for-identity. This call uses the IdentityId returned from get-id rather than the Identity Pool ID used by get-id. The Login JSON is the same as for get-id. Therefore, the call is:

aws cognito-identity get-credentials-for-identity --identity-id "us-east-1:11111111-2222-3333-4444-555555555555" --logins "{\"cognito-idp.us-east-1.amazonaws.com/us-east-1_zAgxxxxxx\": \"ThisIsTheVeryLong.IDTokenReturneFrom.TheCognitoUserPool\"}"

Which returns something like:

{
    "IdentityId": "us-east-1:11111111-2222-3333-4444-555555555555",
    "Credentials": {
        "AccessKeyId": "ASIAXXXXXXXXXXXXXXXX",
        "SecretKey": "Im0JN4PrvZZZZZZZZZZZZZZZZZZZZZZZZZZ/Y/XX",
        "SessionToken": "ExtremelyLongSessionToken",
        "Expiration": "2020-06-07T15:28:51-07:00"
    }
}

You can then use that AccessKeyID and SecretKey to make any needed AWS calls; these temporary credentials are good for an hour.

Using the same methodology with aws history show and Postman described above, we can get the CURL equivalents:

curl --location --request POST 'https://cognito-identity.us-east-1.amazonaws.com/' \
--header 'X-Amz-Target: AWSCognitoIdentityService.GetId' \
--header 'Content-Type: application/x-amz-json-1.1' \
--data-raw '{
    "IdentityPoolId": "us-east-1:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
    "Logins": {
        "cognito-idp.us-east-1.amazonaws.com/us-east-1_zAgxxxxxx": "ThisIsTheVeryLong.IDTokenReturneFrom.TheCognitoUserPool"
    }
}'

and then

curl --location --request POST 'https://cognito-identity.us-east-1.amazonaws.com/' \
--header 'X-Amz-Target: AWSCognitoIdentityService.GetCredentialsForIdentity' \
--header 'Content-Type: application/x-amz-json-1.1' \
--data-raw '{
    "IdentityId": "us-east-1:11111111-2222-3333-4444-555555555555",
    "Logins": {
        "cognito-idp.us-east-1.amazonaws.com/us-east-1_zAgxxxxxx": "ThisIsTheVeryLong.IDTokenReturneFrom.TheCognitoUserPool"
    }
}'

Note that the CLI converts the expiration timestamp to local time. When you call this with CURL, the "Expiration" value will be something like 1.591572729E9 . You can convert that to a human time with a site like https://www.epochconverter.com/

Related