Django allauth social login: automatically linking social site profiles using the registered email

前端 未结 5 1104
鱼传尺愫
鱼传尺愫 2020-12-07 09:25

I aim to create the easiest login experience possible for the users of my Django site. I imagine something like:

  1. Login screen is presented to
相关标签:
5条回答
  • 2020-12-07 09:29

    You will need to override the sociallogin adapter, specifically, the pre_social_login method, which is called after authentication with the social provider, but before this login is processed by allauth.

    In my_adapter.py, do something like this

    from django.contrib.auth.models import User
    
    from allauth.account.models import EmailAccount
    from allauth.exceptions import ImmediateHttpResponse
    from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
    
    
    class MyAdapter(DefaultSocialAccountAdapter):
        def pre_social_login(self, request, sociallogin):
            # This isn't tested, but should work
            try:
                user = User.objects.get(email=sociallogin.email)
                sociallogin.connect(request, user)
                # Create a response object
                raise ImmediateHttpResponse(response)
            except User.DoesNotExist:
                pass
    

    And in your settings, change the social adapter to your adapter

    SOCIALACCOUNT_ADAPTER = 'myapp.my_adapter.MyAdapter`
    

    And you should be able to connect multiple social accounts to one user this way.

    0 讨论(0)
  • 2020-12-07 09:44

    Note (2018-10-23): I'm not using this anymore. Too much magic happening. Instead I enabled SOCIALACCOUNT_EMAIL_REQUIRED and 'facebook': { 'VERIFIED_EMAIL': False, ... }. So allauth will redirect social logins on a social signup form to enter a valid email address. If it's already registered an error shows up to login first and then connect the account. Fair enough for me atm.


    I'm trying to improve this kind of use case and came up with the following solution:

    from allauth.account.models import EmailAddress
    from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
    
    class SocialAccountAdapter(DefaultSocialAccountAdapter):
        def pre_social_login(self, request, sociallogin):
            """
            Invoked just after a user successfully authenticates via a
            social provider, but before the login is actually processed
            (and before the pre_social_login signal is emitted).
    
            We're trying to solve different use cases:
            - social account already exists, just go on
            - social account has no email or email is unknown, just go on
            - social account's email exists, link social account to existing user
            """
    
            # Ignore existing social accounts, just do this stuff for new ones
            if sociallogin.is_existing:
                return
    
            # some social logins don't have an email address, e.g. facebook accounts
            # with mobile numbers only, but allauth takes care of this case so just
            # ignore it
            if 'email' not in sociallogin.account.extra_data:
                return
    
            # check if given email address already exists.
            # Note: __iexact is used to ignore cases
            try:
                email = sociallogin.account.extra_data['email'].lower()
                email_address = EmailAddress.objects.get(email__iexact=email)
    
            # if it does not, let allauth take care of this new social account
            except EmailAddress.DoesNotExist:
                return
    
            # if it does, connect this new social login to the existing user
            user = email_address.user
            sociallogin.connect(request, user)
    

    As far as I can test it, it seems to work well. But inputs and suggestions are very welcome!

    0 讨论(0)
  • 2020-12-07 09:47

    As per babus comment on this related thread, the proposed answers posted before this one (1, 2) introduce a big security hole, documented in allauth docs:

    "It is not clear from the Facebook documentation whether or not the fact that the account is verified implies that the e-mail address is verified as well. For example, verification could also be done by phone or credit card. To be on the safe side, the default is to treat e-mail addresses from Facebook as unverified."

    Saying so, I can signup in facebook with your email ID or change my email to yours in facebook and login to the website to get access to your account.

    So taking this into consideration, and building on @sspross answer, my approach is to redirect the user to the login page, and notify her/him of the duplicate, and inviting him to log in with her/his other account, and link them once they are logged in. I acknowledge that differs from the original question, but in doing so, no security hole is introduced.

    Thus, my adapter looks like:

    from django.contrib.auth.models import User
    from allauth.account.models import EmailAddress
    from allauth.exceptions import ImmediateHttpResponse
    from django.shortcuts import redirect
    from django.contrib import messages
    from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
    
    class MyAdapter(DefaultSocialAccountAdapter):
        def pre_social_login(self, request, sociallogin):
            """
            Invoked just after a user successfully authenticates via a
            social provider, but before the login is actually processed
            (and before the pre_social_login signal is emitted).
    
            We're trying to solve different use cases:
            - social account already exists, just go on
            - social account has no email or email is unknown, just go on
            - social account's email exists, link social account to existing user
            """
    
            # Ignore existing social accounts, just do this stuff for new ones
            if sociallogin.is_existing:
                return
    
            # some social logins don't have an email address, e.g. facebook accounts
            # with mobile numbers only, but allauth takes care of this case so just
            # ignore it
            if 'email' not in sociallogin.account.extra_data:
                return
    
            # check if given email address already exists.
            # Note: __iexact is used to ignore cases
            try:
                email = sociallogin.account.extra_data['email'].lower()
                email_address = EmailAddress.objects.get(email__iexact=email)
    
            # if it does not, let allauth take care of this new social account
            except EmailAddress.DoesNotExist:
                return
    
            # if it does, bounce back to the login page
            account = User.objects.get(email=email).socialaccount_set.first()
            messages.error(request, "A "+account.provider.capitalize()+" account already exists associated to "+email_address.email+". Log in with that instead, and connect your "+sociallogin.account.provider.capitalize()+" account through your profile page to link them together.")       
            raise ImmediateHttpResponse(redirect('/accounts/login'))
    
    0 讨论(0)
  • 2020-12-07 09:49

    If they change how they do the login, how do I automatically associate their Google, FB and local accounts?

    It is possible, but you have to be careful about security issues. Check scenario:

    1. User create account via email and password on your site. User does not have Facebook.
    2. Attacker creates account on Facebook with user email. (Hypothetic scenario, but you do not control if social network verify email).
    3. Attacker login to your site with Facebook and automatically get access to user original account.

    But you can fix it. I describe solution to ticket https://github.com/pennersr/django-allauth/issues/1149

    Happy scenario should be:

    1. User create account via email and password on your site. User logged out.
    2. User forget about his account and try to login via his Facebook.
    3. System authenticate user via Facebook and find out, he already created account via other method (emails are same). System redirect user to normal login page with message "You already create your account using the email and password. Please log in this way. After you log in, you will be able to use and login using Facebook."
    4. User login via email and password.
    5. System automatically connect his Facebook login with his account. Next time user can use Facebook login or email and password.
    0 讨论(0)
  • 2020-12-07 09:52

    I've just found this comment in the source code:

            if account_settings.UNIQUE_EMAIL:
                if email_address_exists(email):
                    # Oops, another user already has this address.  We
                    # cannot simply connect this social account to the
                    # existing user. Reason is that the email adress may
                    # not be verified, meaning, the user may be a hacker
                    # that has added your email address to his account in
                    # the hope that you fall in his trap.  We cannot check
                    # on 'email_address.verified' either, because
                    # 'email_address' is not guaranteed to be verified.
    

    so, it is impossible to do by design.

    0 讨论(0)
提交回复
热议问题