AWS DevOps Blog

Build and Deploy a Federated Web Identity application with AWS Elastic Beanstalk and Login with Amazon.

Today’s post comes from Sebastien, Technical Trainer for AWS. Based in Luxembourg, he helps our customers and partners gain proficiency with AWS services and solutions. You can follow him on Twitter @sebsto.


This article describes why and how your applications can take advantage of Web Identity Federation and how to deploy such applications on Elastic Beanstalk.

In the first part, we will introduce the concept of Web Identity Federation and describe how it can be used to obtain temporary AWS security credentials.

We will walk through a sample web based application in Python to illustrate these concepts, then deploy it to Elastic Beanstalk.

Build an Application that Uses Web Identify Federation

Web Identity Federation is the process of delegating user authentication to a third-party trusted Identity Provider in order to gain temporary privileges to use AWS services.

What is Web Identify Federation?

Let’s imagine you are creating a consumer web site or mobile application that requires user authentication. How would you store user identities, such as usernames and passwords?  You might implement your own identity repository using a relational database or a directory server. You are then responsible to secure the storage of these identities and to implement required lifecycle management functions to create new user identities, to implement password policies, to recover lost passwords, etc.

These functions are complex to implement properly in a secure way and at scale and they do not differentiate your web site or mobile application. Why not focus on your core functionalities instead, delegating user identity storage and management functions to a trusted third party service? This trusted third party service provider would take care of securely storing and managing your user’s identities on your behalf.

When talking about consumer oriented web applications or mobile applications, you can leverage well-known Identity Providers widely available on the Internet, such as Amazon.com, Facebook or Google. Many Internet users already have an account with one or more of these providers.

Delegating user authentication to a third-party, well-known, and trusted Identity Provider will also benefit your customers: they will have one less profile and password to remember, to secure, and to maintain.

Federated Web Identity and Amazon Web Services

Amazon Web Services (AWS) leverages Web Identity Federation and allows your applications to obtain temporary AWS security credentials for federated users.

Let’s imagine your application needs to write documents to an S3 bucket or write to a DynamoDB table on behalf of users.

Instead of using an application-wide set of credentials, shared amongst users, you decide to use different credentials for each user.  These credentials must be limited both in time and in scope to ensure only authorized users get access to AWS services.

Authentication flow in a Federated Web Identity scenario

What is the user experience when someone authenticates to your web site with Web Identify Federation enabled?

1.    On your main authentication page, you will provide a “Login with [Amazon, Facebook, Google]” button to let users choose an Identity Provider.  Let’s assume a user chooses to authenticate with her Amazon.com account.

2.    When clicking that button, your application will redirect her browser to Amazon.com authentication page.

3.    She will enter her amazon.com credentials, typically an email address and a password, then she will click the “Sign In” button on amazon.com

4.    After successful verification of credentials, amazon.com will generate an Identity Token and redirect her browser back to your web site.

5.    Your application will capture this token and call the AWS Security Token Service (STS) API to exchange it for a set of AWS Credentials (an Access Key and a Secret Key).

6.    STS will return a set of credentials limited in time and in scope (based on the permission you defined for this application – more on this later)

The picture below illustrates this entire process.

Description: Macintosh HD:Users:stormacq:Cloud Drive:Documents:AWS:Code:identity-demo:blog:sequence_diagram:Slide1.png


Develop a web-based application using Federated Web Identity

Now that we understand what Web Identity Federation is, the benefits it might bring to your applications and to your users and how we can use it to retrieve AWS security credentials, we will now walk through a web-based application using Web Identity Federation to obtain temporary AWS credentials.

Our sample application let users authenticate with Amazon.com, Facebook or Google Identity Providers.

After user authentication, the application will request from STS AWS security credentials and will use it list S3 buckets in an account.

The application is written in Python, and uses the Flask web framework.

The source for this application is Apache licensed and available at https://github.com/awslabs/eb-wif-sample/.

