I recently struggled with the problem of security and user authentication for an iOS app I'm making, the main problem problem being how does one allow users to sign up with any 3rd party service (or a native user account) and still maintain a secure and modular process.
The solution I came up with is quite complex and I'm not 100% sure if all of it is best practice so I thought I'd ask and get any suggestions and pointers on what I can fix, what works well, what's bad etc.
First is the matter of authentication. I like to separate the idea of authentication from the idea of users. To me, authentication is something that's performed by a device or client, independent of a particular user and a user account is something that's created or retrieved as a result of that authentication. What this allows you to do is treat authentication of the client as one process and then authenticating the user (checking to see if an account exists etc) so that there are two layers of security. Say for example the client successfully authenticates but then the user password is wrong, authentication overall would fail and having the two concepts loosely coupled is beneficial in that way.
To implement authentication, I used JWT (JSON Web Tokens) over cookies for a bunch of reasons. 1) They work much better with mobile 2) are session-less which makes server implementation much easier, and aren't subject to CORS attacks as far as I'm aware. JWT seems to be the better solution when working with mobile devices. I used a lot of npm libraries, most notably express-jwt and jsonwebtoken to do the authentication on the server side.
As I mentioned above, not only was I trying to perform authentication I also want to allow users to sign up with any 3rd party service they want such as Facebook, Twitter to reduce user friction during signup. After thinking about this for a while and googling a lot, I came up with the idea of identity providers, an authentication system in which each "account type" is treated as a separate provider of identity and is generalized to provide information such as an access_token, user_id, expiration data, etc. Identity providers are much like "linked accounts" you see in a lot of app settings pages. On the iOS side of things, I made an abstract class and for each service I want to support, I made a concrete subclass, FacebookIdentityProvider
, LocalIdentityProvider
(email/password) etc.
On the server side, I used Passport modules to back each kind of identity provider. For example, they have a facebook-token module, one for user's email and passwords etc. So I made one api route /authenticate
that my clients make a request to with the serialized identity provider and based on an identifier string, local
, facebook-token
, passport would call the appropriate submodule for authenticating that provider based on the information provided.
Overall, the security flow looks like this:
- Client checks disk for previous JWT token (stored securely using Lockbox).
- If a token is found, the client makes a request to my
verify
endpoint. This endpoint will verify if a token is still valid and hasn't expired. - If the token hasn't expired, the client is sent a 200 and all's good with the world. If not, then the client will make a request to my
refresh_token
endpoint with the expired token which will attempt to reissue a token. If that fails, then the client makes has request to myauthenticate
endpoint which can only be called as a result of a user action. - If no token is found originally on disk, the same thing happens as the end of 3, the client has to wait for the user to authenticate.
With all this done and implemented, I'm still a bit fuzzy on a few things. Primarily, I read something on the express-jwt page about revoking tokens. What determines when I should revoke a token and make a user login again? It doesn't make sense to keep refreshing their token every time it expires indefinitely.
Secondly, when I'm sending the serialized identity provider over to the server, I pass along a dictionary of extra information that'll be used by passport to authenticate based on the process. If it's successful, an identity provider is created for that user and stored in the database. Is that enough or should I be doing more with the access_token and other fields I get back from a successful call? Particularly with the Facebook SDK, I get an access token when the client authenticates via the app and then another token when the client authenticates again with the server.
An additional idea I had was to someone integrate an api key that was passed with every request either through a header or query parameter. The api key would be kept secret and secured on the client side. What I think this would do is add another layer of "authentication" even to clients that haven't gone through the authentication process yet. Only clients with the api-key would every be able to even reach my api in the first place and only those clients would be able to attempt authenticating.
My background is formally cyber security (I was never any good) and now full stack mobile development so I do have a better grasp at this stuff then most but I feel as if I fall short on some potentially dangerous holes. I unfortunately can't post code as this is for a business of mine but if there's anything I didn't make clear, just comment and I'd be glad to elaborate.
Also I feel I should mention, all of this is done over SSL which I configured using Nginx and all my iOS network requests are made using Overcoat. Eventually I want to use Nginx as a load balancer but that's a post for another day.