AWS Startups Blog

Dynamic Websites Using the AWS SDK for JavaScript in the Browser

By Matt Yanchyshyn, Solutions Architect, AWS


301 level guidance from an AWS Solutions Architect: Learn how to leverage Amazon S3 and the AWS SDK for JavaScript in the Browser to create dynamic websites using only static HTML and JavaScript.

AWS SDK Key

Introduction

Late last year we released a developer preview of our AWS SDK for JavaScript in the Browser. [http://aws.typepad.com/aws/2013/10/developer-preview-aws-sdk-for-JavaScript.html] With it, you can develop rich, browser-based applications that make direct calls to AWS services without the need for server-side code. Combined with S3-hosted websites this creates an exciting opportunity: cost-effective, highly scalable and fully dynamic websites with few or no application servers.

Imagine that you’re in marketing and you want to record mouse-click and hover events over certain parts of a web page, such as advertisements. Today you’d typically do this by capturing the browser’s DOM events, sending the data asynchronously using JavaScript to back-end application servers and then populating a queue or database. With the AWS SDK for JavaScript in the Browser you can sidestep much of this workflow, writing the events directly to an AWS-hosted service such as SQS, negating the need for any intermediary application servers. This change has the potential to dramatically reduce the cost and operational complexity of your application because there are fewer applications and Amazon EC2 instances to manage.

Another scenario is a real-time news event such as an election. In the past you would usually hold the election data in a database, often fronted by a cache, and present that data to the web tier via an application server. The web tier would then deliver the data to the customer browser that would render it into visual representations, such as election district maps or graphs. With the AWS SDK for JavaScript in the Browser you could have the customer’s browser reach right into the AWS-hosted cache or database, bypassing the application servers, pulling the data into an election map or graph that is rendered in real-time using popular JavaScript libraries such as D3.

You might be thinking, if it’s all client-side, doesn’t this mean that I have to stick my AWS credentials in the code? This is the best part: you can leverage techniques like web identity federation (WIF) and SAML federation to authenticate users. This has the benefit of negating the need to put credentials in your code and also giving you the ability to track usage. Out of the box AWS supports several web identity providers, including Amazon, Google and Facebook. You can also use any SAML provider, which we’ll discuss in a later blog post.

Today the AWS SDK for JavaScript in the Browser provides access to all of the functionality of AWS services, and the CloudFront-hosted build supports Amazon S3, Amazon SQS, Amazon SNS, Amazon Kinesis, and Amazon DynamoDB. You can also start experimenting with using other AWS services, if you’re working in an environment not bound by CORS restrictions. [https://docs.aws.amazon.com/AWSJavaScriptSDK/guide/browser-building.html]

Example Part 1: Login with Amazon
In this first example we’ll use Web Identity Federation (WIF) with Login with Amazon [http://login.amazon.com] to allow users to access AWS resources that you define in an IAM role. Leveraging a web identity provider (like Amazon, Facebook or Google) for authentication has several benefits: You don’t need to put AWS credentials in your code because after a successful authentication with one of the providers, your application can use temporary credentials delivered by the AWS Security Token Service (STS). This provides secure access to only those AWS resources that you define in an associated IAM role. In addition, you gain insight into who is using your application since logins are captured by the web identity provider. Finally, you can be very granular about what AWS resources are accessed, such as a specific Amazon S3 bucket or DynamoDB table, and easily adjust permissions as needed.

To begin, go to http://login.amazon.com and register a new application. Note the Client ID and Application ID once it’s created. You’ll also need to add an allowed JavaScript Origin, i.e., the location that will host the application that authenticates with Login with Amazon. In this example, we’ll host the HTML and associated JavaScript on Amazon S3, so enter https://s3.amazonaws.com. (At this time you can optionally add https://cloudfront.net as you may consider using Amazon CloudFront as a CDN in front of your static web application as your traffic increases.)

Next, create a new DynamoDB table using the AWS Management Console or AWS CLI. Give the table any name you like, with a hash key of browser and a range key of version. To minimize costs as you’re learning, set provisioned throughput to a very low level (1-5 is fine). In the details tab when you select the table, note the Amazon Resource Name (ARN). For more information about DynamoDB and creating tables, please refer to the AWS documentation: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStartedCreateTables.html

Finally, create an AWS Role for WIF in the AWS Management Console. Click on Services; then IAM; then Roles. Click on Create New Role, give it any name you choose, and select Role for Identity Provider Access and then Grant access to web identity providers. Set the Identity Provider to Login with Amazon and enter the Application ID (not the Client ID) that you received when you created a new Login with Amazon application above. On the next page, Establish Trust, click Continue.

You should now be in the Set Permissions step. Select Custom Policy to define an IAM policy. You can give it any name you want. This policy will determine which AWS resources your application users can access after Login with Amazon authenticates them. In these examples we’ll be reading and writing to the DynamoDB table that you just created, so add a custom IAM policy that looks something like this:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:*"
      ],
      "Resource": [
       "arn:aws:dynamodb:us-east-1:123456789:table/my-new-table"
      ]
    }
  ]
}