Step 1 – Deploy the Sample Application to Elastic Beanstalk

Download the deployable zip file from https://github.com/awslabs/eb-wif-sample/releases/download/v1.0/wif-demo-v1.0.zip, then navigate to the Elastic Beanstalk Management Console at https://console.aws.amazon.com/elasticbeanstalk

Click Create New Application, give your app a Name and Description, and click Next:

Choose the Web Server Environment tier, Python configuration and Load balancing, autoscaling environment type and click Next:

In the Application Version step, choose Upload your own and choose the zip file you downloaded earlier:

Click Next to accept all other defaults and launch the environment.

When your application has launched and your environment is Green, copy and paste its link (you’ll need it in the next steps):

 

Step 2 – Register your application with the Amazon Identity Provider

The exact process varies from one Identity Provider to the other but the idea is similar: you need to register your application against the Identity Provider, using their ad-hoc web console (Amazon, Facebook and Google).

During registration, give your application a name and a list of valid callback URLs. These URLs are the ones the Identity Provider will accepts from your application to redirect a user’s browser after a successful authentication.

Your Identity Provider will generate an Application ID that you will use later.

Here we walk through registering our application with Login with Amazon:

1.    If you are a new user, navigate to https://sellercentral.amazon.com and create a Seller Central account when prompted

2.    Navigate to http://login.amazon.com/app-console-login and click the Register new application button:

3.    Give your application a name, description, and paste in your Elastic Beanstalk environment’s URL you copied previously, appending /privacy:

4.    Click Save

5.    Expand Web Settings and click Edit:

6.    In Allowed JavaScript Origins, paste your environment’s URL. In Allowed Return URLs, paste your environment’s URL. Change the protocol from http:// to https://, and append /oauth2callback/amazon, then click Save:

7.    Copy and paste The App ID, Client ID and Client Secret. You’ll need them in later steps:

Step 3 – Define the permissions for this application in AWS

Using AWS Identity & Access Management (IAM), we then need to define the permissions this application will have to use AWS services.

This is done by creating a role for Web Identity Federation scenario and by providing

·      The Application ID created in previous step (this will be the source of trust for this role)

·      The permission document to describe what actions (API calls) will be allowed or denied on AWS services.

·      The Web Application will later use the newly create role identifier (ARN).

Follow the step-by-step instructions in the IAM documentation to create the role.

Enter the below policy in the Custom Policy section of the Set Permissions step. This will allow authenticated users to list the S3 buckets in your account:

{
  "Version": "2012-10-17",
  "Statement": [
    {       
      "Effect": "Allow",       
      "Action": [         
        "s3:ListAllMyBuckets"
      ], 
      "Resource": [
        "*"      
      ]
    }
  ]
}

Note the Role ARN in the Summary tab when you finish
 

Step 4: Configure an SSL Certificate for the Load Balancer

Note: this steps assumes you are using Linux and have OpenSSL and the AWS CLI installed.

Login with Amazon.com has a different requirement from other Identity Providers, it requires the callback URL (the return address after a successful authentication) to be protected by SSL.  This means you need to generate and install a certificate on your application’s HTTPS end point.  In the case of an Elastic Beanstalk deployment with multiple hosts, this will be an Elastic Load Balancer.

For the purpose of this application, we will generate a self-signed certificate.  In a production scenario, you must of course use your own certificate, signed by a trusted third-party.

The following sequence of commands will generate the self-signed certificate with openssl and will upload it to IAM, where our load balancer will be able to pick it up.

## Generate SSL Certificate for ELB

## Generate a key pair
$ openssl genrsa 2048 > ssl.pem

# Generate a signing request with your public key (change the subject !)
$ openssl req -new -days 365 -nodes -out ssl.csr -key ssl.pem -subj "/C=US/L=Washington/O=Amazon Web Services/OU=Training/CN=*.elasticbeanstalk.com"

