An independent guide to building modern software for serverless and native cloud

Testing API Gateway with the Cognito Authorizer

This lab references the scripts in the aws-connectedcar-common repository. If you're new to this course, see the introduction for information about setting up your workstation and getting the sample code.

For this final lab of the section we’re going to test the Cognito Authorizer that’s used by the Customer API. We’ll step through the process of creating sample user accounts in Cognito, of obtaining access tokens from the command line, and of using these tokens as variables in Postman. Lastly, we’ll show how to authenticate users in Postman with the OAuth 2.0 authorization code flow.

Creating Sample User Accounts

We can’t call the Customer API without having at least one user account in Cognito to use for authentication. Fortunately, the “Create Customer” endpoint in the Admin API creates customer items in DynamoDB and matching user accounts in Cognito.

Step 1: Send “Create Customer” requests to create user accounts

Open the “Customers” folder in the “Admin_API” collection in Postman, select the “Create Customer” test and then the “Body” tab. Once you’ve sent the request and got a successful “201 Created” response, select the “Headers” tab of the response panel at the bottom of the screen:

Keep a copy of the generated username that’s returned in the “Location” header, as highlighted above. The username is the last element of the resource path, with the text that starts with “USER”. Next, update the value of the “firstname” field in the request body, send another request, and then keep a copy of this generated username as well.

Checking Cognito in the Console

Step 2: Check the Cognito user pool in the console

Now that you’ve created a pair of user accounts, let’s check these and some of the user pool properties in the AWS console. Navigate to the Cognito service in the console, where you should see the user pool that was created during the deployment:

Click on the “ConnectedCar_UserPool_Dev” link, and then on the user pool landing page, select the “Sign-in experience” tab. Scrolling down slightly, two panels are of interest:

The “Cognito user pool sign-in” panel shows the sign-in credential used, which is a user name (as opposed to an email or phone number). On the right side of this panel, you can see that user names are case sensitive for this user pool. The “Password policy” panel at the bottom shows the user pool settings for password lifecycle and complexity.

If you click on the “Sign-up experience tab” and scroll to the bottom of the page, you’ll see on the “Self-service sign-up” panel that we have also configured this user pool to not allow users to self-register. User accounts for this user pool have to be created administratively, via the console, command line, or API.

Step 3: Check the Cognito user accounts in the console

Next, select the “Users” tab to see the two user accounts that you’ve just created. They should look something like this:

As you can see from the list at the bottom of the page shown above, these user accounts don’t have email addresses since they authenticate with usernames. Also, these accounts will have to set a permanent password before they can successfully sign-in.

If you click on the user link, the one piece of information that’s good to know is that there’s a “sub” property for Cognito user accounts. This is the unique identifier that you specify for any command line and API operations you might perform on the user account. This is shown below, at the top left, as the “User ID (Sub)” property, as well as at the bottom of the screen in the list of user attributes:

Step 4: Check the Cognito app client in the console

Now, navigate back to the user pool page, select the “App integration” tab and scroll to the bottom of the page. On the “App clients and analytics” panel, click on the “ConnectedCar_UserPoolClient_Dev” link. You should then see the “App client” page, as shown below:

For an identity provider like Cognito, app clients are set up for each application (or group of closely-related applications). Users have to authenticate separately for each application that uses a different app client. Conversely, users can potentially navigate between applications that share an app client without having to re-authenticate.

Step 5: Check the Cognito Authorizer in the console

The last thing to check while you’re in the console is the Cognito Authorizer that’s set for the Customer API. To view this resource, navigate to the API Gateway service and select the “ConnectedCar_Customer_API_Dev” API. Then select the “Authorizers” option on the left, and you should see the “CognitoAuthorizer” panel displayed, as shown below:

The authorizer only has a couple of properties. As you can see from above, it specifies the Cognito user pool through a UserPoolId reference. It also specifies the source of the token to be validated and parsed, which in this case is the Authorization header of incoming requests.

