Regis' Blog

L'informatique, L'amour, Les vaches

Identify your users with oauth2 in Cordova using OAuth.io
September 27, 2014
|
Share this post
| |
Identify your users with oauth2 in Cordova using  OAuth.io

When we write an hybride application, we think immediatly to Cordova. We build our app using the framework HTML/CSS/Javascript you want (for example Ratchet 2 or jQueryMobile). However, there’s still a problem: The user authentification. While we stay in a classical path, there isn’t problems. But when the moment to integrate users authentification with third party services, such as Google, Facebook or Twitter, the problems start because Cordova is built to use static HTML pages. It’s your javascript that will handle the functionnalities.

You can, of course, use the authentification with Javascript, but you will always have the landing page problem with the glorious  redirect_uri. More! You must handle two different protocols  OAuth1 and OAuth2. Linkedin and Twitter for example use OAuth1 and Facebook and Google use OAuth2.

That’s here the  OAuth.io startup decided to offer a unified service. Their catchword : « OAuth that works ». It’s true, it’s simple. With the free account, you could have two providers and a limited amout of users each month. It’s enought for a small app and/or the tests.

Simple, it’s quite difficult to be more. You configure your OAuth.io account. You create your providers account. You download and integrate the plugin (iOS, Android ans Windows Phone 8) in your project. You write your HTML and a little bit of javascript. And yes, it’s true… It works. But the documentation stops here and it’s quite logical. You are supposed to know what you are doing. But, the server side problem is still here.

In this article, I will not to explain to you (or very quickly) how to create a app with Cordova and OAuth.io but what you must do after.

The Cordova app

We will use big classics with Cordova:

cordova create helloworld fr.regisblog.helloworld HelloWorld

Then install the oauth.io plugin:

$ cd helloworld 
$ cordova platform add android # ou iOS 
$ cordova plugin add https://github.com/oauth-io/oauth-phonegap

Modify the HTML file

Everything is explained on this page and if you know Cordova, it’s usual. We will after edit the www/index.html file to add two buttons. One for Google and another for Facebook:

        <meta charset="utf-8" />
        <meta name="format-detection" content="telephone=no" />
        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
        <meta name="msapplication-tap-highlight" content="no" />
        <title>Hello World</title>
        <style>
            button {
                padding: 10px;
                margin-bottom: 10px;
                width: 100%;
            }
        </style>
    </head>
    <body style="padding: 5px">
        <div style="text-align:center">
            <h1>Tap on a button to connect</h1>
                <button id="facebook">Connect with facebook</button>
                <button id="google">Connect with google</button>
            </h1>
        </div>
        <script type="text/javascript" src="cordova.js"</script>
        <script type="text/javascript" src="js/index.js">/script>

Notice that oauth.io is directly loaded by Cordova.

The javascript file

Let’s open the javascript file:js/index.js. You already understand it, for this example, I try to keep the original architecture.

/** Generic function to connect
 * @param {String} accountName The provider name as defined in the doc
 * @param {Function} callback The function to callback in any case 
 */
function connectWith_(accountName, callback) {  
// Ask for auth. In production use cache:true to accelerate the process
     OAuth.popup(accountName,{cache:true})
         .done(function(result) { // Success. Let's get the information
             result.me().done(function(r) { // Success. Let's call the callback with information as argument.
                 callback(false, {
                     email: r.email,
                     name: r.name,
                     avatar: r.avatar,
                     access_token: result.access_token,
                     provider: accountName
                 });
             })
         .fail(function(error) { // No information. The rare case. Mainly due to a service stop
             callback(true, error.message);
         });
     }).fail(function(error) { // Wrong code, wrong connection, or user cancelation
         callback(true, error.message);
     });
   } 
/** Callback to use the response
 * @param {Boolean} err true if there's an error
 * @param {Object|String} response if err === true the response arg contains the error message 
 *                        Else, the response contains an object
 * @see connectWith_ 
 */ 
