Handling mobile authentication with a web-based API

Quite recently I had the chance of implementing a log-in/authentication system for one of our mobile applications. Resources available for both the Android and iOS eco-systems are fairly exhaustive but comparisons of the pros and cons of each and every one are not readily available, at least not of the type I would like. What’s being described here will assume that you have a stack involving a web API along with mobile apps that require connecting. Before showing the implemented solution we’ll go over the various issues regarding authentication.

Conventional methods of authenticating users

OAuth

With the rise of the open OAuth standard came also the idea of simplyfing the process of logging in. Instead of manually implementing an entire authorization system, the crucial part is being delegated to a centralized server (henceforth service). The way this standard is being applied nowadays is that the application (i.e. the web or mobile application you’re attempting to log into) makes use of some commonly used platform, like Facebook, Google, Twitter or in certain more specific cases LinkedIn or Github as the provider for OAuth.

OAuth was published as a protocol by the Internet Engineering Task Force (or IETF for short) back in 2010 and has ever since then been gaining ground.

There are numerous possible downsides to implementing OAuth as your method of authentication on mobile platforms, but I will be naming only a few that I’ve personally encountered.

Apple guidelines

The issue presented itself while implementing an OAuth service for a native iOS app (more specifically, the implementation included OAuth for the Facebook/Google/LinkedIn trio). Rather than encountering a problem with the service itself, the unexpected problem that confronted us was Apple. That is, Apple’s App Store Review procedure.

Apple doesn’t want you to use OAuth merely for logging in if you don’t use any other feature provided by said service (e.g. logging in via Facebook just for the sake of logging in).

apple01

— this is outlined in Apple’s App Store Review Guidelines under Legal/Privacy (5.1.1), Data Collection and Storage, under the second paragraph (ii)
which states the following1,

(ii) If your app doesn’t include significant account-based features, let people use it without a log-in

and further

If your core app functionality is not related to a specific social network (e.g. Facebook, WeChat, Weibo, Twitter, etc.), you must provide access without a login or via another mechanism.

additionally, even if one were to implement some simple functionalities like these

Pulling basic profile information, sharing to the social network, or inviting friends to use the app are not considered core app functionality.

But you’re still in violation of Apple’s policy. While it does seem like an annoyance, there are probably good reasons for this and the privacy-loving folk ought to admire Apple’s stance on this issue.

Since rolling out OAuth solely for the purposes of having a login was forbidden, the choice from here on was either we implement some entirely unnecessary feature for our app, or – maybe, just maybe – we drop the OAuth system and do something different.

Your users might not feel comfortable associating their social media account with your web/mobile application

Unlike the previous issue, which was clearly a practical one, the issue #2 was more of an issue that extended itself from Apple’s own concerns. An issue of privacy.2

Some users might consider it a breach of their privacy if a recently installed app – one they’re merely testing – already asks them to hand over their personal information. They might not want to hand over their personal and identifiable data over to us. And we should not be punishing them for that.

They might not feel comfortable with those social services knowing what apps they’re using

That being said,While services have gone to some lengths to limitthe exposure of their users’ data (e.g. when a Facebook user approves access to their data, they may unselect what they don’t wish to share), some people might not trust

Apart from being cautious about the application wanting you to hand over your personal Facebook, Google or whatever account, the user might actually not want those services themselves to lock onto their app’s account, and they might not want their mobile operating system or other apps be aware of what’s happening.
– not wanting your cell phone to handle it as a special account

Delegating control to a third-party

You’re no longer in control of your log-in, it’s handled by a third-party service.3

Other

Last but not least – OAuth in and of itself is just an authorization method, not an authentication method.4

Conventional log-in flow

conventional_login

Rolling your own username/password system

Even Android’s own Developer Guide5 suggests to minimize the amount of personal data handled by your app – which includes passwords. So as a whole, multiple issues might arise

  • how to send data securely over air
  • user has to come up with a name and/or password for your app
  • user has to enter those on their phone’s clumsy keyboard

