Conventional methods of authenticating users
OAuth
With the rise of the open OAuth standard came the idea of simplifying the process of logging in. Instead of manually implementing an entire authorization system, the crucial part is being delegated to a centralized server (henceforth, the service). The way this standard is 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 platforms, like Facebook, Google, Twitter or in certain more specific cases, LinkedIn or Github, as the provider for OAuth.
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).
— 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 following[^n],
(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 it on 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 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 verboten, 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 accounts 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.[^n]
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 limit the 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 to 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.[^n]
Other
Last but not least - OAuth in and of itself is just an authorization method, not an authentication method.[^n]
Conventional log-in flow
Rolling your own username/password system
Even Android’s own Developer Guide[^n] 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.[^n] What is JWT? JWT stands for JSON Web Token. It’s an open standard for secure creation of tokens.[^n][^n]
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 jump 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 users 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 for only one session. This however seemed a tad too convoluted for our purposes. Or rather, it was something we simply did not need.
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
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.