function socialResponse(err, response) {  
} 
/** Initialize the script when the DOM is ready */ 
function initialize() {  
     document
        .getElementById('facebook')
        .addEventListener('click', function() { 
            connectWith_("google_plus", socialResponse); 
        }, false);
     document
         .getElementById('google')
         .addEventListener('click', function() {
             connectWith_('facebook', socialResponse);
         }, false);
     // Initialize the OAuth.io library
     OAuth.initialize('you-private-key-here');
} 

// When cordova is ready, it emits the deviceready event document.addEventListener('deviceready', initialize, false);

The code is easy to understand, and it’s well documented (who said to much?). The first thing you may notice is great homogeneity of the code. Google and Facebook are called in the same way.

However, there’s a little trap. You must use Google Plus and not Google. Google Plus contains much more information than Google and with G+ you can use the .me() function but not with Google.

If everything goes right, the callback function is called with err===false and response contains an object with few information (email, avatar and the user name) but also a token (which is very important).

For now, only the Cordova app knows that the user is authenticated. But the server don’t know it. You can think to every solutions, the provider must certificated the token you own.

The server request

In the socialResponse function we add:

function socialResponse(err, response) {  
    if (err === true) {
        alert("An error occured: " + response);
    }
    var xhr = new XMLHttpRequest();

    // AJAX response 
    xhr.onload = function() { 
        // We wait the server response (4)
        if (xhr.readyState === 4) { 
        // Response without error 
            if (xhr.status === 200) { 
                alert("Know the server know the use"); 
            } 
        // Response with an error 
            else { 
                alert("Error : "+ xhr.statusText); 
            } 
        } 
    } 

    // Manager AJAX error 
    xhr.onerror = function() { 
        alert("Error : "+ xhr.statusText); 
    };

    // Post it 
    xhr.open('POST', '/my-url-connect-social/', true); 

    // We are telling to the server that's a JSON request 
    xhr.setRequestHeader('Content-Type', 'application/json');

    // Send the request 
    xhr.send(JSON.stringify(response); 
}

It’s more or less the equivalent in javascript of the jQuery $.ajax(). In fact, we send to the server the response we already have.

Server side authentication

The magic is on the server side (here in Django but you can write the same with PHP or Rails without problems) :

def social_connection(request):  
    # Get the json query (external function)
    data = get_json(request)
    access_token = data['access_token']
    email = data['email']
    name = data['name']
    avatar = data['avatar']
    provider = data['provider']

    # Which provider
    if provider == 'google_plus':
        url = "https://www.googleapis.com/plus/v1/people/me?access_token={}"
    elif provider == 'facebook':
        url = "https://graph.facebook.com/me?fields=id&access_token={}"
    else:
        return HttpResponse(json.dumps({'success': False, 'error': "Unknow provider"}), content_type="application/json") # Is the provider knows the token ?
     http = httplib2.Http()
     result = http.request(url.format(access_token), 'GET')[0] # 200 == yes, otherwise == No
     if result['status'] != '200':
         # The server doesn't grant the user
         return HttpResponse(json.dumps({'success': False, 'error': "OAuth2 provider don't grant your identity"}), content_type="application/json")
     # Register your user here
    return HttpResponse(json.dumps({'success': True}), content_type="application/json")

Et voilà! We asked to the provider if it knows the key and if the user is connected. If so (200 response) the user can be created in your database.

You can also determinate if the authentication by third party is required or not. You can check it out each time or, like me, use your own system (based with another key).

Conclusion

It’s incredibly simple, not expansive (from free to 99 USD a month) and you’ll save a lot of time. The only missing part of the OAuth.Io documentation is the server part.  For a personal project with a budget dramaticly close to 0$, it’s possible to use two providers. For a professional project, half an hour of a developer salary is equal to a monthly plan).

For your information, the first time I wrote an third party authentication, I spend 3 days (about 1.000 €).

September 27, 2014
|
Share this post
| |