Dynamic Websites Using the AWS SDK for JavaScript in the Browser, Part 2: Auth0 and Token-based Authentication

301 level guidance from an AWS Solutions Architect

Introduction

The previous post introduced the AWS SDK for JavaScript in the Browser and showed you how to set it up with Login with Amazon for web identity federation. We used this technique to gain read and write access to an Amazon DynamoDB table without including AWS credentials in our JavaScript code. We then added NVD3 to visualize the query results. Finally we demonstrated how to lock down your data by adding fine-grained access control to your Amazon DynamoDB tables with IAM policies that reference unique federated user IDs.

Identity Federation with Auth0

Many modern web and mobile applications have turned to third parties for authentication and identity management to reduce sign-in friction and offload the burden of maintaining their own identity stores. End users have established trust with companies such as Amazon, Google, Twitter, Microsoft and others. They want to use these companies’ systems to login to their applications for security reasons, or simply because they can’t be bothered to remember another password. In addition, many corporate users don’t want to have to manage multiple logins for their ever-increasing ecosystem of applications at work; they want to use a single set of corporate credentials for everything.

Auth0 Setup

To get started, open an account on https://auth0.com or via the AWS Marketplace. You can use the free account for this example.

AWS Setup

Auth0 has developed a quick start guide for many common technologies such as Node.js, Angular.js, Java, PHP, ASP.NET, and many others. For this example we’ll be using jQuery + AWS API.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:Query",
        "dynamodb:UpdateItem",
        "dynamodb:Scan"
      ],
      "Resource": [
        "arn:aws:dynamodb:us-east-1:YOUR-AWS-ACCOUNT-ID:table/browser-metrics-per-user"
      ],
      "Condition": {
        "ForAllValues:StringEquals": {
          "dynamodb:LeadingKeys": ["${saml:sub}"],
          "dynamodb:Attributes": [
            "userId", "browser", "count" 
          ]
        },
        "StringEqualsIfExists": {
          "dynamodb:Select": "SPECIFIC_ATTRIBUTES"
        }
      }
    },
    {
      "Effect": "Allow",
      "Action": ["dynamodb:Scan"],
      "Resource": [
        "arn:aws:dynamodb:us-east-1:368101875365:table/browser-metrics-per-user"
      ],
      "Condition": {
        "ForAllValues:StringEquals": {
          "dynamodb:Attributes": ["browser"]
        },
        "StringEqualsIfExists": {
          "dynamodb:Select": "SPECIFIC_ATTRIBUTES"
        }
      }
    }
  ]
}

Token-based Authentication

Tokens have several advantages over cookies when you’re working with Amazon S3-hosted, client-side applications written in only HTML and JavaScript:

HTML/JavaScript

You can download the full source code for the examples below by viewing our sample login page and viewing the page source.

window.config = {
  role: 'AWS IAM role ARN that you copied from the IAM console',
  principal: 'Provider ARN of the SAML IdP that you created for Auth0',
  domain: 'your.auth0.com Domain',
  clientID: 'Auth0 application clientID',
  targetClientId: 'Auth0 application clientID',
  region: 'us-east-1'
};
<!DOCTYPE html>
<html lang="en">
<head>
  <title>AWS Javascript Browser SDK: Auth0</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
  <script src="//cdn.auth0.com/w2/auth0-widget-5.0.min.js"></script>
  <script src="js/config.js"></script>
  <script src="js/store.min.js"></script>
</head>
<body>
  <a href="#" class="btn-login">Login</a>
  <script>
  // check for HTML5 Web Storage (localStorage) support using store.js library
  init()
  function init() {
      if (!store.enabled) {
          alert('Local storage is not supported by your browser. Please disable "Private Mode", or upgrade to a modern browser.')
 return
      }
  }
  // reset state
  store.clear();
 </script>
</body>
</html>
     $(document).ready(function() {
       var widget = new Auth0Widget({
         domain: window.config.domain, 
         clientID: window.config.clientID,
         callbackURL: location.href,
         callbackOnLocationHash: true
       });       $('.btn-login').click(function(e) {
         e.preventDefault();
         widget.signin({
           popup: true
         }, null, function(err, profile, token) {
           if (err) {
             console.log(err);
           } else {
             store.set('userToken', token);
             store.set('profile', JSON.stringify(profile));
             location.href = 'page2.html';
           }
         });
       });
     });
