20

I have a custom user model and I am using django-allauth for social registration and login. I am trying to connect existing user to new social account when a user login using a social account who already has registered using email. I found this link.

def pre_social_login(self, request, sociallogin):
    user = sociallogin.account.user
    if user.id:
        return
    try:
        customer = Customer.objects.get(email=user.email)
    except Customer.DoesNotExist:
        pass
    else:
        perform_login(request, customer, 'none')

But I'm getting an error when I try to login through social account.

RelatedObjectDoesNotExist at /accounts/facebook/login/callback/
SocialAccount has no user.

Any help will be appreciated.

Also I am aware of the security issue in this. But I still want to try this.

anupsabraham
  • 2,781
  • 2
  • 24
  • 35
  • Did you read the section regarding Custom user models and django-allauth? http://django-allauth.readthedocs.org/en/latest/advanced.html#custom-user-models – petkostas Mar 06 '15 at 15:08

3 Answers3

31

I managed to get this working by changing the code for adapter a little bit.

adapter.py

from allauth.socialaccount.adapter import DefaultSocialAccountAdapter

class MySocialAccountAdapter(DefaultSocialAccountAdapter):
    def pre_social_login(self, request, sociallogin): 
        user = sociallogin.user
        if user.id:  
            return          
        try:
            customer = Customer.objects.get(email=user.email)  # if user exists, connect the account to the existing account and login
            sociallogin.state['process'] = 'connect'                
            perform_login(request, customer, 'none')
        except Customer.DoesNotExist:
            pass

If subclassing DefaultSocialAccountAdapter, we have to specify SOCIALACCOUNT_ADAPTER = 'myapp.my_adapter.MySocialAccountAdapter' in settings.py file

Saurabh Verma
  • 6,328
  • 12
  • 52
  • 84
anupsabraham
  • 2,781
  • 2
  • 24
  • 35
  • Where is the Customer class ?? – Saurabh Verma Jul 13 '15 at 09:06
  • 1
    Customer class is just an example. I created a custom user model with django's AbstractBaseUser. – anupsabraham Jul 13 '15 at 10:56
  • 1
    Okay..Can you also please explain whether you did this change in allauth code or you made a separate class ? – Saurabh Verma Jul 13 '15 at 10:58
  • 4
    That is a signal sent from django allauth. You can check the documentation here. http://django-allauth.readthedocs.org/en/latest/signals.html#allauth-socialaccount I am catching that event in the custom adapter I have written in adapter.py – anupsabraham Jul 13 '15 at 11:05
  • I did what you suggested but its giving AttributeError: 'User' object has no attribute 'backend' Here is the full traceback: http://pastebin.com/v3dMmM5Y – Kishan Mehta May 18 '16 at 09:19
  • 1
    I'm not used to this kind of comments but... hell, man, you're a wizard. That works like a charm. I wonder why it's not in the official docs (the signal docs are not so clear about the topic). – Paolo Stefan Sep 06 '16 at 14:56
  • Lol. Glad my answer helped. :) – anupsabraham Sep 06 '16 at 15:34
  • why do you first check user.id ? When I use your code I have an AttributeError: 'User' object has no attribute 'id' – AmineBTG Jul 13 '23 at 07:40
  • Honestly, the answer is 8 years old. Not sure if this will work now. But, can you check what is the primary key for your "Customer" model? – anupsabraham Jul 13 '23 at 09:09
13

I found the following solution here that also checks that the email addresses are verified.

from allauth.account.models import EmailAddress

def pre_social_login(self, request, sociallogin):

        # social account already exists, so this is just a login
        if sociallogin.is_existing:
            return

        # some social logins don't have an email address
        if not sociallogin.email_addresses:
            return

        # find the first verified email that we get from this sociallogin
        verified_email = None
        for email in sociallogin.email_addresses:
            if email.verified:
                verified_email = email
                break

        # no verified emails found, nothing more to do
        if not verified_email:
            return

        # check if given email address already exists as a verified email on
        # an existing user's account
        try:
            existing_email = EmailAddress.objects.get(email__iexact=email.email, verified=True)
        except EmailAddress.DoesNotExist:
            return

        # if it does, connect this new social login to the existing user
        sociallogin.connect(request, existing_email.user)

if you prefer to skip the verification step, I think this solution is still a bit better:

def pre_social_login(self, request, sociallogin):

    user = sociallogin.user
    if user.id:
        return
    if not user.email:
        return

    try:
        user = User.objects.get(email=user.email)  # if user exists, connect the account to the existing account and login
        sociallogin.connect(request, user)
    except User.DoesNotExist:
        pass
Rani
  • 6,424
  • 1
  • 23
  • 31
5

It's implemented out of the box with the latest allauth. You can use the following in your templates:

{% load socialaccount %}
<a href="{% provider_login_url "spotify" process="connect" %}">connect spotify account</a>

The URL is the following:

/accounts/spotify/login/?process=connect

No internal modifications needed.

felipe
  • 7,324
  • 2
  • 28
  • 37
  • 1
    The connect feature was there already when I posted the question. The problem was that I wasn't able to automatically connect the account upon login. If you are interested to know more about the issue I was facing, please go through https://github.com/pennersr/django-allauth/issues/418. This covers very well the issue and helped me get to my solution. – anupsabraham Sep 29 '20 at 05:24