Note that dynamodb:* allows all actions, including deleting the table. You may want to further restrict the actions allowed on your DynamoDB table to only those needed for your application. For more information about IAM and creating policies, please refer to the AWS documentation: http://docs.aws.amazon.com/IAM/latest/UserGuide/PermissionsAndPolicies.html

Now we’re ready to start using the AWS SDK for JavaScript in the Browser and wrap it in some basic HTML.

Go to https://s3.amazonaws.com/startup-blog-examples/example-login.html, view the source in your browser and copy it into an editor. Replace the bold values below with your own AWS IAM role ARN and Login With Amazon Client ID:

// IAM Role that you create for Login With Amazon
var roleArn = 'arn:aws:iam::123456789:role/example';
// Login With Amazon Client ID
var clientId = 'amzn1.application-oa2-client.abc123456789';

Save the modified code as index.html and upload it to the Amazon S3 bucket that you created earlier. Note that enabling Static Website Hosting on your Amazon S3 bucket is optional for these examples. Also make sure that you set the permissions of the file to public-read using the CLI or AWS Management Console. Alternately you can set an Amazon S3 bucket policy to allow read-only public access to all files in the bucket you upload your example to. For more information about Amazon S3, bucket policies, and static website hosting, please refer to the AWS documentation: http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingBucketPolicies.html

Point your web browser to the Amazon S3 file that you uploaded, e.g.: https://s3.amazonaws.com/yourbucket/index.html. You must use HTTPS or Login With Amazon won’t work!

Click the Login link that appears and you should be prompted to authenticate using your Amazon credentials. Once you authenticate you should see a pop-up in your browser that says Logged in.

Example Part 2: Read and Write Data to a DynamoDB Table Building on the first example, we’ll add some code to read and write items to the Amazon DynamoDB table that you created earlier. Because the IAM role that you associated with the Login with Amazon web identity provider allows full access to the DynamoDB table, users who are successfully authenticated using the code in the previous example will be granted read and write access for the duration of their login session.

We will use JavaScript to detect the web browser that the client viewing this application is using and then add the browser name and version to a table in DynamoDB.

Go to https://s3.amazonaws.com/startup-blog-examples/example-dynamodb.html, view the source and copy the code into an editor. Like above, replace the IAM Role ARN and Client ID with your own. This time also replace the bolded ddbTable value with the name of the DynamoDB table that you created earlier. You can also specify the region that you are using:

AWS.config.region = 'us-east-1';
var ddbTable = 'browser-metrics',

Have a look at the code. Note that this time we’ve extended the amazon.login.retrieveProfile function to create a new DynamoDB object and issue and updateItem API call via the SDK, passing in the detected browser details as parameters. We’ve also added a new updateBrowserCount function that outputs the results of the DynamoDB query.

Upload the updated HTML to your Amazon S3 bucket and reload the page. You should see something like this:

Updated HTML to S3 confirmation