We should also note that just because an authorizer is defined for the API, that alone doesn’t secure the endpoints. Each endpoint also needs to specify the authorizer. To show an example that we’ll be using shortly, the GET method of the “/customer/profile” resource path, shown below, specifies that access is authorized through the CognitoAuthorizer for requests with tokens that include at least one of the two listed OAuth scopes:

Getting an Access Token from the Command Line

To recap: for requests to be authorized to access the endpoints in the Customer API, they need to include a valid access token in the HTTP headers. One way to get this token is with the web-based Authorization Code Flow, which we’ll demonstrate shortly. But with Cognito, for app clients that are configured to support it, you can also get access tokens by authenticating from the command line.

With this in mind, and before we look at the sample script for command line authentication, take a quick look again at the properties for the app client that we saw earlier, in the console:

At the lower-left side of this page, note the “Authentication flows” property. This is where you specify the different ways that applications can authenticate with this app client. (For our deployment this property is explicitly defined in the UserPoolClient resource that’s found in the services.yaml template.)

What you should know about this property is that by default the web-based Authorization Code Flow is always permitted. What the two values shown above indicate are optional flows that have also been enabled. In this case, the “ALLOW_ADMIN_USER_PASSWORD_AUTH” value permits user authentication through the administrative command line and API. (The other value permits the renewal of access tokens through the refresh token process, which we’re not going to cover here.)

Step 6: Set the config.zsh variables for the authentication.zsh script in VS Code

Switch to VS Code and open the “aws-serverless-common” repository. From there, expand the “/scripts/cognito/zsh” folder, and open the config.zsh script:

Before we can run the authenticate.zsh script in the next step, you need to set the username variable, found on line 10, as shown above. For this, you can use the first of the two username values that you saved from the start of this lab.

Step 7: Run the authentication.zsh script and copy the access token

Next, open the authentication.zsh script from the same folder:

Lines 6-20 are familiar commands that you’ve seen before in the CloudFormation section. These commands are using stack queries combined with other commands to obtain the information needed to specify the Cognito user pool and app client. Line 22 generates a hash value that’s needed for the authentication command that follows. Lines 24-28 shows the Cognito admin command to set a permanent password for the specified user account, without which the user cannot sign-in. Lastly, lines 30-36 show the command to authenticate for the specified user pool, app client, user account, password, and secret hash. Line 35 queries the JSON output of this command for the access token, which is saved to the token variable in the script.

Once you’ve run this authentication.zsh script, you should see the token value in the terminal output, as shown below. Carefully select and copy this value and paste it to a nearby text editor:

Testing the Customer API

Now that you’ve obtained an access token, you can use it to test the Customer API in Postman.

Step 8: Set the customerApi global variable in Postman

Whether by running the query-outputs.zsh script or using the console, get the generated ID for the Customer API. Then in the same way that you have for the “adminApi” variable, set the “customerApi” global variable, as shown below:

Step 9: Set the Customer API token variable in Postman

Next, you need to set the token variable for the authorization settings of the Customer API in Postman. To set this variable, select the “Customer_API”, then select the “Authorization” tab. Scroll down to the “Current token” section of the screen and paste the token value from the previous step into the “Token” field, as shown below:

Step 10: Send a “Get Customer” request in Postman

You should now be ready to send a “Get Customer” request for the Customer API. If your settings from the previous two steps are in place, after sending the request you should get a “200 OK” response, like you see below:

Step 11: View the claims in the access token with the jwt.io debugger

In the sample code, the Admin API is where administrative users can make changes to any items in the system. With the Customer API, the logged-in user is a customer who only has access to their own data. The URLs for these APIs reflect this fact, where the Admin API “Get Customer” request URL has a path parameter that specifies the customer username. The Customer API “Get Customer” request URL does not have any parameter to identify the target customer.

