pennersr / django-allauth

Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication.
https://allauth.org
MIT License
9.51k stars 3.03k forks source link

Allow trusted providers to link different Uids to the same account #176

Closed stanhu closed 11 years ago

stanhu commented 11 years ago

I would like the ability to trust certain providers (e.g. Google OpenID) to supply a verified e-mail address so that I can access the system from different URLs without having to create a new account. Another words, I'm trying to solve the problem where if you login successfully with, say, http://localhost:8000 one day, and then decide to run the same server as http://localhost:9000 another day, that the different Uids generated from these URLs will still point to the same account.

StackOverflow has the best overview about the problem and what they did:

http://blog.stackoverflow.com/2010/04/openid-one-year-later/

"That’s a major bummer for site networks like us with multiple domains. We use the OpenID string as your user “fingerprint”, so if your “fingerprint” changes, we can’t tell who you are any more. It’s a frustrating problem, but we think we’ve finally come up with a fix: we demand email from Google GMail OpenIDs!

If we have an email address from a verified OpenID email provider (that is, an OpenID from a large email service we trust, like Google or Yahoo), then it’s guaranteed to be a globally unique string. We treat this as part of the identifying user token, attached only at login time, that is not editable by the user.

So our cross-site user account matching now works this way:

Match by GUID. This is something we generate and assign during account association, so it’s a perfect fingerprint. Match by OpenID URL. This works for the vast majority of OpenID providers. Match by OpenID provided email address … if you are on our trust whitelist. This works for those rare OpenID providers (currently, only Google GMail) who generate domain-specific identifiers. This satisfies all known OpenID providers, so we can now potentially associate your accounts, across all of our websites, automatically. You’ll still have to log in, of course, but the login itself could trigger account association for every site in the network.

There is one, and only one downside: we must demand email from Google OpenIDs. Email is not usually required to use our sites, but you can’t log in via Google if you refuse to provide email to us. You can always switch OpenID providers, of course, but we regretfully must make the email demand mandatory in the case of Google."

I took a pass at hacking django-allauth to do something like this, but I don't think it's quite secure:

https://github.com/stanhu/django-allauth/commits/master

Do you have any opinions on:

1) Is this a good idea to provide a similar feature? 2) If so, what is the best approach? 3) How to improve upon what I attempted?

pennersr commented 11 years ago

Quoting from above: "This works for those rare OpenID providers (currently, only Google GMail) who generate domain-specific identifiers."

I am really wondering, given that is a Google specific problem, why don't you use Google OAuth2 instead?

stanhu commented 11 years ago

I did get Google OAuth2 working, but I ran into a number of issues:

1) Google OAuth2 requires you to register a fixed callback URL. This is really inconvenient if lots of developers need to run a local server. I could have a development test secret and callback, but if someone wants to run on a different port he has to register a new API client. Google OpenID is a lot easier in this respect, since the callback URL is specified as a parameter.

2) I switched the scope of the Google provider from the suggested 'https://www.googleapis.com/auth/userinfo.profile' to 'https://www.googleapis.com/auth/userinfo.email' so that I could use the AUTO_SIGNUP option. The GoogleOauth2Adapter assumes the return string has the field 'id' (line 37), so this fails. That code needs to be modified to handle the case where 'email' and 'verified_email' are the only values returned to 'extra_data'.

3) I had some trouble initially getting Google OAuth2 working out of the box. I kept getting an ugly error (SocialApp does not exist) until I realized that you have to go into the admin page and manually add the API information into the database. I see that it is mentioned in the 'Architecture & Design' section of README, but I think it would help if this reminder were put into somewhere closer to the provider details.

Thanks for the great work on this module!

stanhu commented 11 years ago

My point 2 isn't quite right. Apparently if you have a SCOPE definition in SOCIALACCOUNT_PROVIDERS, the logic in GoogleProvider.get_default_scope() to add the USERINFO_EMAIL scope isn't actually run. That's because OAuth2Provider.get_scope() gets called, retrieves the SCOPE definition, and never executes GoogleProvider.get_default_scope() if there is one already defined.

You either have to leave out the SCOPE definition or add in both values to make QUERY_EMAIL work, such as:

SOCIALACCOUNT_PROVIDERS = \
    { 'google':
        { 'SCOPE': ['https://www.googleapis.com/auth/userinfo.profile',
                    'https://www.googleapis.com/auth/userinfo.email'] } }

Things now behave as I would hope, except point 1 about the fixed callback is a big drawback to our system.

pennersr commented 11 years ago

At the Google API console you can list multiple callback URLs. Options:

I do understand that from a development point of view OpenID may be easier. How about the end user experience though? The OAuth2 flow is much better looking, even allowing you to put in your own logo et al.

Don't get me wrong, I am not really against the stackoverflow approach, though I am really wondering why one would go through all the trouble. Sure, it may ease development, but IMHO user experience suffers from that.

stanhu commented 11 years ago

Thanks. In principle, I agree with everything you have to say about OAuth2. I switched our application to OAuth2 and loaded the client API/secret automatically with a Django fixture.

The biggest downside I see now is that OAuth2 doesn't allow you to:

-Use unqualified hostnames (e.g. http://test-server:8000) -Use raw IP addresses (e.g. http://192.168.1.10:8000)

This makes it an additional pain for developers. I may continue on the OpenID "trusted providers" track just for that reason.

pennersr commented 11 years ago

It does allow http://localhost:8000/accounts/google/login/callback/ which should be sufficient for development purposes?

stanhu commented 11 years ago

Yeah, I used the localhost trick, but we have developers who want to access their local machines via direct IP address to see how things will look on an tablet or a phone. The fallback is to login directly with the e-mail account or username, but it would be nice not to have to do that.

pennersr commented 11 years ago

Developer experience is indeed suboptimal, but is (IMHO) worth it given that end users benefit from a nicer UX. For now I am closing this issue...