Example Part 3: Graph the Data in Amazon DynamoDB by Using D3
In the third example we’ll use the data that we insert into the DynamoDB table with each page load to display a graph generated using the popular JavaScript library, D3. [http://d3js.org] We’ll also use NVD3 to make the D3 graphing even easier by leveraging its pre-made chart components. [http://nvd3.org].

Go to https://s3.amazonaws.com/startup-blog-examples/example-dynamodb-d3.html, view source and copy the code into an editor.

There are a lot of changes in this code: we’ve added some new JavaScript dependencies, a <div> placeholder to display our pie chart and a new drawChart function draw the NVD3 pie chart object.

Like you did in the previous examples, change the value of roleArn to your IAM Role and change the Login With Amazon Client ID to match your own. You’ll also need to change the value of TableName in the updateBrowserCount() function to point to your DynamoDB table. Note that ‘browser-metrics’ appears twice in the code.

amazon.Login.retrieveProfile(response.access_token, 
  function(response) {
    // global DynamoDB object
    db = new AWS.DynamoDB();
    // global array to hold graph data
    d3Data = [];
    var detectedBrowser = identifyBrowser();
    var params = {
      TableName: ‘browser-metrics’,

function updateBrowserCount() {
  db.scan(params = {TableName: 'browser-metrics'},

After uploading the updated code to Amazon S3 and reloading the page, you should see something like this:

Updated pie chart after code change

Example Part 4: Fine-grained Access Control with Unique Federation IDs
In the final example, we’ll demonstrate how to tighten-up our IAM policy so that we limit what users who have been authenticated using a web identity provider can see. You can use this technique, for example, to isolate user resources such as private user buckets in S3 or data in a DynamoDB table. Let’s rework the last example so that we only graph the display a pie chart of browsers used by the individual user, not everyone who visited the page.

First of all, create a new DynamoDB table with a hash key of userId and a range key of browser.

Next we’ll need to refine the IAM policy. In the AWS Management Console, go to the IAM service, click Roles and select the role that you created earlier. Click the Permissions tab and then Manage Policy beside the policy you created earlier. Replace it with this:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
    "dynamodb:Query",
        "dynamodb:UpdateItem",
        "dynamodb:Scan"
      ],
      "Resource": [
        "arn:aws:dynamodb:us-east-1:123456789:table/your-example-table"
      ],
      "Condition": {
        "ForAllValues:StringEquals": {
          "dynamodb:LeadingKeys":["${www.amazon.com:user_id}"],
          "dynamodb:Attributes": [
            "userId", "browser", "count" 
          ]
      },
      "StringEqualsIfExists": {
        "dynamodb:Select": "SPECIFIC_ATTRIBUTES"
      }
    }
  },
  {
    "Effect": "Allow",
    "Action": ["dynamodb:Scan"],
    "Resource": [
      "arn:aws:dynamodb:us-east-1:123456789:table/your-example-table"
    ],
    "Condition": {
      "ForAllValues:StringEquals": {
        "dynamodb:Attributes": ["browser"]
      },
      "StringEqualsIfExists": {
        "dynamodb:Select": "SPECIFIC_ATTRIBUTES"
      }
    }
  }]
}

The entry “dynamodb:LeadingKeys”: [“${www.amazon.com:user_id}”] limits users to querying and updating only items that have a hash key corresponding to their Amazon unique user ID that we retrieve after a successful authentication with Login with Amazon. In addition, we restrict users to only being able to retrieve only a specific subset of attributes — in this case, userId, browser and count. The second Allow action lets users scan for browsers used by other users but not list their user IDs or browser counts. This is an example of mixing row-level and column-level access control.

Next, go to https://s3.amazonaws.com/startup-blog-examples/example-dynamodb-d3-granular.html, view the source and copy the code into an editor. Note how in the amazonAuth function we have updated the params variable to reflect the new, more granular IAM policy. Also for simplicity we also merged the browser and version attributes into a single string:

amazon.Login.retrieveProfile(response.access_token, 
function(response) {
  // global DynamoDB object and an array to hold graph data
  db = new AWS.DynamoDB();
  d3Data = [];

var detectedBrowser = identifyBrowser();
var params = {
‘TableName’: ‘your-new-table’,
‘Key’: {
‘userId’: { ‘S’ : response.profile.CustomerId},
‘browser’: { ‘S’ : detectedBrowser.n + ‘-‘
+ detectedBrowser.v}
},
‘AttributeUpdates’: {
‘count’: {
‘Action’: ‘ADD’, ‘Value’: { ‘N’: ‘1’}
}
},
‘ReturnValues’: ‘UPDATED_NEW’
}
}

Lastly, we the code so that you we a DynamoDB query operation instead of a full table scan, limiting the items to only those with the hash key corresponding to the Amazon unique ID. We output the return values to a new list to the right of the chart that shows browsers used by all users, without exposing their user IDs or browser counts.

Make all the usual changes to your code, including subsituting your IAM Role ARN for the one in the code, changing the Login With Amazon Client ID and changing any references to DynamoDB tables to point the new one that you just created.

Upload the file to Amazon S3 and give it a spin. Have a friend with another Amazon account do the same. If everything was setup correctly, then only the browsers that your account uses will be reflected on the pie chart while in the list you should see all browsers used by all users.

To recap, in this post we covered the following topics:

  • How to setup Login with Amazon WIF
  • How to read and write to DynamoDB
  • How to use the D3 library to graph data stored in DynamoDB
  • How to get more granular access control using IAM policies that reference unique federated IDs

In a follow-up post we’ll cover some more advanced topics including these:

  • How to setup SAML federation using an external provider
  • How to make your credentials persist using HTML5 web storage