Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firebase $onAuth has wrong authData after $authWithOAuthRedirect from Facebook

I am trying to authenticate users of my Firebase (Angularfire) app with Facebook Login.

Everything works as expected when I authenticate with a pop-up window, but to support as many browsers as possible (Chrome on iOS doesn't support pop-ups, for e.g.) I want to fallback to authenticating with a redirect ($authWithOAuthRedirect).

I have confirmed my setting in Facebook are correct (my app ID and secret, for e.g.) but when I am redirected back to my app after Facebook authenticating with a redirect, $onAuth fires but I don't have my Facebook authData.

Instead, I have anonymous authData. For a bit of background; all users are authenticated anonymously if they are not otherwise authenticated (with Facebook, in this e.g.).

I can't see to find why this would be - the user should now be authenticated with Facebook, and have the Facebook authData.

Excepts of my code are below for some context:

Triggered when a user clicks the login button

function logIn () {
    firebaseAuth
        .$authWithOAuthRedirect('facebook', function (error) {
            if (error) {
                console.log(error);
            }
        });
}

$onAuth (inside my Angular app's run)

function run ($rootScope, firebaseAuth, sessionStore) {
    $rootScope
        .$on('$routeChangeError', function (event, next, prev, error) {
            if (error === 'AUTH_REQUIRED') {
                console.log(error);
            }
        });

    $rootScope
        .$on('$routeChangeSuccess', function (event, current, prev) {
            $rootScope.title = current.$$route.title;
        });

    firebaseAuth
        .$onAuth(onAuth);

    function onAuth (authData) {
        console.log(authData);
    }
}

Route resolver to otherwise anonymously authenticates users

function sessionState ($q, firebaseAuth) {
    var deferred = $q.defer();

    firebaseAuth
        .$requireAuth()
        .then(deferred.resolve, guest);

    return deferred.promise;

    function guest () {
        firebaseAuth
            .$authAnonymously()
            .then(deferred.resolve, rejected);
    }

    function rejected () {
        deferred.reject('AUTH_REQUIRED');
    }
}

The route resolver (sessionState) checks to see if the user is authenticated already, and if not, tries to anonymously authenticate them.

After the Facebook authentication redirect, the user will already be authenticated, and therefore does not need to be anonymously authenticated.

But, it appears that they are? As $onAuth logs the authData to the console, and it is anonymous.

Any help with this would be much appreciated! I am sure it has something to do with my route resolver, as pop-up authentication works fine (the route is already resolved).

EDIT: I tried completely removing my route resolver in case it was that causing an issue, but it made no difference. The user was just 'unauthenticated' instead of being either authenticated with Facebook (after $authWithOAuthRedirect) or anonymously.

UPDATE: I tried authenticating with Twitter and the redirect transport and I have encountered the exact same problem. I have also tried using port 80, instead of port 3000 that my app was being served on locally, but no joy.

UPDATE: When I turn off html5Mode mode in my app - and routes now begin with #s - $authWithOAuthRedirect works perfectly. From this I can only assume that $authWithOAuthRedirect does not support AngularJS's html5Mode. Can anyone confirm this is an issue, or do I need to change my code to support html5Mode and authWithOAuthRedirect?

EXAMPLE REPO Here is an example repo demonstrating the problem: https://github.com/jonathonoates/myapp

Look in the dist directory - you should be able to download this and run the app to reproduce the problem. In scripts/main.js is the app's JS; I've added a couple of comments but it's pretty self explanatory.

To reproduce the problem: click on the 'Facebook Login' button, and you'll be redirected to Facebook to authenticate. FB will redirect you back to the app, but here lies the problem - you won't be authenticated, and the returned authData will be null - you'll see this in the console

UPDATE: When I add a hashPrefix in html5Mode e.g.

$locationProvider
    .html5Mode(true)
    .hashPrefix('!');

The app works as I would expect - authenticating with Facebook and the redirect transport works.

Couple of niggles though:

  1. The URL has #%3F appended to it, and is available/visible in the browser's history.
  2. This would rewrite URLs with #! in browsers that do not support History.pushState (html5Mode), and some less advanced search engines might look for a HTML fragment because of the 'hashbang'.

I'll look into highjacking the URL upon being redirected back from Facebook instead of using hashPrefix. In the URL there is a __firebase_request_key which may be significant e.g.

http://localhost:3000/#%3F&__firebase_request_key=
like image 635
Jonathon Oates Avatar asked Jan 16 '16 17:01

Jonathon Oates


1 Answers

It looks like this is indeed an incompatibility between Firebase and AngularJS's html5mode as you suspected. At the end of the redirect flow, Firebase was leaving the URL as "http://.../#?", and Angular apparently doesn't like that so it did a redirect to "http://.../" This redirect interrupts Firebase (the page reloads while we're trying to auth against the backend) and so it is unable to complete the authentication process.

I've made an experimental fix that ensures we revert the URL to http://.../#" at the end of the redirect flow, which Angular is happy with, thus preventing the problematic redirect. You can grab it here if you like: https://mike-shared.firebaseapp.com/firebase.js

I'll make sure this fix gets into the next version of the JS client. You can keep an eye on our changelog to see when it is released.

like image 176
Michael Lehenbauer Avatar answered Oct 11 '22 14:10

Michael Lehenbauer