AWS Mobile Blog

Deploy and Secure REST-based Mobile Apps with AWS Mobile Hub

This post was written by Brice Pelle, AWS Technical Account Manager.

RESTful APIs are used today to power many mobile apps. Using RESTful interfaces allows developers to decouple the frontend application from the backend systems and provides standard interoperability between the two components. Controlling access to resources can be hard to set up. This usually requires addressing authentication and authorization concerns.

In this article, I am going to look at how AWS Mobile Hub helps build a serverless mobile backend that exposes REST interfaces, and uses Amazon Cognito to help developers integrate authentication and authorization into an Ionic v2 app. I will be using a sample app, to demonstrate the implementation. The app is a simple task management app that allows access to authenticated and unauthenticated users.  I will cover authentication in the second part of this blog.

Before getting started

To create and run the Ionic app, you will need the following

Set up the backend

Set up your AWS resources using AWS Mobile Hub by clicking the button below:

Deploy to AWS Mobile Hub

Update the preselected name of your project if necessary and take note of the region in which your resources are created in. Press Import project. This creates a new Mobile Hub project with the following features enabled:

  • User Sign-in
  • Hosting and Streaming
  • Cloud Logic
  • NoSQL Database

The User Sign-in feature sets up a Cognito User Pool for user management. This will allow the app to register and sign in users.

The Hosting and Streaming feature creates a S3 bucket to host content and a Cloudfront distribution to make the hosted content globally available. The hosting bucket also contains Javascript configuration files that can be used to configure the client application:

  • aws-config.js – Uses ES5 syntax, and is suitable for browser-based implementations
  • aws-exports.js – Uses ES6 syntax, supports export and require statements, and is suitable for Node.JS projects

The sample will be making use of the aws-config.js file.

The Cloud Logic feature sets up two APIs: a TaksAPI which restricts API access to signed-in users and a ProjectAPI which does not. This means that app users that are not signed in will be able to access resources via the ProjectsAPI as guests (unauthenticated users).

The app interacts with the APIs by making HTTP requests to the endpoint URLs, specifying the resource path.
For TaksAPI, this will be https://<apigateway>.execute-api.<region>.amazonaws.com/tasks/* and for ProjectsAPI: https://<apigateway>.execute-api.<region>.amazonaws.com/projects.

Note that unauthenticated users must still receive an identity ID from the backend via Cognito Federated Identities and sign their requests to be granted access to the resource. Let’s look at how this can be done in the app.

Set up the client

On your workstation, clone the repo that hosts the sample:

git clone https://github.com/awslabs/aws-mobile-ionic-sample

In the Mobile Hub console, click the Hosting and Streaming tile. At the bottom of the page, download the aws-config.js file into the ./aws-mobile-ionic-sample/client/src/assets.

Navigate into ./aws-mobile-ionic-sample/client and run:

npm install
ionic serve

Your application is now running on your workstation! You can bootstrap your application with with some default tasks:

  • On the left hand side of the Mobile Hub console for your project, select the the Resources tab. On the AWS Lambda Functions tile, copy the name of your TasksAPI-handler.
  • Using the AWS CLI, run the following command with your Lambda function name and the region your resources were deployed in (e.g.: us-east-1)
    sh ./aws-mobile-ionic-sample/bootstrap/run.sh <YOUR_LAMBDA_FUNCTION> <REGION>

    Open a browser to http://localhost:8100 to see your running sample app on the Dashboard page.

Anatomy of the app

Accessing the configuration

The aws-config.js file is used to configure how the app communicates with the AWS backend services. This file contains all configuration required to communicate with AWS resources that have been created by Mobile Hub.

The file is referenced in ./client/src/index.html file via a <script> tag. Since the aws-config.js (and the rest of the app) depends on the AWS SDK for Javascript, a script tag referencing the SDK is also included.

<script src="https://sdk.amazonaws.com/js/aws-sdk-2.92.0.min.js"></script>
<script src="assets/aws-config.js"></script>

The configuration data is imported into the sample app by ./client/src/app/app.config.ts. This file defines a config class (AwsConfig) that loads the information at run time. Since multiple Cloud Logic APIs can exist in an application, AwsConfig also defines an APIs object through which all endpoints can be referenced by name. This generic format makes the class reusable in any project.

@Injectable()
export class AwsConfig {
  public load () {
    let aws_cloud_logic_custom_obj = JSON.parse(aws_cloud_logic_custom)
    return {
      'region': aws_cognito_region, // region you are deploying (all lower caps, e.g: us-east-1)
      'userPoolId': aws_user_pools_id, // your user pool ID
      'appId': aws_user_pools_web_client_id, // your user pool app ID
      'idpURL': `cognito-idp.${aws_cognito_region}.amazonaws.com`, // cognito idp url
      'identityPool': aws_cognito_identity_pool_id, // your federated identity pool ID
      'APIs': aws_cloud_logic_custom_obj.reduce((m, v) => { m[v.name] = v.endpoint; return m }, {})
    }
  }
}

The configuration data is loaded by AwsConfig when the Ionic app is bootstrapped. This is done in ./client/src/app/app.module.ts by calling the AwsConfig.load function when the root app component is set:

IonicModule.forRoot(MyApp, new AwsConfig().load())

Retrieving credentials

The app implements a service to handle getting credentials for the current user ,in order to handle user sign-up and sign-in scenarios, in ./client/src/app/auth.service.ts. Definition of the service is done using standard Ionic and Angular conventions. The service provider is declared in ./client/src/app/app.module.ts and can be injected into other parts of the application using Dependency Injection (DI).

The service makes use of the Amazon Cognito Identity SDK for JavaScript. The dependency can be added to the project using npm:

npm install --save amazon-cognito-identity-js
npm install --save-dev @types/node

The command installs the package and adds it to the list of dependencies in a package.json file.

Upon creation, the service injects the app configuration and tries to refresh or reset the credentials.

constructor(private config: AppConfig) {
  AWS.config.region = this.config.get('region')
  this.poolData = { UserPoolId: this.config.get('userPoolId'), ClientId: this.config.get('appId') }
  this.userPool = new CognitoUserPool(this.poolData)
  this.refreshOrResetCreds()
}

private refreshOrResetCreds () {
  this._cognitoUser = this.userPool.getCurrentUser()

  if (this._cognitoUser !== null) {
    this.refreshSession()
  } else {
    this.resetCreds()
  }
}

If a valid user session exists, the service will create a new set of CognitoIdentityCredentials based on the session information and the available Cognito User Pool information. Otherwise, the service will create a new set of CognitoIdentityCredentials based on the current identity pool for unauthorized users. When the credentials object is created, a GetId request is made to Cognito to return an identity ID. This ID can then be used to make a GetCredentialsForIdentity request to get AWS credentials consisting of AccessKeyId, SecretKey and SessionToken.

getCredentials (): Observable<any> {
  let result = null
  if (this._cognitoUser === null) { result =  this._getCreds() }
  else if (this.session && this.session.isValid()) { result = this._getCreds() }
  else { result = this.refreshSession().then(this._getCreds) }
  return Observable.from(result)
}

The getCredentials function goes through a few steps to get the right credenials:

  1. If there is no cognito user signed in, it will return credenials for an unauthenticated user
  2. If a user is signed in and their session is valid, it will return the current credentials
  3. If a user is signed in and their session is not valid, it will first refresh the session and then fetch the credentials

Fetching credentials is an asynchronous process handled by a Promise. The function in turn returns an Observable created from the returned promise.

Signing requests

All HTTP requests to the backend are signed with the user’s Cognito credentials using Signature Version 4.

Signing requests adds authorization information to the HTTP headers and allows the backend (API Gateway) to grant or deny access to resources based on the identity of the requester.
The functionality is implemented in ./client/src/app/sigv4.service.ts.
The file implements a service that uses Angular’s Http service to make requests and uses aws4 to create signature headers. The aws4 dependency can be added to the project using npm:

npm install --save aws4 querystring-browser
npm install --save-dev @types/aws4

The ./client/package.json has to be modified to include this line:

"config": { "ionic_webpack": "./webpack.config.js" },

This directs the ionic build script to use the project’s webpack configuration (./client/webkpack.config.js) in which the querystring alias is redefined.

To create Signature Version 4 headers, information about the requests and the user’s credentials is passed to the aws4 library which returns a set of headers.
aws4 will also add ‘Host’ and ‘Content-Length’ headers which are restricted by some browser implementation.
They are deleted to avoid warnings been generated.

let aws4Options = {
  host: this.parser.hostname,
  path: path,
  method: verb,
  headers: headers,
  body: body
}

let aws4Sign = aws4.sign(
  aws4Options,
  {secretAccessKey: credentials.secretAccessKey, accessKeyId: credentials.accessKeyId, sessionToken: credentials.sessionToken}
)

aws4Sign.url = reqEndpoint + path

delete headers['Host']
delete headers['Content-Length']
console.log(aws4Sign)
return this.http.request(new Request(aws4Sign))

Finally we create a new request by calling the http.request function. The request is asynchronous and returns an Observable object.

Making a request

A request can be made to the backend by first getting the user’s credentials and then calling the sigv4Http service. Credentials are cached by the Amazon Cognito Identity SDK for JavaScript and are only fetched if they have expired.
You can find an example of this being done in ./client/src/app/project.store.ts. Using observables enables chaining function calls. The observable finally returned can then be subscribed to, to handle the HTTP response.

refresh () : Observable<any> {
  let observable =  this.auth.getCredentials().map(creds => this.sigv4.get(this.endpoint, 'projects', creds)).concatAll().share()
  observable.subscribe(resp => {
    let data = resp.json()
    this._projectsMap = _keyBy(data.projects, (p) => `${p.projectId}+${p.month}`)
    this._projects.next(List(<IProject[]>data.projects))
  })
  return observable
}

Next Steps

Now that you know how to secure RESTful interfaces in Ionic using the AWS SDKs, you can use Mobile Hub to quickly deploy backends and use the sample application to accelerate your own app development.

You can download the code we present in this tutorial on GitHub and visit ionicframework.com to learn more about building Ionic apps.  You can learn more about Mobile Hub Hosting and Streaming by visiting the documentation.