If you missed Part II: Loading JavaScript Properly, you may want to go there and grab the code we wrote so far so that you can follow more easily what we’ll do in this part.
Today, we’ll see how to implement a RESTful, stateless authentication layer in our application. Let’s first talk a bit about REST; the acronym stands for “REpresentational State Transfer” and describes a way of handling client-server transactions in a uniform, layered, stateless, scalable and cacheable way. I know that’s a lot of adjectives to process so I won’t delve into much more details here. We’ll focus on the stateless constraint, which requires that the exchanges have no memory per se, they always contain all the information that the other party has to know to process them correctly.
The REST philosophy is widely considered one of the best ways to solve communication challenges between a client and a server over the Internet. You may want to read the linked Wikipedia article for more details about the how and why.
Now what we’re interested in is the design of a system in which we can “phone home” from our application to an online server. This is needed for applications which have to pull data from a remote host like news items or score boards, or on the contrary push data to a remote host like creating a new account or updating profile information. Obviously such a system must allow for authentication, or we could end up with users accessing or modifying other users’ data at will!
Phoning Home (Cross-Domain) With jQuery
Phoning home is surprisingly easy with PhoneGap & jQuery, and at the same time it can prove hard to do it right. What I mean is that on the one hand jQuery provides us with powerful tools like jQuery.ajax(), which we can leverage in order to query our servers; on the other hand, there is a bunch of things we need to do so that our query is not blocked by the anti-cross-domain security features embedded in web browsers like Chrome.
Thanks to jQuery, the client code is rather straightforward; we simply need to add a function like this to our App namespace:
"servers": { "public": { "URL": "https://foo.bar/public.server.php", "query": function (action, data, callback) { console.log("[public.query]"); jQuery.ajax(App.servers.public.URL, { "type": "GET", "dataType": "json", "data": {"action": action, "data": data}, "contentType": "application/json", "success": callback }); } } }
As for the server, you can hack something quite quickly in pure PHP:
<?php function successJSON ($content, $die=true) { wrapJSON('SUCCESS', $content, $die); } function errorJSON ($content, $die=true) { wrapJSON('ERROR', $content, $die); } function wrapJSON ($code, $content, $die=true) { // encode content as {foo:bar,foobar:baz} $content = json_encode((object)$content); // strip the first character '{' from the json encoded string $content = substr($content, 1, strlen($content)-1); // add the "code" part to the string $buffer = '{"code":"'.$code.'",'.$content; echo $buffer; if ($die) { die; } } // empty request? if (empty($_REQUEST['action'])) { errorJSON(array("message" => "empty_request")); } switch ($_REQUEST['action']) { case 'test': $data = 'yeepee'; if (!empty($_REQUEST['data']['foo'])) { $data = $_REQUEST['data']['foo']; } successJSON(array('foo' => $data)); break; default: errorJSON(array("message" => "access_denied")); break; }
Change the URL content in your client code and you can test the function from anywhere in your application:
App.servers.public.query("test", {"foo":"bar"}, function (response) { if (response && response.code) { if (response.code === 'SUCCESS') { PGproxy.navigator.notification.alert(response.foo); } else { console.log("error while phoning home!"); } }); // should result in an alert box saying "bar"
I wrote “should” specifically because if you try it locally (on your MAMP/LAMP/WAMP stack) with a remote server, it’s actually probable that it won’t work as expected. Here’s the thing: querying cross-domains from PhoneGap on a smartphone is typically not a problem, but doing so on your desktop system can be, due to browsers’ security policies.
To fix this, we have to (1) make the server tell the client it allows it to perform queries from a remote origin, and (2) make jQuery and jQuery.Mobile know they are allowed to perform cross-domain calls.
First, simply add the following lines to the top of your PHP file:
header("Access-Control-Allow-Origin: *"); header('Access-Control-Allow-Methods: POST, GET, OPTIONS'); header('Access-Control-Max-Age: 1000'); header('Access-Control-Allow-Headers: Content-Type'); header('Content-Type: application/javascript'); if ($_REQUEST['method'] === 'OPTIONS') { echo '1'; die; }
What does this code do? Basically, jQuery.ajax() will always start by pinging the remote URL with an HTTP OPTIONS request and checking the response headers; if it detects Access-Control-* headers and in particular Access-Control-Origin matches the source domain, then it will perform the “real” query. We die early if the request method is “OPTIONS” in order to prevent our script from processing the same data twice!
Then, make sure you have those lines in your jQuery.Mobile configuration code:
jQuery.support.cors = true; jQuery.mobile.allowCrossDomainPages = true;
Now you should be able to run the example call successfully on both your desktop browser and your Android smartphone!
Stateless Authentication Using Public/Private Tokens
Now that’s great and all, but how about sensible queries like updating a user’s account information or posting things in his name? As I wrote earlier, surely we can’t use this public server queries implementation, or we’d allow anyone to mess with our system!
In order to solve this problem, we’ll use an additional server script named private.server.php, which will only accept authenticated queries. Users will authenticate themselves using an email/password, like they do on most websites. The REST philosophy then requires us to pass authenticating information inside all our requests, as opposed to, say, using session cookies. We’ll use private/public tokens to securely authenticate the user across requests to our API without passing its login information, which is very sensible in essence, in every request.
Here’s how the login process works:
- App: Present the user a login form
- User enters his/her login info (email/pass, username/pass, etc.)
- Send the login information to public.server.php
- Server: Find user in the database based on the login info
- Retrieve his/her public & private tokens
- Send the two tokens back to the app
- App: Store the two tokens locally for future reference (using localStorage)
Having those two tokens, we can authenticate every subsequent query that needs so:
- App: Construct a hash from the private token and another value, say the timestamp
- We’ll use the sha1() algorithm: hash = sha1(private_token + timestamp)
- App: Send a query to private.server.php passing additional variables
- We’ll need hash, timestamp and public_token added to action & data
- We’ll automate this using an additional query method: App.servers.private.query
- Server: Find user in the database from the public token
- We actually only need the private_token field from our users table
- Construct the same hash from the (retrieved) private token and the (passed) timestamp
- Compare (reconstructed) hash to (passed) hash. If the two match, the query is authenticated
Stateless Exchanges While Logged In
Let’s assume we’re logged in. As we’ve seen, this means that we have stored our public and private tokens on the mobile device. Now all we have to do is create a brand new App.servers.private.query() method, which we will use for every authenticated query. I’ve actually written a wrapper method called App.servers.query, which allows us to have cleaner code. Here is the whole thing; take your time reading it, it should be quite easy to understand but please do not hesitate to post a comment if you need me to explain anything:
"servers": { "query": function (url, type, data, callback) { console.log("[query "+url+"]"); jQuery.ajax(url, { "type": type, "dataType": "json", "data": data, "contentType": (type==="GET" ? "application/json" : "application/x-www-form-urlencoded"), "success": callback }); }, "public": { "URL": "https://foo.bar/public/", // perform unauthenticated query "query": function (action, data, callback) { console.log("[public.query]"); App.servers.query(App.servers.public.URL+action, "GET", {"data": data}, callback); } }, "private": { "URL": "https://foo.bar/private/", // perform authenticated query "query": function (action, data, callback) { console.log("[private.query]"); if (!localStorage || !localStorage.getItem("token_public") || !localStorage.getItem("token_private")) { console.log("can't do private query: empty private/public tokens!"); return; } // used as seed for hashing the private token var timestamp = Math.round(+new Date()/1000); App.servers.query(App.servers.private.URL+action, "POST", { "timestamp": timestamp, "token_public": localStorage.getItem("token_public"), "hash": sha1(timestamp+localStorage.getItem("token_private")), "data": data }, callback); } } }
That’s it, basically: once you’re logged in, you can run whatever type of query you want in a very simple, unobstrusive way:
App.servers.public.query('deposit_cash', {"account":1,"amount":"€4,321.00"}, function (result) { /* ... */ }); App.servers.private.query('wire_money', {"account":2,"amount":"€1,234.00"}, function (result) { /* ... */ });
As for the private server, here’s what you could have set up:
// reject incomplete requests if (empty($_REQUEST['token_public']) || empty($_REQUEST['hash']) || empty($_REQUEST['timestamp'])) { errorJSON(array('message' => "empty_tokens")); } // try to find a user from the public token we've been sent (die if not found) try { $User = User::find('first', array('conditions' => array('token_public = ?', $_REQUEST['token_public']))); } catch (Exception $e) { errorJSON(array('message' => "invalid_tokens", "e" => $e->getMessage())); } // make sure the hash hasn't been tampered if ($_REQUEST['hash'] !== sha1($_REQUEST['timestamp'].$User->token_private)) { errorJSON(array('message' => "invalid_tokens")); } // try and parse any additional data we've been sent if (!empty($_REQUEST['data'])) { parse_str($_REQUEST['data'], $_REQUEST['data']); } switch ($_REQUEST['action']) { case 'check_tokens': // if we've arrived this far, it's all good successJSON(array('message' => "all_good")); // shouldn't happen break; case 'wire_money': // [...] // [...] successJSON(array('message' => "tranfer_order_placed")); // shouldn't happen break; default: errorJSON(array('message' => "invalid_action")); // shouldn't happen break; }
How To Authenticate For The First Time
Now how about logging in? Let’s start slowly with the addition of two fields in the users table: public_token and private_token, both as VARCHAR(40), NOT NULL and with an index so that we can retrieve their associated record quickly later on. You will then have to generate the two tokens in a random way upon registration, and I encourage you to generate new ones regularly.
Here’s a App.handleLogin function launched when the user submits the login form:
"handleLogin": function () { console.log("[handleLogin]"); var m = jQuery("#input-Login_mail").val(); var p = jQuery("#input-Login_pass").val(); if (m.length === 0 || p.length === 0) { console.log("! empty fields"); PGproxy.navigator.notification.alert("empty fields", function() {}); return false; } App.servers.public.query("login", { "mail": m, "pass": p }, App.callbacks.handleLogin, true); return false; }
… and finally the App.callbacks.handleLogin() callback:
"handleLogin": function(r) { console.log("[callback: handleLogin]"); if (r && r.code && r.code === "SUCCESS" && r.token_private && r.token_public) { console.log("user is now logged in!"); localStorage.setItem("token_private", r.token_private); localStorage.setItem("token_public", r.token_public); localStorage.setItem("id", r.id); // you can store whatever other data you want return; } else { PGproxy.navigator.notification.alert("login failed", function() {}); } }
Those functions were rather simple and intuitive, the most interesting part to my mind comes from the servers and their handling of the requests. Here’s a rough example in PHP of the code could have in your public server:
switch ($_REQUEST['action']) { case 'login': // exit early if obviously invalid fields if (empty($_REQUEST['data']['mail']) || empty($_REQUEST['data']['pass'])) { errorJSON(array('message' => "invalid_fields")); } // get corresponding user or exit early try { $User = User::find('first', array('conditions' => array( "email = ? AND password = ?", $_REQUEST['data']['mail'], sha1(SOME_SALT.$_REQUEST['data']['pass']) ))); } catch (RecordNotFound $e) { } if (empty($User)) { errorJSON(array('message' => "invalid_credentials")); } // send the tokens back to the app & die successJSON(array( "token_private" => $User->token_private, "token_public" => $User->token_public, "id" => $User->id )); // shouldn't reach this break; } }
Wrapping Up
Phew, that was a long post. I hope that these snippets and explanations will be somewhat useful to you; if you would like me to give more details on something specific please do tell me in the comments or send me a mail through the contact form, I’ll try my best to reply. This public/private tokens system is quite simple to handle once you’ve grasped the concepts around it, it is reasonably secure and is completely platform independant. I wanted to include a section on how to add even more security features to it like using a user-specific salt instead of a global one or implementing a retry limit, however the article was becoming way too long so I’ll talk about it in another part ;) Thanks for reading!
Thank you x 10000000000000000000000!!!!!!
I’m joining Marina’s enthusiasm! 🙂 You rock, thank you!
Thank you!!!!
Can you share me your source code.
My email [email protected]
thank you
Nice tut! thanks a lot!
It could be interesting effectively to share your source code with us.
I have several errors in my log console and don’t know how to go on.
(awesome fonts do not appear, app function is not defined…)
to get fonts to work replace
with
http://fortawesome.github.io/Font-Awesome/cheatsheet/
Excellent piece of work! Really appreciate it if you can publish the full working codes. Thank you very much again and keep it up!
Hey kenny, thanks for the kind words 🙂
I’m sorry I’ve been quite busy lately… I’ll definitely try publishing a fully working code sample tonight; stay tuned!
Nice work, but I have a doubt: as you’re using Local Storage to store the tokens, wouldn’t it be available to other local apps? I mean, the domain used to scope the local storage is the localhost, so every other app running on the app would be able to see this info right?
Hello Caio, thanks for your comment!
I have to admit, I sure was taken aback by your -excellent- remark for a second. Here’s the answer.
Unlike the now deprecated GlobalStorage, LocalStorage is designed from the bottom up to prevent data from leaking from one tab to another. The deciding criteria is a combination or the protocol, domain and port number (if not standard).
Under those premises, our PhoneGap app would indeed be protected from leaking to other web-based apps but we could very well be wary of it leaking to other PhoneGap apps because all of them would use the http/localhost/80 combination. Luckily for us, the issue is non existent thanks to Android specifically not sharing localStorage data between distinct WebViews. Each PhoneGap app will necessarily use theirs, hence them being protected 🙂
-Thomas
Thank you, that was a major concern for us! I’m glad that it’s safe enough (:
Looks good, did you ever publish that source code? Would help…
thx
Pingback: #3: A REST Auth. Layer | Coding An App On Phone...
Hii… thanks for tutorials,
i’ve tried it, but i can’t.
can you share your source code?
my email : [email protected]
Thanks! can you please share your code?
Hey buddy, all parts of the tutorial is great!
Thanks for wasting time writing great things.
But as you didn’t make the code public, could you send me it?
I am having some problems, and can’t wait to make it right.
Thanks! can you please share your code please?
Hey Thomas,
Thanks for the writing! It’s all good and well, but I’m concerned about this part:
“App: Present the user a login form
User enters his/her login info (email/pass, username/pass, etc.)
Send the login information to public.server.php
Server: Find user in the database based on the login info
Retrieve his/her public & private tokens
Send the two tokens back to the app”
If I get it right that data is sent as is in plain text. Are you using SSL for this? I mean what’s the point in using public/private key system, if the initial process of getting the keys is insecure?
Hey Andy, thanks a lot for the kind words.
You’re absolutely right! Using an insecure, unencrypted connection to send the user’s (login, password) tuple is problematic to say the least.
The URLs that I’ve used in my code samples were only intended to serve as an illustration of the different types of scripts involved (public & private, respectively). The public.server.php script is definitely not to be understood as unsecured access to our backend, only unauthenticated access. I have updated the article to make it clear that the whole setup should use SSL-enabled URLs. 😉
Happy coding!
Thomas
Looks better now 😉
The process of user registration and authentication in mobile apps appears to be the most tricky part to handle. Your article has really helped me in this! Many thanks Thomas!
Can you share me your source code?
Thank you sir!
Thanks for this post =)
It was exactly what I was looking for!
Thank you so much for this clear presentation.
Could you please share source code for this project?
Thanks a lot. This is very helpful.
Appreciate you can share the source. Thanks.
Wow, just wow.
Your code is so elegant haha, I understood every single bit, very nice coding decisions too.
Thanks for this!
Also I have existing accounts I want to integrate your system into, what would be the best way of generating public/private keys for these accounts?
Can I just create a random algorithm for both public/private keys using some form of hashing based on their user ID or something unique?
Thank you very much for this excellent tutorial! I was just wondering why we have public and private keys on the client. Could an alternative be asymmetric encryption? Example: client has public key, server has private key. Client sends his id, a timestamp and a public key encrypted timestamp to the server. Server gets private key using the id, decrypts encrypted timestamp with private key and compares it with the client’s timestamp. Or do you see a flaw in that? Thanks.
Thank you for the educational article.
Wouldn’t storing the public and private key locally on the mobile be a security risk?
Also, you mentioned you were going to post your full source. Did you get a chance to post it?
Pingback: Gregory Smith
Great article! I implemented it on a phonegap app with https php backend.
It seems like a very safe way of authentication but is there still a way to hack this? (= impersonate another user) (except hacking the database or stealing the password of a user)
I am just wondering since you say it is “reasonably secure”.
Pingback: Vanessa Smith
There are few details missing:
1. The callbacks.handleLogin construction is missing in the example. It should start like this:
"callbacks": {
"handleLogin": function(r) { console.log("[callback: handleLogin]");
....
2. Javascript doesn’t have its own sha1 function. I used this one
3. The servers.public and servers.private javascript functions won’t correctly format the URL and action if you want to use a folder name as the destination page for your ajax call. I made the following change:
.....app.servers.public.URL + '?action='+ action, "GET",....
4. To test with a local browser you can start Chrome with the following flags to enable crossbrowser loading. (works on MacOs in terminal)
open -a Google Chrome --args --disable-web-security