API-less backend for iOS with AWS in Swift (part 1)

In this set of articles, I want to share with you an experience of building scalable and cost efficient backend for mobile application. We’re going to build a simple iOS application in Swift, but the same principles could be applied for Android as well. I hope you would enjoy.

Why AWS and not mBaaS?

I like the concept of mBaaS. Why not? It allows you to relatively easy build and publish your MVP or even further. All these fancy and easy to use SDKs remove a headache of managing your servers, syncing backend and mobile teams work, and boosting overall development time. But with power comes great responsibility, and if your company/project has plans to live long and healthy business life (I hope you do!) you should ask yourself at least these questions:

  • What would be overall cost/time/impact to switch to different mBaaS/custom solution?
  • Can I rely that current service provider would be on the market in 1/2/n years?

I bet no one has expected Parse to shut down. Of course, you can host Parse instance on AWS or Heroku, but why to bother if you can build you scalable solution with almost same effort level. Let’s give it a try, shall we?

Agenda and Overview

Here’s what we are going to cover:

  • Basic AWS account/project setup;
  • User authentication/basic profile with email/Facebook using IAM/Cognito/Cognito Sync;
  • Storing/fetching data with DynamoDB;
  • Business logic with Lambda;
  • File storage/distribution with S3 and CloudFront;
  • Push notifications with SNS;
  • Analytics Amazon Mobile Analytics;
  • App landing page hosted on S3 with custom domain via Route53 and CloudFront distribution.

I will try to explain from both perspective: mobile and devops, so that non AWS familiar could jump in and give it a try. What I like about AWS is that it is kinda of Cloud Lego bricks, when you can go from simple solution to really sophisticated/global one without leaving AWS console.

Sounds interesting? I hope so, let’s start.

Basic AWS account/project setup

I’m not going to cover signup process, but I suggest to look over Free Tier (which also includes 12 months tier for new accounts) . I think it is important to know where you can save money. Without going into many details, I’d say MVP/early stage would cost you nothing or really small amount, the only service (depends on the project type, for sure) that might go beyond free tier pretty quickly is S3; with only 5 GB standard storage, 20000 Get/2000 Put Requests, and 15GB transfer.

AWS works in multiple regions, in terms of MVP you can select one region that is closest to your initial, target audience. Set chosen region as home (drop-down menu at top right corner).
An important concept to remember: Most of the services are region based, which means files in S3 or tables with data in DynamoDB, for examples, would be physically stored in a specific region (without additional replication setup). Although data is accessible throughout all the worlds, but latency would be different.

Amazon politely insists on security, so do not hesitate to complete all steps in IAM section. Also, it is considered good practice do not user your root account for day to day action in AWS. Instead, create as many admin users as you need (for example for your developers).

First we need create a group:

Secondly, we can assign this group to any user:

User authentication with Cognito

Now, let’s jump into Cognito to setup user authentication and control access. Think of Cognito as user session management piece of a backend, which enable you to create unique identities for your users and authenticate them.

More about authentications flows

Cognito Identity Pool

The process of creating new Cognito Pool is pretty straightforward (assuming you’ve already got Facebook App configured):

Note, by default each new Identity Pool is going to have two roles associated with it, one for Unauthenticated and one for Authenticated users.

If you plan to have multiple Identity Pools (for example a separate pool per dev/stage/prod environment) you might want to specify existing roles. For now, we will keep it as it is, since we will update those roles later.

From this point, we can already authenticate Facebook users and start using AWS services, but we also want to allow users to signup with Email/Password.

Cognito User Pool

This is relatively new service from Amazon and I think they should have added it earlier (more about user pool). Before User Pool developers had to authenticate users through custom server code, with an obligation to store and check user’s password. Which frankly looked a bit odd.

Let’s go over creating a new User Pool, since it requires a slightly more options:

We define which attributes we want to be required at signup or we will get an error from Cognito. Note an Alias options, it allows a user to use their email as login along with the password.

For this test app purpose, we will disable MFA and account verification, but you might want to consider using it in your production app.

One more important step is to create an application for your pool:

Once done we need to connect our User Pool with Identity Pool so that our users will get identity and IAM role associated with it to access AWS services. We would need two identifiers from newly created Pool:

Go to Cognito Federated Identities where you should find your Identity Pool and edit it:

And add your User Pool as provider:

We are almost done! One final step is left before we can switch to our iOS app. Since we disabled account confirmation (for test purpose) all new accounts in User Pool are going to be unconfirmed, thus not fully functional. One way to overcome this is to create a Lambda trigger, that will confirm each new user in our User Pool.

Create new Lambda function with no blueprint selected and no initial trigger. Here’s a code for auto-confirming new users, which you could paste in code box (for Node.js environment):

exports.handler = function(event, context) {
event.response.autoConfirmUser = true;
// Return result to Cognito
context.done(null, event);
}

In handler and role section select New Role from template and set a name for a role (we do not need any specific permission for this function):

Now, having this function created we can assign it to our User Pool:

iOS Application with Cognito

Now we can finally switch to iOS stuff. The complete project for this part can be find here.

The key concept of authentication with Cognito, from a developer point of view, is pretty simple: We need to get a token from our authentication provider (Facebook or User Pool) and pass it through to AWS default configuration via AWSCognitoCredentialsProvider. This token will eventually be exchanged to session attributes and identity.

Note: You can set defaultServiceConfiguration only once, any subsequent setters are ignored. What we are missing here is an instance of AWSIdentityProviderManager:

The only purpose of this class is to wrap-up token into dictionary. The dictionary should contain only 1 entry:

[ “<Linked Login Provide Id>” : “<Token>”]

<Linked Login Provide Id> identifies type of provider we use (more info):

  • Facebook: “graph.facebook.com”
  • Twitter: “api.twitter.com”
  • Google: “accounts.google.com”
  • User Pool: “cognito-idp.<region>.amazonaws.com/<USERPOOLID>”

Ok, but what’s <Token> in this case? Here’s an example how this dictionary might look like for Facebook login case:

[“graph.facebook.com” : FBSDKAccessToken.current().tokenString]

For User Pool it is a slightly more complicated and we are going to review it shortly. Here’s a final piece of code, which you could copy/paste and try login user via Facebook:

Authentication via User Pool

The main object we are going to interact with is AWSCognitoIdentityUserPool:

Now we have two options how we can get a user from User Pool, we can either signup, login a user, using username and password, or restore session for last logged user. Our target is to get idToken from AWSCognitoIdentityUserSession:

If you try to signup a user with existing email or login with non-existing, on both cases you’ll get an appropriate error with a message to display. Once you have this token AWS service initialization is the same:

AWS Cognito Sync to store user’s data

The concept is pretty simple, it is a key-value storage with a local cache, that can be synchronized with remote storage associated with current user Identity (although there’s a bit more functionality available, so you might want to check the documentation).

Before reading a values from existing dataset (for example for logged-in user) we should synchronize() it, while for a new user we can create a new one with openOrCreateDataset and upload it with the next synchronize() or synchronizeOnConnectivity().

Storing profile in CognitoSync is one of the ways and it won’t be accessible by anyone, except this user. Or, you could store the complete profile in DynamoDB, if you plan to make it available to other users. There’s even a mix of both, where you can setup a trigger that will sync CognitoSync with DynamoDB on each update.

All complete project for this part can be find here.

Conclusions

It might seem as a lot of actions just to setup Authentication for an application, but threat it as investments in long term game. Feedback is welcomed.

Stay tuned for the next parts!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.