# Generate the certificate
$ openssl x509 -req -days 365 -in ssl.csr -signkey ssl.pem -out ssl.crt

# Upload it to IAM (AWS CLI must be installed and configured)
$ aws iam upload-server-certificate --certificate-body file://./ssl.crt --private-key file://./ssl.pem --server-certificate-name IdentityDemoCertificate
{
    "ServerCertificateMetadata": {
        "Path": "/",
        "Arn": "arn:aws:iam::577031028568:server-certificate/ IdentityDemoCertificate",
        "ServerCertificateId": "ASCAJXYWIFFFGJNHAE3GG",
        "ServerCertificateName": "IdentityDemoCertificate",
        "UploadDate": "2013-07-12T14:53:14.024Z"
    },
    "ResponseMetadata": {
        "RequestId": "c743145c-eb02-11e2-971b-a352653ac2bd"
    }
}

# You can test IAM to check your certificate
$ aws iam get-server-certificate --server-certificate-name IdentityDemoCertificate
{ … } # ouput suppressed for brevety

You then need to update Elastic Beanstalk’s application configuration to include the SSL certificate:

1.    Click your Elastic Beanstalk environment’s configuration:

2.    Click the gear next to Load Balancing:

3.    Choose your certificate from the dropdown and click save:

4.    Wait for the environment update to complete.

Step 5 – Configure Your App

1.    Navigate to your application in Elastic Beanstalk, click the Configuration link, then click the Software Configuration gear:

2.    Fill in the AMAZON_APP_ID, AMAZON_APP_SECRET, and AMAZON_ROLE_ARN values based on what you created in previous steps. Note: AMAZON_APP_ID and AMAZON_APP_SECRET refer to the client ID and secret you created in Step 2 above:

3.    Scroll to the bottom and click Save

Step 6 – Test it Out

Now you’re ready to test your application and its integration with Login with Amazon:

1.    Click your environment’s URL in Elastic Beanstalk:

2.    Choose the Login with Amazon button:
 Description: https://images-na.ssl-images-amazon.com/images/G/01/lwa/btnLWA_gold_156x32._V372226923_.png

3.    Sign in:

4.    You’ll be redirected and your application will use your Amazon authentication to list all S3 buckets in your account:


About the Code: Redirect users to IDP authentication URL

When users will click on the “Login with…” button, the application will direct them to selected IDP’s authentication page.  The IDP needs a few parameters to recognize your application and to know what to do after a successful authentication.

Exact details will vary from one Identity Provider to the other, but the common parts are:

·      The base URL for the IDP authentication page (www.amazon.com/ap/oa) in this example.

·      The ID of the client application, created in Step 1 above.

·      The Scope of the request.  This will tell the IDP what type of action will be authorized after successful authentication.  In this example, we are telling Amazon.com that our application might request information from user profile.  This information will be give to the user when she will authenticate at amazon.com

·      The callback URL.  The address the IDP will redirect user’s browser after a successful authentication.  This address must match the address provided when registering your application in Step 1 above.

·      The type of response expected – this is dependent and specific to each IDP.

The code bellow constructs the authentication URL for Amazon.com authentication.

Text Box:     def loginURL(self):
        loginURL = 'https://www.amazon.com/ap/oa?'
        loginURL = loginURL + 'client_id=' + self.APP_ID + '&'
        loginURL = loginURL + 'scope=profile&'
        loginURL = loginURL + 'redirect_uri=https%3A%2F%2F' +
                              request.headers['Host'] +
                              '%2Foauth2callback%2Famazon&'
        loginURL = loginURL + 'response_type=token'
        return loginURL

Notice that parameters must be URL-encoded.

The request.headers[‘Host’] construct will return the host part from the current URL.  This allows using the same code for development, test and production environments.

This sample code uses Implicit Grant method (response_type=token) to directly receive the access_token from Amazon Identity Provider.  Applications may also use two-steps, more secured, Authorization Code Grant method to first ask for an authorization code and then to securely exchange the authorization code for an access_token in background.  More details are available in the Login With Amazon developer documentation, starting on page 15.

