Chris Risner . Com
Posted on: 4/22/2013 8:13:00 PM by Chris
Update 4/22/2014: Version 1 JWTs have been deprecated. Make sure you check out this post to see how the script for JWT creation needs to be changed.
This article is the companion to one I just posted about handling different types of authentication with Windows Azure Mobile Services. Prior to taking a look through this article and the mentioned code, I would go through the original article on Mobile Services and auth. This article will cover how to connect the Mobile Service we set up there with an iOS client using the Mobile Services SDK for iOS. All of the source code for this iOS app is available here in GitHub. I’m going to cover a few different areas in the app in this post: giving users the choice of how to login, creating and logging in with custom accounts, logging users out and returning to the root view controller, caching user tokens so we won’t have to login each time, and dealing with expired tokens now that we’re caching them.
Giving the user choice
This is one of the easiest things to do from a front end perspective because all I need to do is give the user the ability to select their authentication provider:
All I’ve done is put one button for each provider and then the custom auth at the bottom. Each of these buttons (except for the last) will call the same method and pass in the provider’s name:
Depending on which button is clicked, one of the following is sent in: facebook, google, microsoftaccount, twitter. I do want to highlight here that all of these are exactly as you’d expect, except the Microsoft one. I’m not exactly sure why it’s like that. Next we have the loginWithProvider method:
Here the first thing we do is record which provider the user selected in a variable inside the authService (which is a class I have wrapped all of my Mobile Service functionality in). We then create a MSLoginController using our authService’s client property. That get’s presented to the user. When that method’s callback is called, we log the error if there was one and, if not, we save the auth info (which we’ll talk about later) and then trigger the loggedInSegue. I’m going to highlight how I created that segue because it was something I didn’t know about before this sample. If you open up your storyboard (I’m using storyboards for everything here) you can control + click and drag from the View Controller icon under your view to another View Controller and create a segue which you can then give an Identifer so you can call it from the code:
So even though there’s nothing in my UI triggering this segue (like there would be if I’d done the same thing from a button instead of the icon) I’m able to use this segue from my code behind. So here, once the user has logged in successfully, we’re taking them to a page that’s “inside the authentication wall”. We’ll talk about what happens in there a bit later, but first, let’s talk about what happens when you hit the Login with Email button.
When you tap the Login with Email button, you’re taken (via segue) to a login page where the user can enter their username and password:
Let’s take a look at logging the user in here before we look at registering their account. When the user taps Login we call the following code:
Here we’re building a NSDictionary with the username and password and then passing that to the loginAccount method of the authService. In the callback, we’re checking to see if the NSString that is returned is equal to SUCCESS, and if so, we’re dismissing this view and then calling the customAuthSegue segue. This segue is defined just like the one above but on our CustomLoginViewController. If we didn’t have a success, then we’re setting a label below the buttons to whatever message was returned. Inside my AuthService, I have the loginAccount method:
This method creates a NSDictionary to store parameters for the query string and adds one named login. If you recall from the previous article about the server side scripts, when we do an insert on the Accounts table and login is a query string parameter, it knows we’re trying to log the user in as opposed to register a new account (since we’re using the same script for both things). We then call the insert method on the accountsTable. If there was an error we call the completion handler with the error text (which sets it to the label on the screen), and if not, we create a new MSUser using the userId that is returned and then set it’s mobileServiceAuthenticationToken using the token that was returned from the insert call. We then set the client’s currentUser to be equal to that and call the saveAuthInfo method before finally returning SUCCESS. One thing I’ll point out is that I didn’t put any validation in to check that they entered a username or password. This is definitely something you’ll want to do in your own apps to make them user-friendly.
If the user taps Register for Account, we’ll take them to this view:
Here the user enters all of their information and then taps Register. This calls the tappedRegister method:
The first thing we do here is check to make sure they’ve entered data and that it’s valid. After that, we build our NSDictionary with the necessary information and pass that to the registerAccount method on AuthService. If this comes back with a SUCCESS message, we dismiss the view and call the customAuthSegue which will take us to the logged in view. If not, we display the error on the screen. Let’s look at registerAccount next:
As you can see, this is the exact same as the loginAccount method earlier, we’re just not passing the login parameter in. Now let’s take a look at how we save the account info.
Saving account info
To store our account info, we’re using something in iOS called the Keychain. You can read a lot about the keychain if you want or you can skip that for now and just know that we use it to securely store data on our device. The KeychainWrapper’s provided in Apple’s docs don’t actually account for Automatic Reference Counting (ARC) so instead of using their files, we’re going to take the version from Chris Lowe’s iOS Security tutorial here. This code is available under the MIT license so we can use it (as long as we know it’s our responsibility if anything crazy happens). Once you’ve added those files to a project you can then use the methods in your code. Here we’re using it to save our data in the saveAuthInfo method:
We’re simply storing the userId and token properties. Now that we’ve seen how we can cache the user token and ID, let’s see how we can load that when the app starts.
Loading the auth info
When our application starts, in the first View Controller, we’re initializing the AuthService which calls this init method:
The important call here is the call to loadAuthInfo:
Inside that method, we’re first fetching the userid from our Keychain. If that value isn’t null, we create a new MSUser for the client.currentUser and then set it’s mobileServiceAuthenticationToken. Now we finally see where this is called and set from our initial View Controller:
After the AuthService is set up, we check if it’s client.currentUser.userId property isn’t NIL and if so, we call the loggedInSegue. This segue (just like the earlier ones that aren’t connect to a UI element) takes the user into the logged in section of the app and by passes the login screen. So now we’ve seen how to cache the current user’s token and user ID and how we can load it when the application starts. Next let’s talk about how we can trigger logouts.
Logging the client out
To log out of this application, there is a logout button on our LoggedInViewController:
This logout button is connected to a segue, but not just any kind of segue, an unwind segue. Unwind segues are a way for us to trigger returning back to a certain point in the application flow. So if I want to return back to the view with all of the login options, this is perfect for it. To create an unwind segue, we first have to add a method to a View Controller. This method needs to have a return type of IBAction and a UIStoryboardSegue parameter. Here’s the method we have in the initial View Controller:
The only thing we’re doing is calling the killAuthInfo method on authService. From a code perspective, none of this is saying “roll back all the view controllers until you get here”. iOS actually handles that for us. All we have to do is connect the Logout button to that segue. In order to do that, we can control + click and drag from the button down to the green Exit indicator beneath the View Controller:
When you release, any method that matches the IBOutlet / UIStoryboardSegue method format will show up for us to select. So when a user taps that button, iOS says “I’m going to roll back to the first View Controller that implements the matching method (so watch out if you have multiple unwind methods named the same thing) as well as calling that method. So now that we’re back to the initial View Controller, let’s look at the killAuthInfo method that is called:
The first thing this method does is delete the userid and token items from the Keychain. After that we go through the cookies and remove them. The reason we do this is that logging in through any of the built in providers (Facebook, Google, Microsoft, and Twitter) sets cookies on the UIWebView that is used to display the login pages. If these cookies were left there and we tried to log back in using the same provider, then that provider would say we were already authenticated. One caveat to this is that here we’re removing ALL of the cookies from our app. If we were using UIWebView’s for more than just that, this could cause a problem if we remove cookies we don’t want to. Lastly we call logout on the client. This currently just sets the currentUser to NIL. Now the last thing we need to look at is how to handle expired tokens.
Handling expired tokens
The motivation for handling this came from another post by Josh Twist which you can check out here. The idea is that if your app makes a request to your Mobile Service that SHOULD get through because the user is authenticated and you receive a 401 (unauthorized error) it means the user token you’re passing over has expired. There are a couple of ways we could handle this. In the completion handler for every method that we have that interacts with our Mobile Service, we could check for a 401 response, or we can handle things in one place: the MSFilter’s handleRequest method. Basically, when you say that your service class (AuthService here) implements the MSFilter protocol, you’re saying you’ll also be implementing the handleRequest method which will be called for each request you make to your Mobile Service. Here’s the handleRequest method in our AuthService:
The only thing we’re doing in this method is calling onNext and specifying a method to call when we get a response. That method is filterResponse:
In this method we first check to see if the response was a 401, if not we just call onResponse which continues to deliver the response to the completion handler the call to the Mobile Service had. If it was a 401, we first call killAuthInfo to remove all the authentication info. We then check to see if the shouldRetryAuth boolean has been set to true (we’ll get to where that is set soon). If it isn’t true, or the auth used was the custom auth, then we call a triggerLogout method (we’ll look at that in a second). We’re checking for custom auth because we haven’t implemented a popup to let the user log back in with custom auth. We could of course create something like that and show it, I just haven’t done it for this sample. Provided the boolean is true and we used a non-custom auth, then we proceed to call loginWithProvider on the client to pop up the login window again. When we get our response back from the login window, if they didn’t log in successfully, then we call triggerLogout (they had a chance to log back in and missed it). If they did login successfully, then we save the new auth info with saveAuthInfo and then we recreate the original request. We first take a mutableCopy of the original request. We then set that NSMutableURLRequest’s X-ZUMO-AUTH header to the new auth token. Finally we add a new query string parameter and tell our filter to process the request again. Now if you remember how we implemented the BadAuth service so it would trigger a 401, you’ll remember that if the request contains a query string parameter named bypass, then we return a normal 200 response. We fix this with the addQueryStringParamToRequest method:
Here we’re assuming there isn’t already any query string parameters but that’s a safe thing to do because we know exactly what the original request was. Plus, if we really had an expired token and fixed the issue, we wouldn’t need to worry about a bypass parameter. The triggerLogout method is called whenever we want to force our user back to the login screen from our filter:
Here we make sure the auth info was killed, then we get the top View Controller and tell it to perform the logoutSegue segue. This segue is defined like the others to be an untrigger segue, this one on the root View Controller. Finally, let’s look at the method we use to trigger that call to the BadAuth method:
Here we’re taking in the retry parameter which will dictate whether or not we try the re-login or if we just force the user out.
Today we looked at how to implement an iOS client with access to each of the built in authentication providers as well as how to handle custom authentication. We reviewed how to register a user for new accounts as well as how to login with them. We also saw how to cache the user’s auth token so they don’t have to login each time the app runs and how to reload that auth information. With all of this, you could pretty much just drop in whatever functionality you want only logged in users to have access to. There are some further things you might want to handle such as sending an email to the user after they register for an account to make sure the email address they used is valid (which you can do with SendGrid) as well as offering Forgotten Password functionality. These are things that are definitely part of a full application and should be considered.