And this points to the next topic of interest, which is the content of the access token. As we covered earlier in this section, the JWT access token contains “claims” in the form of name-value pairs. You can see the content of the access token you’ve used for this lab by going to the JWT debugger at http://jwt.io and pasting in the content of the token. When you do so, you should see something like this:

To outline what’s inside a JWT access token, there are three period-delimited parts, each of which is Base64 encoded. Once decoded, the first part of the token has JSON that includes the public key for the digital signature and the name of the encryption algorithm that applies. The third part has the signature, while the middle part, shown above on the right, in purple, has the list of claims. The last claim, you’ll note, has a value for the username. It’s by reading this claim from the token that the Lambdas for the Customer API are able to filter for the logged-in customer’s data.

Using the Authorization Code Flow in Postman

Authenticating with Cognito from the command line or through the API can be very useful. The sample code demonstrates using the command line to make automated tests for the Customer API possible, as you’ll see later in the section on CodePipeline.

But as noted previously in this section, there are also reasons to test with the web-based, Cognito-hosted Authorization Code Flow. With the web-based flow you have delegated authentication that can be tested by third-party applications without them accessing the users’ credentials. Like most identity provider services, Cognito can also federate with other OIDC- or SAML-compatible identity providers. Links to these other providers, when enabled for an app client, will be integrated into the hosted login page and can only be tested by using this flow. So, let’s run through the steps required to set up this flow for the Customer API.

Step 12: Configuring Postman for Cognito Authentication

To start, you’ll need to run the query-attributes.zsh script from the “aws-serverless-common” repository to get the app client properties. As a reminder, here’s what the output from that script looks like in the VS Code terminal:

Next, you need to select the “Customer_API” collection, and then enter values in the “Current token” section of the “Authorization” tab, in Postman. The screen shot below shows what these fields look like with values entered:

Here are the values you need to enter for these fields:

Field Name Field Value
Token Name Enter “test”
Grant Type Select “Authorization Code”
Callback URL Enter “https://oauth.pstmn.io/v1/callback” (this endpoint is hosted by Postman)
Auth URL Enter the value from the query-attributes.zsh script (make sure you add the protocol)
Access Token URL Enter the value from the above field, but change the “/login” path to “/token”
Client ID Enter the value from the query-attributes.zsh script
Client Secret Enter the value from the query-attributes.zsh script
Scope Enter “openid”
Client Authentication Select “Send as Basic Auth header”

Step 13: Get the customer username and temporary password

Next, you need to get the username and temporary password that you’ll be using to sign-in. You already used the username for the first account so now you need to have the username for the second account ready. The password was set in the body of the ”Create Customer” requests that you sent.

Here’s what the body for this request looks like in Postman:

Assuming you didn’t change the value of the password field, it’s hard-coded to “$FlipHop123” for this request.

Step 14: Signing in with Cognito

Now you should be ready to login with Cognito for this second user account. First, go back to the “Authorization” tab for the Customer_API collection. Scroll all the way to the bottom of the screen, and click the orange “Get New Access Token” button. You should then see the Cognito hosted login page displayed in a window, into which you can enter the username and temporary password, as shown below:

When you click the blue “Sign in” button on this page, assuming the credentials are correct, you’ll then be presented with the “Change Password” page, as shown below:

Fill in the password fields on this page, and click “Send”.

Now, invisible to the user, the Cognito login page will redirect to the Postman-hosted callback URL specified in the app client configuration. This redirect will include an authorization code as a query parameter. Postman will then make the secure, server-side call to the access token URL for the user pool, as described earlier in this section. Postman will provide the client ID and client secret, as well as the just-issued authorization code, to which Cognito, in response, will return the access token.

This new access token is displayed in a popup dialog once this back-channel process has completed, as shown below. When you see this dialog, click the orange “Use Token” button at the top-left, and your Customer API will be ready for testing:

You can now send a “Get Customer” request for the Customer API, and you should get response JSON with field values that correspond to the second user account: