FirebaseExtended / angularfire

AngularJS bindings for Firebase
MIT License
2.73k stars 631 forks source link

authWithOAuthToken bug on mobile devices #637

Closed karensg closed 9 years ago

karensg commented 9 years ago

Hi guys,

I am experiencing an issue with a function authWithOAuthToken on mobile devices. I login a user with gapi library and get a token. Then I try to login the user in Firebase with:

Auth.$authWithOAuthToken('google', token).then(redirect, showError);

This works on my desktop, however, not on my mobile devices (tried Nexus 5 and iPad).

I tested that the token is correct and the same on both devices. Just to be sure, I include the json dump of the token data:

{"state":"","access_token":"ya29.xxx","token_type":"Bearer","expires_in":"3600","scope":"https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/plus.me","id_token":"eyJHbGciOiJSxxx","client_id":"38812610813-xxxx.apps.googleusercontent.com","response_type":"token id_token","issued_at":"1436558734","expires_at":"1436562334","g-oauth-window":{},"status":{"google_logged_in":false,"signed_in":true,"method":"PROMPT"}}

Is this a bug?

katowulf commented 9 years ago

This may very well be a bug, either in the cross-platform lib you're using (Cordova? Important to include the details here) or in Firebase. But very unlikely to be a bug in AngularFire, which simply ferries the request from you to Firebase. Without a minimal repro, (i.e. a stack trace, an error message, and full version info; the minimum amount needed to reproduce the error) we won't be able to help much.

karensg commented 9 years ago

No, I am not using any mobile cross-platform library such as Cordova. So I guess it should be a bug in Firebase then. I will try to create a minimal repro after the weekend.

jwngr commented 9 years ago

Yeah, do you also have the error that is being returned by the Firebase client? I see you have a showError() method which catches the error, but you didn't include it in the post. It's hard to say what could be going wrong without it.

karensg commented 9 years ago

The error message is as follows:

"Error: Blocked a frame with origin "http://localhost:9000" from accessing a cross-origin frame.
    at Error (native)
    at da (http://localhost:9000/bower_components/firebase/firebase.js:4:271)
    at ea (http://localhost:9000/bower_components/firebase/firebase.js:5:105)
    at http://localhost:9000/bower_components/firebase/firebase.js:20:665
    at hb (http://localhost:9000/bower_components/firebase/firebase.js:20:550)
    at jb (http://localhost:9000/bower_components/firebase/firebase.js:20:646)
    at Dg.open (http://localhost:9000/bower_components/firebase/firebase.js:144:472)
    at Tg (http://localhost:9000/bower_components/firebase/firebase.js:160:293)
    at Rg (http://localhost:9000/bower_components/firebase/firebase.js:159:24)
    at Pg (http://localhost:9000/bower_components/firebase/firebase.js:154:168)"

This is not only the case with localhost but also with two other domains. I double-checked that all of the used hosts are added in the Google project Auth settings under 'Redirect URIs' and 'JavaScript origins'. They are also added in Firebase.

katowulf commented 9 years ago

This works on my desktop, however, not on my mobile devices (tried Nexus 5 and iPad).

Once again, we need a complete picture here of what you are doing and where things are failing. Can you please describe, in glorious detail, what "doesn't work" means here?

We need a complete mcve, including steps to repro, error messages, and version info, to even get started on this. Closing this. If you add those details we can re-open and explore.

karensg commented 9 years ago

I created a minimal repro with angularfire generator: https://github.com/karensg/google-login-bug-repro You can see the error on the login page.

Your help is appreciated.

jamestalmage commented 9 years ago

@karensg That does not feel like a "minimal" reproduction. You have multiple custom directives and a router. I appreciate that much of that may have been generated by yeoman, but it makes it hard to dig into what's happening.

Also, is the "Google Drive" auth scope necessary? If not, drop it.

jamestalmage commented 9 years ago

@karensg

Consider using Firebase's API to grab the tokens you need directly. Use authWithOAuthPopup or authWithOAuthRedirect instead of authWithOAuthToken and let firebase handle the whole thing for you. (See the scope option for enabling Google Drive if that is what you need).

karensg commented 9 years ago

@jamestalmage Thanks for your reply. I simplified the demo and pushed it back to the repository.

In the beginning I had the authWithOAuthPopup functionality. However, I had problems with double authentication when you are logged in with more than one Google account. I wanted to have only 1 login to solve this bug. I found this thread on Stackoverflow and implemented it according to @katowulf's suggestion. According to the post, I assumed that the token which is produced by Firebase can not be used for Google Drive interactions.

What are your thoughts about it?

Karens

PS: the authWithOAuthToken example on the API reference website is incorrect.

jamestalmage commented 9 years ago

I can't speak directly to the Google situation, but I have used Firebase to provide the authentication flow for Github API's, and it worked fine. I'd recommend trying the token returned by Firebase, if it works, you'll be moving forward with a much nicer API.

You may need to inspect the authData returned to your callback for the token value that will actually work to authenticate against google drive (that is what I had to do with GitHub).

jamestalmage commented 9 years ago

Now that I've actually read kato's post I think your initial instincts were correct. Can't hurt to try though. When I get near a PC I can try your repo again.

jamestalmage commented 9 years ago

OK, I have a working example completed. It is not as straightforward as it was for GitHub, but definitely doable. I've written up a gist complete with example code, instructions, and some explanation on how I did it.

karensg commented 9 years ago

Thanks for your extensieve answer. I will check it out as soon as I am in the neighborhood of my laptop. Your help is very appreciated

jwngr commented 9 years ago

Fantastic write up @jamestalmage! Thanks so much for diving in and getting this issue resolved. I'm going to be sending people to that gist for many months to come :)

@karensg - I think James left you with a working solution. Also, I just sent a PR with a fix for our AngularFire docs. The fix should get deployed in a day or two.

jamestalmage commented 9 years ago

When it comes right down to it, the authentication part of the solution is only about 10 lines of code. They were a hard won 10 lines for me, but now that I know how to do it, it really is about the simplest OAuth solution out there (though it could be even better).

At some point, it would be nice to see authData.google get reformatted so it could be passed directly into gapi.auth.setToken() without reformatting. For example, my code doesn't set the expires value on the object. I'm not sure what the implications of that are. There could certainly be other such minor details that need exploring.

Also, when you use gapi.auth.authorize directly, the value returned to the callback contains a list of the included scopes. That would be handy for knowing if you needed to create an additional request for whatever action you were about to perform. The developer docs for Google Sign-In specifically state that you should request permissions "as you need them", (i.e. don't ask for access to Drive until they click "Save to my Google Drive"). They detail how to ask for "incremental" permissions in those docs. With some of the Google+ SignIn stuff you can deny specific permissions (i.e. deny access to the friend list, etc). It would nice to have that data available so you can make decisions.

Pretty much every OAuth provider you guys integrate with takes a scope argument, and has their own version of "incremental permissions" (requesting scopes as you need them). It would be awesome if Firebase made all of that dead simple as well. Right now if you call authWIthOAuthPopup, the user is going to see a popup whether or not you already have access to the requested scopes. It would be cool if you parsed those and simply just "faked" a success callback if we already have all the pertinent permissions.

See the hasGrantedScopes(), and signIn(options) from the Google Sign-In docs

karensg commented 9 years ago

@jamestalmage Thanks a lot for your help and your willingness to dig in to the problem. I implemented your approach into my application and it finally works! I agree with you that authData.google should always represent the token which is used by Google, so it can be passed immediately. Also, a good part of this solution is that the google clientId can be removed from the code, as it is already available in Firebase and used from there.

Keep up the good work! Karens