I recently encountered a security and user authentication problem for the iOS application that I am doing, the main problem is how you can allow users to subscribe to any third-party service (or their own user account) and still maintain a secure and modular process.
The solution I came up with is quite complicated, and I'm not 100% sure if all this is the best practice, so I thought that I would ask and bring any suggestions and pointers that I can fix, that works well, that’s bad and etc.
Firstly, it is an authentication issue. I like to separate the idea of authentication from the idea of users. For me, authentication is what is performed by the device or client, regardless of the specific user, and the user account is what was created or received as a result of this authentication. This allows you to authenticate the client as a single process, and then authenticate the user (checking for an account, etc.), so there are two levels of security. Say, for example, the client successfully performs authentication, but then the user password is erroneous, authentication as a whole will fail, and the presence of two concepts that are loosely coupled is beneficial in this way.
To implement authentication, I used JWT (JSON Web Tokens) over cookies for several reasons. 1) They work much better with a mobile device 2) do not have a session, which greatly simplifies the implementation of the server and is not subject to CORS attacks, as far as I know. JWT seems to be the best solution when working with mobile devices. I have used many npm libraries, especially express-jwt and jsonwebtoken , to perform server side authentication.
As I mentioned above, not only did I try to authenticate, I also want to allow users to subscribe to any third-party service that they want, such as Facebook, Twitter, to reduce user friction during registration. Having thought about this for a while and worked many times, I came up with the idea of identity card providers, an authentication system in which each "type of account" is considered as a separate identity provider and generalized to provide information such as access_token, user_id, expiration data etc. Identity providers are very similar to the "linked accounts" that you see on many application settings pages. On the iOS side of things, I created an abstract class, and for each service I want to support, I created a specific subclass of FacebookIdentityProvider , LocalIdentityProvider (email / password), etc.
On the server side, I used Passport modules to support each type of identity provider. For example, they have a facebook-token module, one for user email and passwords, etc. Thus, I made one api /authenticate route that my clients make using a serialized identity provider and based on the identifier string local , facebook-token , the passport would name the corresponding submodule for authenticating this provider based on the information provided.
In general, the security flow is as follows:
- The client checks the drive for the previous JWT token (stored securely with Lockbox ).
- If a token is detected, the client sends a request to my
verify . This endpoint checks if the token is valid and has not expired. - If the token has not expired, 200 is sent to the client and everything is fine in peace. Otherwise, the client will make a request to my
refresh_token with the expired token, which will try to reissue the token. If this fails, the client makes a request to my authenticate endpoint, which can only be called as a result of user action. - If the token is not found initially on the disk, the same thing happens at the end of 3, the client must wait for the user to authenticate.
When all this has been done and implemented, I am still a little blurred by a few things. First of all, I read something on the express-jwt token revocation page. What determines when I should revoke a token and re-enter the user login? It makes no sense to continue updating your token every time it expires endlessly.
Secondly, when I send the serialized identity provider to the server, I pass in a dictionary of additional information that the passport will use for authentication based on the process. If successful, an identity provider is created for this user and stored in the database. Is this enough, or should I do more with access_token and other fields that I get from a successful call? In particular, using the SDK for Facebook, I get an access token when the client authenticates through the application, and then another token when the client authenticates again with the server.
Another idea I had was for someone to integrate the api key that was passed with each request either through the header or request parameter. The api key will be kept secret and protected on the client side. I think this will do another level of “authentication” even for clients who have not yet gone through the authentication process. Only clients with an api key could even reach my api in the first place, and only those clients could try to authenticate.
My background is formal cybersecurity (I've never been so good), and now full-fledged development of mobile applications, so I understand this material better than most, but I feel that I can not find some potentially dangerous holes. Unfortunately, I can’t publish the code, because this is for my business, but if I don’t explain anything, I just comment and I would be happy to develop it.
I also believe that all this is done over SSL, which I configured using Nginx, and all my iOS network requests are done using Overcoat . In the end, I want to use Nginx as a load balancer, but this is a message for another day.