AWS API Gateway Tutorial Step 4

AWS API Gateway Tutorial Step 4

Step 4 - Use Multiple Roles with Amazon API Gateway

In this step, you'll assign different AWS IAM roles to users based on authentication information:

  • Users authenticating with Social Connections will be treated as buyers;

  • Users authenticating with Database Connections will be treated as admins.

You will perform this role assignment logic in two different ways:

  • JavaScript;

  • Auth0 rules.

For many Auth0 Applications, you'll want different users to have different levels of access, and you'll want additional information about a given identity to use in your service logic. In cases where it's sufficient to lock down access at the API level, you can use different AWS IAM roles (for example, administrators can use the update function to add and remove pets, but social users can only buy pets).

The following diagram illustrates AWS IAM role assignments for two different user classes: users authenticated via Social Connections and users authenticated via Database Connections. It also illustrates that AWS IAM roles can be assigned to other entities, like AWS Lamdba functions, to control the permissions these entities are assigned for an account. In short, an IAM role is a group of permissions to AWS capabilities that is defined by one or more policies and then assigned to an entity.

For cases where you want to make decisions within your code (for example, you might want a credit check of a user buying a pet), you will want to flow identity as well. This will be demonstrated below in Step 5 - Using Identity Tokens to Flow Identity.

1. Create the PetPurchase API Resource

Using the Amazon API Gateway Console, select your Pets API. You will be taken to its Resources page.

Click on Actions and Create Resource. Name the New Child Resource Purchase. Click Create Resource.

Add an OPTIONS method for the purchase resource as outlined previously for pets in the Set Up Cors and Deploy the API section of Step 2 - Securing and Deploying the Amazon API Gateway.

Create a new AWS Lambda function for purchasing a pet called PetPurchase, which adds isSold and soldTo attributes to a pet as follows:

var AWS = require('aws-sdk');
var DOC = require('dynamodb-doc');
var dynamo = new DOC.DynamoDB();

exports.handler = function(event, context) {
   var petId = event.petId;
   var user = event.userName;
   var pets = {};
   console.log('start PetsPurchase, petId', petId, ' userName', user);

   var writecb = function(err, data) {
      if(!err) {
          context.done(null, pets);
      } else {
          console.log('error on GetPetsInfo: ',err);
          context.done('failed on update', null);
      }
   };

   var readcb = function(err, data) {
      if(err) {
          console.log('error on GetPetsInfo: ',err);
          context.done('failed to retrieve pet information', null);
      } else {
          // make sure we have pets
          if(data.Item && data.Item.pets) {
              pets = data.Item.pets;
              var found = false;

              for(var i = 0; i < pets.length && !found; i++) {
                  if(pets[i].id === petId) {
                     if(!pets[i].isSold) {
                        pets[i].isSold = true;
                        pets[i].soldTo = user;
                        var item = { username:"default",pets: pets};
                        dynamo.putItem({TableName:"Pets", Item:item}, writecb);
                        found = true;
                     }
                  }
               }
               if(!found) {
                 console.log('pet not found');
                 context.done('That pet is not available.', null);
               }
           } else {
              console.log('pet already sold');
              context.done('That pet is not available.', null);
           }
       }
   };

   dynamo.getItem({TableName:"Pets", Key:{username:"default"}}, readcb);
};

Was this helpful?

/

Once you have defined the Lambda function, add a POST method to the purchase resource that calls the PetPurchase Lambda. Be sure to also add the Access-Control-Allow-Origin header with a value of * to the POST method using the method response/integration response configuration found in Set Up Cors and Deploy the API section of Step 2 - Securing and Deploying the Amazon API Gateway.

Test the API gateway method, providing the following as an input message:

{
    "petId": 1,
    "userName": "fred flintstone"
 }

Was this helpful?

/

In the test response, you should see the pet with ID of 1 is now sold to Fred Flintstone:

[
  {
    "id": 1,
    "price": 249.99,
    "type": "dog",
    "isSold": true,
    "soldTo": "fred flintstone"
  },

  ...

Was this helpful?

/

2. Use IAM to Secure the PurchasePet API

Update IAM

To secure your API, follow the same process for adding a new role that you performed in Part 2 of this tutorial. Call the new role auth0-api-social-role.

The ARN for the method you will secure in the IAM policy should look something like:

arn:aws:execute-api:us-east-1:your-accountid:your-api-id/*/pets/purchase

Was this helpful?

/

Be sure to update the trust policy as well.

Go to the Amazon API Gateway Console, and select the POST method for the /pets/purchase resource. Select Method Request and change Authorization Type to AWS_IAM. Click the check to save the setting.

At this point, you have defined two roles that you can use with the API gateway:

  • auth0-api-role: permits updating pets

  • auth0-api-social-role: permits purchasing a pet

Configure Login with Amazon and Update Auth0

You can create a social role using Login with Amazon (LWA).

While this tutorial includes instructions for using Login with Amazon, please note that you can use other social providers as well.

  1. Navigate to Auth0 Dashboard > Authentication > Social, and select Create Connection.

  2. Choose the connection you want to set up, and consent.

  3. Copy and paste the Client ID and Client Secret from your social identity provider, select the Attributes (and Permissions, where applicable), and click Save.

  4. Select the Applications view, enable the switch for each of your Auth0 applications that should be able to use this connection, and select Save.

Once you've entered the appropriate information, select Try Connection to ensure that everything is set up correctly.

When you configure LWA using the Amazon console, be sure to enter into Allowed Return URLs the callback URL to your Auth0 Application, which should look something like https://johndoe.auth0.com/login/callback. The Auth0 help page will show you specifically what to enter.

Navigate to Auth0 Dashboard > Applications > Applications, and select your Application to view its settings. Select the Connections view, locate the Social section, and ensure that Amazon is enabled.

Deploy the API and Update the Single-Page Application

Deploy the API

Using the Amazon API Gateway Console, you will again deploy the API and generate a new JavaScript SDK.

At this point, you have made the necessary configuration changes to enable pet purchases. To make this live, copy your newly downloaded SDK over the previous one in your pets folder, as well as your Amazon S3 bucket.

Update the Login Controller Logic to Choose Different Roles for Different Types of Users

The login controller logic uses getOptionsForRole to select different roles for different users. When you obtain the delegation token, you can tell Auth0 which role to use (that is, the user is an admin or not).

In the pets/login/login.js file, modify the role and principal values for the non-admin user for the social user IAM role you just created.

At this point, you should be able to log in using Amazon credentials or the database user you previously created. Notice that the UI lets a social user buy pets, while an admin user can add and remove pets.

To test this functionality, you can temporarily hide the remove button in the UI by removing ng-show="isAdmin" in /pets/home/home.html:

<button ng-show="isAdmin" class="btn delete-btn" ng-click="removePet(pet.id)">remove</button>

Was this helpful?

/

After copying the changes to your S3 bucket, attempt to remove a pet while logged in as a social user.

Update the Home Controller Logic to Allow Social Users to Purchase Pets

In home.js, modify the buyPet function to enable pet purchases:

function buyPet(user, id) {
    var apigClient = getSecureApiClient();

    apigClient.petsPurchasePost({},{userName:user, petId:id})
      .then(function(response) {
        console.log(response);
        $scope.pets = response.data;
        $scope.$apply();
      }).catch(function (response) {
        alert('buy pets failed');
        showError(response);
    });
}
…

Was this helpful?

/

Copy the code to your S3 bucket, log out, and then log back in in as a social user by clicking on the Amazon icon in the Lock login dialog. You may need to click SHOW ALL if your previous login persists in the Lock pane.

Note that, as an Amazon user, you can buy a pet, but not add or remove pets. However, if you log in with a user associated with a Database Connection, you are able to add and remove pets, but not buy pets.

Enforce Role Assignment with Auth0 Rules

In some cases, you might determine the appropriate role using the Application (as shown here), but for security reasons (you might want to prevent the user from assuming a more privileged role than necessary), you might want to determine user privileges on the server-side.

With Auth0, this is done via rules, which are service logic statements you define that are then run during the Auth0 authentication process. For example, you could create rules to:

  • Eliminate the passing of role information from the browser to the Application;

  • Insert role information into the delegation request based on the authentication source.

Enforce Role Assignment

You will add a rule that will check to see if the role requested by the user is allowed, depending on its association with a Social or Database Connection.

  1. Navigate to Auth0 Dashboard > Auth Pipeline > Rules, and select Create Rule.

  2. Choose the Empty rule template

  3. Name the rule AWS Pets (or something similar), then populate the body of the rule with the following JavaScript code:

    function (user, context, callback) {
      if(context.clientID === '{yourClientId}') {
        var socialRoleInfo = {
          role:"arn:aws:iam::<your account>:role/auth0-api-social-role",
          principal: "arn:aws:iam::your account>:saml-provider/auth0"
        };
    
        var adminRoleInfo = {
          role:"arn:aws:iam::<your account>:role/auth0-api-role",
          principal: "arn:aws:iam::<your account>:saml-provider/auth0"
        };
    
        var requestRole = context.request.body.role;
        var requestPrincipal = context.request.body.principal;
        var allowedRole = null;
    
        if(user.identities[0].isSocial === false) {
          allowedRole = adminRoleInfo;
        } else {
          allowedRole = socialRoleInfo;
        }
    
        if((requestRole && requestRole !== allowedRole.role) ||
           (requestPrincipal && requestPrincipal !== allowedRole.principal)) {
            console.log('mismatch in requested role:',requestRole, ':', requestPrincipal);
            console.log('overridding');
        } else {
          console.log('valid or no role requested for delegation');
        }
    
        context.addonConfiguration = context.addonConfiguration || {};
        context.addonConfiguration.aws = context.addonConfiguration.aws || {};
        context.addonConfiguration.aws.role = allowedRole.role;
        context.addonConfiguration.aws.principal = allowedRole.principal;
        callback(null, user, context);
    
      } else {
        callback(null, user, context);
      }
    }

    Was this helpful?

    /
    Be sure to adjust the above code with the correct values for your integration. The fields are Princial ARN, Role ARN, and Client Secret.

  4. Save your changes.

Caveats

  • Rules run at a global scope for every authentication. You should only run the logic on authentication requests associated with a given application (which is why the script used asks for the clientID. Without this information, the logic runs for every authentication request associated with your Auth0 account.

  • Information is passed into the rule with the context and the user.

  • You can extend the objects passed in to the rule. In the code above, the rule checks the body of the request for the role information. The role is set into the context addonConfiguration of the allowed role, which always overrides settings in the request body.

Debug Your Rule

You are ready to debug your rule(s). Select Try this Rule, and you will be presented with a script that tries the rule's logic. Select Try.

You will then be presented with the output of running your rule.