AWS: API Gateway Cognito Authorizer

Scenario

Imagine you want to build and expose a REST API on AWS. At this moment your API’s only requirement is to support a single resource (domain.com/default/greetings), and whenever this resource is called with a GET request, it should return “Hello”.

Constraints

What if you would like to protect your API resource from unauthorized users? What if instead of greetings you would like to expose specific data fetched from third-party backends or storage solutions. We can achieve this using a AWS Cognito authorizer.

Solution

For testing purporses, let’s create a user (butters@southpark.com) in your Cognito User Pool, followed by a administrator triggered “sign up” confirmation:

# Create user (sign up):
aws cognito-idp sign-up \
--client-id a11b22c33d44e55f66g77h88i99j \
--username butters@southpark.com \
--password S3cr3tP4ssw0rd \
--region eu-central-1 \
--user-attributes '[
    {"Name":"given_name","Value":"Butters"},
    {"Name":"family_name","Value":"Scotch"},
    {"Name":"email","Value":"butters@southpark.com"},
    {"Name":"gender","Value":"Male"}
]'

# Confirm sign up:
aws cognito-idp admin-confirm-sign-up --user-pool-id eu-central-x_xxxyyyzzz --username 58ccaeb4-0668-4361-97f2-b782f4dc00c1

You could rely directly on your application in order to authenticate, for this example, let’s simply use curl. To keep things simple and repetable, create a .json file (aws-auth-data.json) with the following structure, replacing values when needed:

{
   "AuthParameters" : {
      "USERNAME" : "butters@southpark.com",
      "PASSWORD" : "S3cr3tP4ssw0rd"
   },
   "AuthFlow" : "USER_PASSWORD_AUTH",
   "ClientId" : "a11b22c33d44e55f66g77h88i99j"
}

More information about different AuthFlow can be found on this page.

Putting it all together, we end up with the following authentication script (fetch_id_token.sh):

#!/bin/bash
curl -s -X POST \
    --data @aws-auth-data.json \
        -H 'X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth' \
        -H 'Content-Type: application/x-amz-json-1.1' \
        https://cognito-idp.eu-central-1.amazonaws.com | jq .AuthenticationResult.IdToken | sed 's/\"//g'

The above command will call AWSCognitoIdentityProviderService API on the InitiateAuth resouce, this expects a request content type x-amz-json-1.1 (provided with aws-auth-data.json). A successful authentication will return:

{
    "AuthenticationResult": {
        "AccessToken": "FVytONjAyMnpvVmtYaFR...FVytONjAyMnpvVmtYaFR",
        "IdToken": "NhaTY0Z0FjMFNhaTY00F...NhaTY0Z0FjMFNhaTY00F",
        "RefreshToken": "eyJjdHkiOiJKV1QiLCJb...eyJjdHkiOiJKV1QiLCJb",
        "TokenType": "Bearer",
        "ExpiresIn": 3600
    },
    "ChallengeParameters": {}
}

Each of these tokens follows the JWT standard and can be validated here. You should also verify the claim on your beckend before further processing, like discussed here.

Our script job is to parse the blob above, retrieving the only value we care about for the sake of this example. That would be the IdToken. We could use it as such:

curl -i -s -X GET https://example.execute-api.eu-central-1.amazonaws.com/default/greetings -H "Authorization: $(./fetch_id_token.sh)"