The APP_ID parameter is your application id returned by the Identity Provider during step 1 above.  If you look in details at the code, you will see the APP_ID – used to build to login URL – and the APP_SECRET – used to query the IDP’s profile service.

About the Code: Implement callback URL

Your application will retrieve the ‘code‘ parameter passed in the callback URL.

Text Box:     @application.route('/oauth2callback/<provider>')
    def OAuth2Callback(provider):

       access_token  = request.args.get(‘access_token’, 'unknown') 
       if (access_token  == 'unknown'):
           # error handling 
	     result = …

       # Redirect user to post-auth page
       result = … 
            
    return result

The application will send this code to STS to exchange it for temporary credentials.

p class=MsoNormal>At this stage the application can also use the access_token to call any IDP specific API.  Typically, an application will retrieve the username, first name, last name and email address from the IDP profile service.

About the Code – Call STS and exchange code for temporary security credentials

The last step in order to receive AWS security credentials is to call STS. Like all AWS API, this call can be done with a plain HTTP request or using one of the higher-level language SDKs.  Because our application is written in Python, we are using the Python SDK, Boto.

Text Box:    def doGetAccessCredentials(self, access_token, profile):
      from boto.sts.connection import STSConnection

      conn = STSConnection(anon=True)

      # the identifier of the role created in Step 2 above
      roleARN = self.getRoleARN()

      # any name to identify this session (32 chars max)
      email   = profile['email'][:32]

      assumedRole = conn.assume_role_with_web_identity(
                       role_arn=roleARN,
                       role_session_name=email,
                       web_identity_token=access_token,
                       provider_id='www.amazon.com')

      return assumedRole.credentials.to_dict()

This code does the following:

1.    Open a connection to STS.  By nature, this connection is not authenticated

2.    Call assume_role_with_web_identity API with the following parameters:

·      role_arn : the identifier of the IAM role create in Step 2 above

·      role_session_name : any name you would like to use to identify this session.  We are using user’s email address fetched from user’s profile as provided by the IDP.

·      web_identity_token : the access_token received from IDP during Step 4 above

·      provider_id : an internal parameter used to identify OAuth2 providers (facebook and amazon)

This call returns a Credentials object that contains the Access Key and Secret Key.

About the Code – use the credentials to call AWS API

Once your application receives the AWS Access Key and Secret Key, it can calls AWS API, provided it was granted proper authorization to do so in Step 2 above.

In this example, the application is using S3 API provided by Python SDK to list all buckets in an account:

Text Box: def doListBuckets(bucketName, credentials):
    from boto.s3.connection import S3Connection
    from boto.s3.bucket import Bucket
        
    conn = S3Connection(credentials['access_key'],
                        credentials['secret_key'],
                        credentials['session_token'])
    buckets = S3Connections.get_all_buckets()
    return [bucket.name for bucket in buckets]

Client Side vs Server Side implementation

This sample application’s Python code is executed on the server side.  Recently, the AWS SDK team released a developer preview of a client-side, JavaScript-based, SDK.  This SDK allows to access STS and S3, amongst other services.

What are the main criteria that would let you decide in favor of a client-side implementation or a server-side implementation?

Client-side has advantages in terms of scaling – no server side resources required – and therefore pricing.

But server-side implementations bring you the following:

·      It is easier to validate input or output parameters against backend systems;

·      It is easier to log interactions with STS for auditing or tracking purpose;

·      It is easier to hide access key and secret key from end users, using HTTP Session storage for example;

Also notice that JavaScript SDK is a Developer Preview release, not a fully supported SDK yet.  It only supports STS, S3, DynamoDB, SQS and SNS.

We let you choose whatever makes more sense for you, depending on your context, your application, and your requirements. Feel free to experiment with both SDKs and pick up the right tool for the job.