Anyway, here goes:
Well, the Rails project I started a week ago has become a bit of a monster. Basically it is a proof of concept right now. The minimum goals are to allow a user to login to a web site or from a mobile device with either a registered account or through a service they are already a member of such as LinkedIn or Facebook.
After last week’s effort, I had the test server up, so I decided the next step was to look at authentication since I had a feeling it might have impacts on the data model. Also, since I needed to authenticate to other services (Facebook, LinkedIn, etc) I knew I would not be able to easily roll my own authentication scheme. So I struck out looking for gem that could meet my needs.
It didn’t take long to run across the Devise gem as it seems to be used everywhere.
Vanilla Devise
I installed Devise into my application per the instructions on their site.
It was fairly painless and worked great for authenticating the rails web site but this application also has the requirement to provide secure REST calls for mobile apps.
That’s where things got complicated. One way to support this, and the one I zeroed in on, was to use token authentication. The Devise gem used to have support for that baked in but in the latest versions it was removed.
In the Devise documentation they explain why token authentication support was removed and provided links to two gems that would add it back in, devise_token_auth and simple_token_authentication.
Devise_Token_Auth
I first started with devise_token_auth as it looked to be much more robust and, quite frankly, when something has the word “simple” in it’s title like simple_token_authentication does, I read that as indicating while it may be simple it isn’t necessarily good for production.
I spent several hours adding the devise_token_auth gem in. The first hurdle was that it sits on top of Devise and as such when I originally installed Devise, the database migration wasn’t quite the same (at least out of the box) as the database migration for devise_token_auth.
Since I am just learning the intricacies of Devise and token authentication I blindly followed the installation and configuration instructions for devise_token_auth. That was probably my first mistake, not surmountable, but a mistake none the less.
I was expecting it to create a migration to add to my existing user schema, but instead it created a new migration that created the same table as my original Devise authentication table, which obviously would fail if I ran “db:migrate". Uh oh!
So to get back to a stable state I decided to remove the original Devise migration and use the one devise_token_auth generated instead. This caused me to have to dump the database and recreate it. I also had to manually change the order of the generated migrations so the user account would be created first.
This gave me one of those “what would happen in production if I had to change the authentication scheme” moments. I decided that would not be a good day for me. At this point in my exploration I was getting the feeling I was doing something wrong or didn’t understand something very fundamental.
It turns out devise_token_auth really is Devise with token authentication added back in. So, in hindsight I should have just started with the devise_token_auth gem, and not included and configured vanilla Devise beforehand.
Once I corrected my mistake and got everything back up, reran the migrations and seeded the database I ran into an error in the devise_token_auth code itself. It seems that the version of the devise_token_auth gem I had wasn’t compatible with the version of Devise I had originally installed. Welcome to gem hell.
And, because the devise_token_auth gem is packaged as an engine, many of it’s inner workings were hidden from me. To be honest I like the idea of packaging gems as engines, if they work, but this concept was foreign to me and made troubleshooting a bit harder when troubles arose. That didn’t feel good to me.
At this point I could have removed the Devise gem from my Gemspec and let the devise_token_auth gem install what it needed, but at the time I guess I was too dense to know that was the best course of action, so...
Simple_Token_Authentication
To make progress I decided to switch to the other gem, simple_token_authentication. As the name states it was simpler. Another thing about it, that felt more comfortable to me, was it isn’t packaged as an engine so it was more what I was used to. Finally it doesn’t replace Devise, instead it just enhances it a bit. Again, what I was expecting.
I followed the install instructions, added the before action, and created the migration as outlined on their site. I was up and running again, the rails web site was now secured again and I had token authentication added in. Next hurdle was a mobile client.
Mobile Integration
To do this I decided I would create a test iOS app that would first call the REST api on the rails app to sign-in, get the authentication token and then call a different REST method to get some data. I figured if I could do this then I would have a basic setup that could be fleshed out further.
Unfortunately, I quickly ran into Apple’s ATS (App Transport Security) changes made in iOS 9. These changes require the following of the server and client communication (from Apple’s site):
- The server must support at least Transport Layer Security (TLS) protocol version 1.2.
- Connection ciphers are limited to those that provide forward secrecy
- Certificates must be signed using a SHA256 or better signature hash algorithm, with either a 2048 bit or greater RSA key or a 256 bit or greater Elliptic-Curve (ECC) key.
- Invalid certificates result in a hard failure and no connection.
This created a different problem that I didn’t expect. I could sign-in and get a valid authentication_token, but on the second REST call to get data I would get a string in my JSON saying "the certificate was not secure would I like to proceed anyway?"
Apparently, a self-signed certificate isn’t good enough to pass the check list in ATS. I googled around how to add exceptions to my app’s configuration. There appear to be several ways to get around the problem. I feel like I explored all of them but to no avail.
First, you can just allow all connections and disregard the invalid certificate problem by adding NSAllowsArbitraryLoads to your NSAppTransportSecurity section of your info.plist. I did this first and my proof of concept app was up and running, albeit without any security checking.
But, I know this isn’t the way to ship, and I figured if our project did go into beta production we would probably be using a self-signed certificate, so I dug deeper into the ATS configuration options.
According to their documentation, I should be able to set the NSExceptionAllowsInsecureHTTPLoads to by-pass the invalid certificate exception I was getting when making the second REST call. I tried many different variations and tried a lot of other suggestions I found on Stack Overflow. But I just couldn’t get it working, if anyone knows how (and has successfully done it) I would be very interested in what you did.
Conclusion
In short, this is a short synopsis of how painful adding token authentication has been. Looking back at this post it doesn’t appear to be as bad as it was in reality. I think one of the things that really tripped me up, and continues to this day, is all the terminology the security gurus use. Just reading through the documentation on the various gems, they throw out a lot of new terms I was not familiar with so that probably added to my frustration.
So in the end, I think once we get closer to production and we have a legitimately signed certificate all of this will go away. But for now, until I can figure out how to do it the right way, we’ll have to have our iOS app continue to “punch” through the security settings with the NSAllowsArbitraryLoads option. Not ideal but expedient.
My final thoughts are that if I was doing this over again, and had more time to research and try things out, I would start with devise_token_auth as it feels much more robust and thought out but, at least at this point, I’ll go with the simple_token_authentication gem so I can make progress on the rest of the app.
I had run into this problem as well and it looked like no combination of flags were going to cut it. Even after setting the NSAllowsArbitraryLoads to true, the REST POST call resulted in server cert is not valid error. I am posting to a dev server which probably has a self-signed cert.
ReplyDeleterunning /usr/bin/nscurl --ats-diagnostics --verbose http://
listed all fails
In the end I used this configuration in my info.plist and was able to successfully post to the server
NSAppTransportSecurity
NSAllowsArbitraryLoads
NSExceptionDomains
http://www.myserver1.com
NSIncludeSubdomains
NSExceptionAllowsInsecureHTTPLoads
http://MyServer2.net
NSIncludeSubdomains
NSExceptionAllowsInsecureHTTPLoads