Problem solve Get help with specific problems with your technologies, process and projects.

AWS authentication needed to protect a serverless app

Using Amazon Cognito in conjunction with an "authenticate" function in AWS Lambda can help secure vulnerable back-end information and infrastructure.

Amazon Cognito is a useful tool for running code from the AWS software development kit in the browser. This SDK...

connects a serverless application directly to AWS without any middleware to the underlying databases. That means no EC2 instances are needed to listen for requests, and there are no idle servers just waiting to serve up requests.

But it can be tricky to support validation and authorization of user credentials, and it's nearly impossible to support a certain restriction without writing some of your own back-end code. Without proper AWS authentication and authorization, an enterprise exposes its entire back-end infrastructure to all clients. And that code is simply downloaded to a client system when building a Web application, making it exceptionally easy to gain access to unprotected resources. To prevent unwanted access, developers need to support proper authorization and set up restricted user groups so the access credentials provided to the user only have the access desired.

For example, if a developer authorizes a set of users credentials to read from Amazon DynamoDB, that entire domain can be read by the user -- meaning any user-specific information in that database can be downloaded by a user, even if it's not their own information. Alternatively, if a site requires payment, developers may only want to allow users who have paid to access certain functionality in their system. To do this, developers need to support some sort of look-up method to authenticate only paid users.

In my environment, there are three types of users: unauthenticated, authenticated and administrators. Unauthenticated users have a very limited set of access -- they can see general statistics, but cannot dig too deeply into any specific data. Authenticated users can see all of the data, but cannot edit anything. And administrators have full access to view and update data. In my scenario, administrators include those with the email domain -- aci.info.

For all of this, I use Google+ login, which makes means I don't have to worry about any user credential issues -- no lost passwords or lost accounts, for example. All of that is handled right through Google, and I just get back a token to verify the user's identity. Cognito even integrates with Google login.

Unfortunately, Amazon Cognito does not allow administrators to limit permissions based on anything other than "they logged in with a Google account." I could set up a server to host my own OpenID authentication, but the goal here is to go to an entirely serverless app, and I want to continue to use the Google+ login mechanism.

Setting up two identity pools

The easiest way to handle multiple permissions is to set up separate identity pools for different groups of users.

I created a "public" identity pool and an "admin" identity pool in Cognito. First, I will authenticate the user to the "public" identity pool so that I can have AWS credentials call my "authenticate" function. This will allow me to use developer authenticated identities on a specific subset of users to advance them to the admin identity pool. The "authenticate" function can also inform users of accessible credentials.

Authenticating the client

Once I set up identity pools, clients must be authenticated to the correct pool. It's easiest to start by authenticating to the public identity pool, and then use those credentials to call an "authenticate" function in AWS Lambda, which may or may not let them move to a different identity pool.

Here's a look at the client-side code involved in this:

login.js:

 

/**

* AWS Cognito Login

*/

var clientID = ‘XXX.apps.googleusercontent.com'; // Google client ID

var lambda;

 

document.getElementById('login').setAttribute('data-clientid', clientID);

function loginToGoogle(response) {

if (!response.error) {

    console.log('ID Token', response.id_token);

    // Basic Access

    AWS.config.region = 'us-east-1'; // Region

    AWS.config.credentials = new AWS.CognitoIdentityCredentials({

      AccountId: ‘XXXXX',

      IdentityPoolId: 'us-east-1:XXXX',

      Logins: {

        'accounts.google.com': response.id_token

      },

    });

 

    lambda = new AWS.Lambda();

    lambda.invoke({

      FunctionName: 'authenticate',

      Payload: JSON.stringify({ google: response.id_token }),

    }, function(err, loginResp){

      if(loginResp && loginResp.Payload){

        var credentials = JSON.parse(loginResp.Payload);

        console.log('Config Credentials', credentials);

AWS.config.credentials = new AWS.WebIdentityCredentials({

RoleArn: credentials.RoleArn,

          WebIdentityToken: credentials.Token,

        });

        lambda = new AWS.Lambda();

      }

 

      // Load the app.js

      var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;

      po.src = 'app.js';

      var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);

 

    });

  } else {

    console.log('There was a problem logging you in.', response);

  }

}

 

(function() {

  var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;

  po.src = 'https://apis.google.com/js/client:plusone.js';

  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);

})();

This code is similar to the basic example for using Cognito with the Google+ login, except it has a second step. After an administrator authenticates with Cognito for the public identity pool, he calls the Lambda function "authenticate." The Google Authentication Token is passed along, and the administrator gets back a Role ARN and WebIdentityToken.

Important: The administrator must use the "WebIdentityCredentials" for the token received from your server-side call of "getOpenIdTokenForDeveloperIdentity," otherwise AWS will likely return a "Please provide a valid public provider" error.

