Pain Point

I intent to create a REST API to handle request from unauthenticated mobile app(s), but the API should not be invoked by other unrecognized end points.

For access control, I could not rely on providing client applications with static API key strings; these can be extracted from clients and used elsewhere.

The Amazon API Gateway allows to generate client SDKs to integrate with your APIs. That SDK also manages the signing of requests when APIs require authentication.

Each resource/method combination that you create as part of your API is granted its own specific Amazon Resource Name (ARN) that can be referenced in AWS Identity and Access Management (IAM) policies.

API access is enforced by the IAM policies that you create outside the context of your application code. This means that you do not have to write any code to be aware of or enforce those access levels.

Authorizing clients using AWS Signature version 4 (SigV4) authorization and IAM policies for API access allows those same credentials to restrict or permit access to other AWS services and resources as needed.

However there is no comprehensive sample code to walk through the idea as I document the post, I hope this could be of some help to whom would like to have similar intention as I do.

Overview

In this sample code, I’d like to create an MOCK service which would only response to client (mobile app) with signing requests.

Steps by Steps

Create Mock API Gateway and Enable CORS

Change Authorization Settings to AWS_IAM

{
  "message": "Missing Authentication Token"
}

Create Cognito Identity Pool

Grant Cognito_StoreUnauth_Role to invoke MOCK API Gateway

Replace [region], [account-id] and [api-id] first

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "execute-api:Invoke"
            ],
            "Resource": [
                "arn:aws:execute-api:[region]:[account-id]:[api-id]/dev/*/*"
            ]
        }
    ]
}

Invoke MOCK API Gateway with Cognito SDK in JS

<!DOCTYPE html>
<html>
<title>Web Page Design</title>
<head>
    <script type="text/javascript" src="lib/axios/dist/axios.standalone.js"></script>
    <script type="text/javascript" src="lib/CryptoJS/rollups/hmac-sha256.js"></script>
    <script type="text/javascript" src="lib/CryptoJS/rollups/sha256.js"></script>
    <script type="text/javascript" src="lib/CryptoJS/components/hmac.js"></script>
    <script type="text/javascript" src="lib/CryptoJS/components/enc-base64.js"></script>
    <script type="text/javascript" src="lib/url-template/url-template.js"></script>
    <script type="text/javascript" src="lib/apiGatewayCore/sigV4Client.js"></script>
    <script type="text/javascript" src="lib/apiGatewayCore/apiGatewayClient.js"></script>
    <script type="text/javascript" src="lib/apiGatewayCore/simpleHttpClient.js"></script>
    <script type="text/javascript" src="lib/apiGatewayCore/utils.js"></script>
    <script type="text/javascript" src="dist/aws-sdk.min.js"></script>
    <script type="text/javascript" src="apigClient.js"></script>
    <script src="script.js"></script>
</head>
<body>
</body>
</html>

Replace [region] and [identity-pool-id] first

function foo() {

  AWS.config.region = 'region'; // Replace with the region you deploy

  var cognitoidentity = new AWS.CognitoIdentity();
  var params = {
    IdentityPoolId: 'identity-pool-id'
  };

  // Generate a Cognito ID for the 1st time, so IdentityId could be kept for future use
  cognitoidentity.getId(params, function(err, data) {
    if (err) console.log(err, err.stack); // an error occurred
    else console.log(data); // successful response

    var params = {
      IdentityId: data.IdentityId
    };

    // Retrieve temp credentials with IdentityId
    cognitoidentity.getCredentialsForIdentity(params, function(err, data) {
      if (err) console.log(err, err.stack); // an error occurred
      else console.log(data); // successful response

      var apigClient = apigClientFactory.newClient({
        accessKey: data.Credentials.AccessKeyId,
        secretKey: data.Credentials.SecretKey,
        sessionToken: data.Credentials.SessionToken,
        region: 'region' // Replace with the region you deploy
      });

      var params = {
        //This is where any header, path, or querystring request params go. The key is the parameter named as defined in the API
        foo: "foo"
      };

      apigClient.demoGet(params).then(function(result) {
        //This is where you would put a success callback
        console.log(result);
        alert("Hello foo!");
      }).catch(function(result) {
        //This is where you would put an error callback
        console.log(result);
        alert("Oops foo!");
      });
    })

  });
}

foo();

You ONLY need to invoke AWS.CognitoIdentity.getId() first time or as you receive ResourceNotFoundException: Identity 'identity-id' not found.

To use an API key with the API Gateway-generated SDK, you can pass the API key as a parameter to the Factory object by using code similar to the following. If you use an API key, it is specified as part of the x-api-key header and all requests to the API will be signed.

var apigClient = apigClientFactory.newClient({ apiKey: ‘api-key’ });

References