Practically unworkable on mobile.

Token-based authentication (automatically generated tokens & JWT)

Introducing JWT.6 What is JWT? JWT stands for JSON Web Token. It’s an open standard for secure creation of tokens.78

token which allows them to fetch a specific resource – without using their username and password

And it’s precisely what we need to implement a simple-to-use system where users do not have to go through hoops.

So how did we implement a workable solution with JWT? Here’s how.

Our implementation

After carefully considering the various pros and cons we ended up with the following.

Our system relies on our user having one identifiable piece of information – their email. Reasoning behind it,

  • everyone has an email address
  • if people are conscious enough to use throwaways, they’re more likely to have a throwaway email account rather than a throwaway Google/Facebook/LinkedIn account

The rest of the web end of the implementation is irrelevant for the purposes of this post. Depending on one’s wishes, one might continue using OAuth only for their web application (but implement our mobile-specific backend).

Some authentication implementations even go a step further. For example, they might send their users a newly-generated login link via email, valid only for one session. This however seemed a tad too convoluted for our purposes. Or rather, it was something we simply did not need.

send_link

An additional upside of our implementation is that a user immediately gets to use their application as a full user. They’re not being treated as a guest. They’re treated as a user immediately upon launching the app and they’re securely authenticated with full-fledged access.

This is what our mobile app’s interaction with the backend looks like

infrastructure

Our mobile app stores a token on the client’s device. In the case of Android, we’re using a SharedPreference as the data store for our token.


    SharedPreferences prefs = getSharedPreferences("USER_PREFS", Context.MODE_PRIVATE);  
    String userToken = prefs.getString("userIdKey", null);

During our data sync, we make a request to our backend web API,


    #jwt_auth_controller.rb
    def authenticate_request!  
        fail StandardError.new('NotAuthenticatedError') unless user_id_included_in_auth_token?
            @current_user = User.find(decoded_auth_token['user_id'])
        rescue JWT::ExpiredSignature
            raise StandardError.new('AuthenticationTimeoutError')
        rescue JWT::VerificationError, JWT::DecodeError
            raise StandardError.new('NotAuthenticatedError')
    end

Since this only handles our mobile app’s API, we limit this only to the views relevant to the client app. We decided to use the before_filter hook,


    before_filter :authenticate_request!, :except =>; :handshake

We’re skipping this hook on the handshake method since we use that controller method for initializing the environment of our newly created accounts (that’s where we generate some default data commonly used by our users).

We then attempt to verify the user. If no user can be found, we can issue a new account for the user. The client app will then fetch the new token and store it inside of the SharedPreference on the client device. This simplifies the procedure as all three cases (correct token with existing account, incorrect token for whatever reason that may happen, and no token) are handled in a similar fashion – a user will not have to go through the process of manually registering. They will simply get a new account.

However, to avoid possible loss of account data – say for example iif a user were to get rid of their app’s data and thereby loses the token – we implemented a account sync using a QR code reader. The expected workflow is like this,

  • user visits our web application
  • user logs in
  • user scans the QR code we display on our web app
  • the mobile client updates the data on their device and syncs it with their account

The scanning is done using Zxing‘s scanning library. The library is open source and offers lots of bells and whistles but those aren’t really relevant here. We just launch a scanner,


    public void readUserToken(View view) {  
        IntentIntegrator integrator = new IntentIntegrator();
        integrator.initiateScan();
    }

We’ll then save the token on the mobile device and do an account sync.

Now back to our backend implementation of JWT.


    #jwt_auth_controller.rb
    def decoded_auth_token  
        @decoded_auth_token ||= AuthToken.decode(http_auth_token)[0]
    end

Our decode function might look something like this,


    def self.decode(token)  
        payload = JWT.decode token, self.hmac_secret, true, { :algorithm => 'HS256' }
    end

And the rest of our JWT auth controller can be seen over here.

References