This method will allow developers to use customized Lambda "authenticate" function to validate the token from Google and either return a set of credentials for the Web client or deny access. Here's what the "authenticate" Lambda function looks like:

var AWS = require('aws-sdk');

var request = require('request');

var GOOGLE_CLIENT_ID = ‘XXXXXX.apps.googleusercontent.com';

var GOOGLE_URL = 'https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=';

var idPoolID = 'us-east-1:XXXXXXX';

var roleArn = 'arn:aws:iam::XXXXXX:role/Cognito_AdminStaffAuth_Role';

var cognitoidentity = new AWS.CognitoIdentity({ region: 'us-east-1' });

 

module.exports = function(credentials, context){

  request.get(GOOGLE_URL + credentials.google, function(err, resp, body){

    if(body){

      body = JSON.parse(body);

    }

    // Anything with an email at cannabiz.media is allowed to access admin functionality

    if(body && body.email && body.email.match(/@aci\.info$/) && body.aud === GOOGLE_CLIENT_ID){

      cognitoidentity.getOpenIdTokenForDeveloperIdentity({

        IdentityPoolId: idPoolID,

        Logins: {

          'login.cannabiz.media': body.kid,

        },

      }, function(err, data){

        if(err){

          context.fail(err);

        } else {

          data.RoleArn = roleArn;

          context.done(err, data);

        }

      });

    } else {

      context.fail('Invalid Account');

    }

  });

};

This function uses some third-party modules that aren't included in AWS Lambda, so those will need to be included in the .zip file that you upload to Lambda. I use the very convenient grunt-aws-lambda NPM package to automatically build and deploy packages to Lambda.

This code does a few important things. First, it verifies the token using Google's API and then ensures the token is valid for the application. It also checks that there is a valid email address associated with that account and that the account is using a valid email domain. To look up an email address, look in the DynamoDB database; a user ID can be returned from there. It can also determine to which identity pool a specific user belongs. Finally, it uses "getOpenIdTokenForDeveloperIdentity" to generate an ID token for this user. It passes that token as well as the authenticated role to the client for it to use in AWS authentication.

The last important piece is the HTML button for Google+ login:

<span

  id="login"

  class="g-signin"

  data-height="large"

  data-callback="loginToGoogle"

  data-cookiepolicy="single_host_origin"

  data-scope="profile email">

</span>

The data-callback needs to link to the function in the login.js script, and the data-scope needs to include "email," so that calls to the Google Token API will return the email. If that isn't present, the user's email address will be unreadable from the provided token. Developers can include as many different scopes as wanted in that parameter, but generally "profile" and "email" include enough information about the user to determine if he should have access.

Why use this pattern?

All of this may seem a little unusual to authenticate a few users. Most developers would simply set up an application authentication mechanism like Express.js to sit on top of a framework. But what if the developer wants a serverless app to scale indefinitely?

Lambda's back end automatically provisions hardware, and AWS users only pay for actual usage. So, if the system doesn't get any hits, the enterprise is not paying for idle servers. When use begins, AWS simply charges for the traffic that actually hits the systems, and the underlying Lambda microservices architecture will scale automatically.

This pattern also assumes the developer will split the system up into microservices. After all, each Lambda function will have a maximum of 60 seconds to process. It encourages developers to write small pieces of an application that are independent, which allows them to update each piece individually. It also lets them use the same APIs on their Web applications as they would on a mobile application, bringing all user-facing applications one step closer to running directly on the cloud.

Where do we go from here?

Now that the workflow is set up to authenticate a user via Google -- and users can be authorized via the Lambda "authenticate" function -- a developer can set up as many different identity pools as necessary, with varying levels of permissions. Developers can also use this workflow style to tie in other authentication mechanisms if needed; be aware that some credentials are needed to access the Lambda function to check AWS authentication.

Configure the AWS Identity and Access Management (IAM) roles for each identity pool to define their access. I recommend allowing access to specific IAM functions for all user groups to prevent anyone from getting unauthorized access to resources. In addition, if the application is directly uploaded to Amazon Simple Storage Service (S3), a back end is no longer needed at all. S3 will serve the static application files, while Lambda executes the back-end processes.

Next Steps

AWS Lambda shears cloud development time

EC2 Container Service, AWS Lambda changing cloud provisioning

AWS Multi-Factor Authentication protects data

This was last published in October 2015

Dig Deeper on AWS instances strategy and setup

Join the conversation

1 comment

Send me notifications when other members comment.

By submitting you agree to receive email from TechTarget and its partners. If you reside outside of the United States, you consent to having your personal data transferred to and processed in the United States. Privacy

Please create a username to comment.

What problems have you encountered running serverless applications?
Cancel

-ADS BY GOOGLE

SearchCloudApplications

TheServerSide.com

SearchSoftwareQuality

SearchCloudComputing

Close