<!DOCTYPE html>
<html lang="en">
<head>
   <title>AWS Javascript Browser SDK: Auth0</title>
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
   <script src="//sdk.amazonaws.com/js/aws-sdk-2.0.8.min.js"></script>
   <script src="//d2cdc0w3k7b2of.cloudfront.net/identify_browser.js"></script> 
   <script src="//d2cdc0w3k7b2of.cloudfront.net/d3.min.js" charset="utf-8"></script>
   <script src="//d2cdc0w3k7b2of.cloudfront.net/nv.d3.min.js"></script> 
   <link href="//d2cdc0w3k7b2of.cloudfront.net/nv.d3.min.css" rel="stylesheet" type="text/css">
   <script src="js/store.min.js"></script>
   <script src="//cdn.auth0.com/w2/auth0-3.1.min.js"></script>
   <script src="js/config.js"></script>
</head>
<body>
 Hello, world.
</body>
</html>

Getting Access to AWS via Auth0

Now that Auth0 authentication is working and we’ve learned how to write the JWT (token) into localStorage, we can use that token to assume an AWS IAM role and receive temporary AWS credentials from AWS STS. This works by taking the token stored in localStorage and passing it to the Auth0 authentication API, which then calls out to AWS IAM and identifies itself as a valid SAML provider for the account. AWS STS then returns a set of temporary credentials that can be used to access AWS resources, restricted by the IAM role that you associated with the IAM SAML identity provider for Auth0.

<script type="text/javascript">
    function get_aws_token(options, callback) {
      var auth0 = new Auth0({
        domain: options.domain,
        clientID: options.clientID,
        callbackURL: 'dummy'
      });
      auth0.getDelegationToken(options.targetClientId, user.get().token, {
      role: options.role,
      principal: options.principal
    }, callback);
  }  window.user = {
    get: function() {
      if (!store.get('profile')) return;
      return {
        profile: JSON.parse(store.get('profile')),
        token: store.get('userToken'),
        aws_creds: store.get('aws_creds') ?
          JSON.parse(store.get('aws_creds')) : undefined
      };
    }
  };  function initialize_security_context(callback) {
    if (!user.get()) return location.href = 'index.html';
    // if token is not expired, return 
    if (user.get().aws_creds && new Date(user.get().aws_creds.Expiration) > new Date())
      return callback();    get_aws_token(window.config, function(err, delegationResult) {
      if (err) return location.href = 'index.html';
      store.set('aws_creds', JSON.stringify(delegationResult.Credentials));
      callback();
    });
  }  initialize_security_context(function() {
    $('.btn-logout').on('click', function() {
      store.clear();
      location.href = 'index.html';
    });
    alert("It's working!");
  });
</script><div id="logout" style="clear:both"> 
 <a href="#" class="btn-logout">Logout</a>
</div></body>
</html>

Putting It All Together

It may not look like much so far, but at this stage we’ve already built a fully functional dynamic website that can maintain its state across a refresh or page navigations using localStorage and make secure calls to AWS APIs with temporary credentials granted through identity federation. All that’s left to do is to merge this code with the example that we built last time using DynamoDB.

Conclusion

After we published the first blog post on this topic, someone on Twitter called it “0-tier architecture.” That’s maybe not 100% accurate, since we’re still relying on Amazon S3 for hosting the HTML and JavaScript files plus Amazon DynamoDB for database tasks, but it does capture the essence of what we’ve built: Auth0’s powerful but easy to use identity tools combined with a token-based approach allows us to build a dynamic website with persistent sessions where almost everything happens on the client side, removing the need for Amazon EC2–based application servers.

AWS Startup Collection

For startups building on AWS.

    AWS Startups

    Written by

    Amazon Web Services Startup Program. Follow @AWSstartups.

    AWS Startup Collection

    For startups building on AWS.