Skip Navigation LinksHome > View Post

Exploring custom identity in Mobile Services (Day 12)

This is the last post in the series the twelve days of ZUMO and we'll pull together many of the themes from the last few days, including unit testing scripts, generating your own auth tokens and setting the auth token.

In today's post, we'll go a little experimental, and explore how we could use all these techniques and the built in flexibility of Mobile Services to implement custom identity for our service, where Mobile Services stores your user's username and password and allows them to logon without using a social network.

In order to set this up, we'll need a table to store the user's details. I called it accounts and we'll use this table to store the credentials and also to login using the Mobile Service client.

accounts table

Since we'll allow anyone to register we set the INSERT permission to only require the application key. All other operations (especially READ) should be set to scripts and admins only.

We'll do all the work in the insert script with two flows:

1. Account creation

A POST (insert) to the /tables/accounts endpoint with a body will start the account creation flow. I'll leave it as an exercise for the reader to decide what other data they might want to store in the accounts table and how you'll validate it (e-mail for example? checkout our integration with sendgrid).

In this flow the majority of the work is generating a salt and hashing the password before we write it to storage. 

2. Login

A POST (insert) to the /tables/accounts endpoint with a login parameter set to "true" means this is a login attempt and we should return a 200 with user and token data if successful, otherwise we'll send a 401 Unauthorized.

In this flow, we load the user account from the database by matching the username. The record loaded should include the salt and hashed password (a unique salt per row helps prevent the use of highly effective lookup tables to crack your hashed passwords, if they should ever end up in the wrong hands). We then hash the submitted password and do an equality check with the stored password, if the credentials are good - the hash will match.

Without further a do, here's the script that does all this and should be uploaded against your accounts table's insert operation.

Unit testing the script

With a script like this, it's always good to have some tests in place and so I created a suite of mocha TDD tests to verify the behavior. This is the first script I've shown that we'll unit test and also uses the 'tables' global in Mobile Services. It's the perfect opportunity to demonstrate a simple mockTables module that I use to mock the tables global in scripts. The idea is simple, first your create the mockTables instance:

var tables = require('./mockTables.js');

Then in each test you clear all data (or whenever appropriate):

tables.clear();

and populate the table with the data required for your test, specifying the name of the table:

tables.addItem('accounts', { 
username: "Josh",
password: "<hash>",
salt: "<blah>",
etc: "…" });

Now you can search this table in your Mobile Service' scripts and it should behave as you'd expect, e.g.

var accounts = tables.getTable('accounts');
accounts.where({ username: "Josh" }).read({
   success: function(results) {
      console.log(results); // will output a single record
   }
}); 

Note this mock is great for reading data but doesn't support setting up particular behaviors (such as returning an error) or verifying the order of invocations - but it's useful nonetheless. You can also use the functional where syntax, e.g:

accounts.where(function(a) {
    return this.x === a
}, 1)).read( // etc

The full unit test code and simple mockTables module are shown at the bottom of the post, before that though - the client.

Implementing the client

Believe it or not that's pretty much all we have to do on the server to implement custom identity for this post. Now for the client. We'll need to support registration and a new login approach, let's take a look at how we'd do this in Objective-C for iOS. I've decided to use categories to extend the MSClient class.

This adds login and register methods to the MSClient, so they feel right at home. And as you can see, the login method simply sets up the user and the token. You could change the register method to automatically log the user in (since, if registration was successful, they obviously have the right credentials).

I created a modified iOS quickstart that you can setup to see this in action. You'll need to set your TodoItem to authenticated for all operations and add the accounts table and script (be sure to create your own hashing key and use your own master key).

IOS Simulator Screen shot Jan 1 2013 7 08 31 PM IOS Simulator Screen shot Jan 1 2013 7 12 38 PM IOS Simulator Screen shot Jan 1 2013 7 14 37 PM

You can download the Xcode project here: CustomIdQuickstart.zip (2.6MB)

The client also uses a slightly modified filter from day 11 that uses NSNotificationCenter. 

Unit Tests and MockTables

As promised, here are the full unit tests and that mockTables code:

IMPORTANT: In this post we follow some best practice with regard to password storage by salting and hashing the password value and using a key-stretching algorithm (pbkdf2) and slower equals comparison. However, security and attacks continue to evolve. Remember, this is code you got from the internet and and comes with no warranty. Check out this article for more detail on password hashing: Salted Password Hashing.

Things we didn't look at

There are many. Perhaps the most obvious is, if you support custom identity, then you'll need to provide a way for users to recover their password in the event that they forget it. This isn't necessary when using Twitter or Facebook as they provide this mechanism for you. Typically, this involves an e-mail loop and as we integrate with sendgrid - this is entirely possible to implement. Of course, another key thing to remember is that Mobile Services composes extremely well with other services in Azure (and beyond) - so it's easy to augment your Mobile Service with other capabilities as necessary.

Another thing I'd want to do to ensure the integrity of my account data before putting this into production is enforcing a unique constraint on the username column, to remove the unlikely race condition

And this closes the series "the twelve days of ZUMO", thanks for reading and I value your feedback. The good news is the team is working hard on making almost everything the series has covered even easier in 2013 - HAPPY NEW YEAR!

PS - it is one of my New Year's Resolutions to fix the layout and design of this blog :)

 
Josh Post By Josh Twist
3:19 AM
01 Jan 2013

» Next Post: A quick lick of paint
« Previous Post: Handling expired tokens in your application (Day 11)

Comments are closed for this post.

Posted by Mariusz @ 02 Jan 2013 5:52 PM
Great article Josh, just in time for me. I was looking for a custom register and login possibility and maybe I would like using the iOS build in Facebook and Twitter authentication mechanism. With this article and your example it should be no problem anymore :-)
The Xcode project, you've attached, is not available. If you could take a look, this would be great. Thanks a lot for this article series.

Posted by Ignacio Fuentes @ 10 Jan 2013 3:00 AM
Hey Josh. thanks for sharing all these great articles on ZUMO.
i have a question though.
How does the server check that each request made to any resource is authenticated?
I was under the impression that (when using fb as identity provider) the server went to fb on every request to check if the token was valid, but now, after reading this approach, Im confused as to what exactly does the server do upon every request (requests to resources marked as only for authenticated users) to allow the request to go through.

Posted by thmoore @ 31 Jan 2013 10:58 AM
In the insert function you return the JWT this way:
request.respond(200, {
user: { userId : userId },
token: zumoJwt(expiry, aud, userId, masterKey)
});

But how can I ever get the value of token from C#? As far as I can tell, no 200 level response is returned when you call InsertAsync. Help!

Posted by ap @ 01 Mar 2013 7:40 AM
This is awesome and It is what I was looking for!


one thing, I ran into one odd problem with objective-c code sample.

It doesn't seem like "user" retains the value when returning to the caller in completion block.

MSUser *user = [[MSUser alloc] initWithUserId:[item valueForKey:@"userId"]];
user.mobileServiceAuthenticationToken = [item valueForKey:@"token"];
self.currentUser = user;
NSLog(@"%@",user); // value is still here.
completion(user, error);

called from the login method

completion:^(MSUser *user, NSError *error) {
NSLog(@"%@ %@",user.userId,error.localizedDescription); // user is null here.
}

any idea?

Posted by ap @ 01 Mar 2013 8:14 AM
oh sorry about that, It turnes out that only userId is null

Posted by ap @ 01 Mar 2013 8:30 AM
fixed!

I took a look at insert script carefully. It returns userId in user object so the correct code in objective-c would be like this.

NSString* userId= [[item valueForKey:@"user"] valueForKey:@"userId"];
MSUser *user = [[MSUser alloc] initWithUserId:userId];

© 2005 - 2014 Josh Twist - All Rights Reserved.