Social Login for mobile apps with Ionic / Cordova + PHP + OAuth + JWT

Recently I’ve started looking into building hybrid mobile apps with Ionic (based on Apache Cordova) and one of the first things I realized was that my existing authentication flow with Facebook & Co. won’t work anymore.

My flow for Web-Apps with AngularJS (Frontent) and PHP with HybridAuth (Backend) is like this:

The Web-App flow

  1. call backend (e.g ../login.php&provider=Facebook)
  2. backend responds with redirect to Login provider
  3. user logs in with provider
  4. provider calls redirect URL on backend with the authorization token (e.g. ../hybridauth_endpoint/?hauth.done=Facebook)
  5. backend checks whether user is already registered (i.e. is in our DB), if not: add the new user in DB
  6. backend creates JWT
  7. backend redirects to app passing in the JWT as route parameter (e.g.: ../#/loginsuccess/<JWT>)
  8. web-app validates JWT and puts it into Local Storage
  9. as long as the JWT exists (and isn’t expired) the app considers the user as logged in
  10. web-app intercepts subsequent calls to backend and adds an authorization header with the JWT
  11. backend extracts the JWT from the authorization header and validates it, if valid & not expired => user authenticated

Note: Article that helped me to get going with JWTs & PHP (uses Firebase’s php-jwt)

The Mobile-App flow

All bright & RESTful until that day where we want a ‘native’ (hybrid mobile-app in my case) instead of a Web-App to be able to access some of the mobile device’s features (e.g. GPS, address book, etc.).
So we add the Ionic Framework (incl. Cordova) and some Cordova plugins to the frontend side to create our now hybrid mobile ‘native’ app.

Now trouble starts: we can’t redirect to the ‘native’ app as we can do this for a web-app (Step 7 in the flow above). Hm, how to solved that?

So far I didn’t want / had the need to do the authentication “client side (first)” although there is a great module for Angular called Satellizer. But beggars can’t be choosers so after a few days racking my brain over this I finally came to the conclusion that I have to do the authentication client-side first and then on the backend in a second step.

To make the problem clear: if we would do the authentication server-side (using HybridAuth) first we would need a way to let the client know that the user is authenticated and so far I haven’t found a solution for this.

So what we need to do is:

  1. Authenticate the user with the OAuth provider client-side
  2. Grab the authentication token from the provider’s response
  3. Pass the token to the backend and authorize the user with this token at the backend-side

Solution for steps 1. + 2. is easy: there are modules as cordovaOAuth and very good documentation for this.

Note: looking at Satellizer‘s page again for this article I’ve found that it includes (now) details for mobile apps with cordovaOAuth as well. (Might have missed that before simply because that wasn’t my concern at that time.) 

Passing the token to the backend using a HTTP GET or POST isn’t magic either but how to authorize at the backend with the access token?

That turned out to be the tricky part given that I wanted to stick with HybridAuth but fortunately people 1000x more clever than I am found a solution already. (see here  and here)

So my Mobile-App flow looks like this:

  1. authenticate the user with the OAuth provider client-side using cordovaOAuth
  2. grab the token and POST it to the backend
  3. backend connects to provider and pulls the user profile with HybridAuth and the token received
  4. backend checks whether user is already registered (i.e. is in our DB), if not create a new user in DB
  5. backend creates JWT and returns it in response to the call in step 2.
    And the rest is the same as steps 8. – 11. of the Web-App flow

The Implementation

I will not provide a complete example here – especially not the creation and handling of JWT on backend and app side because there is plenty of existing documentation for this – but just what I believe is key to a working solution.

Mobile-App (Ionic / Cordova / Angular)


.controller('LoginCtrl', function ($scope, $cordovaOauth, $http) {

    var loginWithBackend = function (provider, token) {
        var data = {provider: provider, token: token};
        $http({
            method: 'POST',
            url: '&amp;lt;PATH_TO_AUTH_SERVICE&amp;gt;' // example: http://localhost/oauth
            data: data
        })
        .then(function (success) {
            alert(JSON.stringify(success.data));
            //get JWT from response
            ...

            //put JWT to Local Storage (or Session Storage)
            ...

        }, function (error) {
            alert(JSON.stringify(error));

       });
    };

    $scope.loginGoogle = function () {
        $cordovaOauth.google(&amp;quot;&amp;lt;CLIENT_ID&amp;gt;&amp;quot;, [&amp;quot;profile&amp;quot;])
        .then(function (result) {
            loginWithBackend(&amp;quot;Google&amp;quot;, result.access_token);
        }, function (error) {
            alert(error);
        });
    };

    $scope.loginFacebook = function () {
        $cordovaOauth.facebook(&amp;quot;&amp;lt;CLIENT_ID&amp;gt;&amp;quot;, [&amp;quot;public_profile&amp;quot;])
        .then(function (result) {
            loginWithBackend(&amp;quot;Facebook&amp;quot;, result.access_token);
        }, function (error) {
            alert(error);
        });
    };

})

Backend (PHP / HybridAuth)

Note: I’m using Slimframework for the REST services but I think it’s not hard to understand and to port to other frameworks.


$app-&amp;gt;post('/oauth', function($request, $response, $args) {
    try {
         $parsedBody = $request-&amp;gt;getParsedBody();
         $provider = $parsedBody['provider'];
         $token = $parsedBody['token'];

         $hybridauth = new Hybrid_Auth(HYBRID_AUTH_CONFIG);

         $hybridauth-&amp;gt;storage()-&amp;gt;set(&amp;quot;hauth_session.&amp;quot; . strtolower($provider) . &amp;quot;.is_logged_in&amp;quot;, 1);
         $hybridauth-&amp;gt;storage()-&amp;gt;set(&amp;quot;hauth_session.&amp;quot; . strtolower($provider) . &amp;quot;.token.access_token&amp;quot;, $token);

         //Authenticate
         $authProvider = $hybridauth-&amp;gt;authenticate($provider);

         //Get user profile
         $user_profile = $authProvider-&amp;gt;getUserProfile();

         //Extract user identifier
         $oauthId = $user_profile-&amp;gt;identifier;

         //Check whether user already exists
         ...

         //User doesn't exist yet, so save to DB
         ...

         // Create JWT
         ...
         
         // Return the JWT in the body the response
         return $response-&amp;gt;withJson([&amp;quot;token&amp;quot; =&amp;quot;&amp;lt;THE_JWT_JUST_CREATED&amp;gt;&amp;quot;]);
     } catch (Exception $ex) {
             return $response-&amp;gt;withJson([&amp;quot;error&amp;quot; =&amp;gt;&amp;quot;Not Authorized&amp;quot;], 401);
     }
});

That’s all folks, problem solved!

Any questions, corrections, additions? Just leave a comment, I’ll get back to you soon. Share if you found this useful.