27

I am successfully able to login via Google and Facebook using Django (1.6.4) and allauth (0.16.1) and Python (2.7) with expected redirect to settings.LOGIN_REDIRECT_URL in case when there isn't a existing user with emailid retrieved from provider. However, when there already exists an user with same emailid as the one retrieved from provider (fb or goolge), it always redirects to /accounts/social/signup/#= signup page asking:

You are about to use your Facebook/Google account to login to example.com. As a final step, please complete the following form: Email is auto-filled.

I have tested with SOCIALACCOUNT_AUTO_SIGNUP = True or False, but no effect. I tried changing auth_type for facebook, but I don't see any options other than "rerequest"

I have the following settings.py:

ACCOUNT_AUTHENTICATION_METHOD = "email" # Defaults to username_email
ACCOUNT_USERNAME_REQUIRED = False       # Defaults to True
ACCOUNT_EMAIL_REQUIRED = True           # Defaults to False
SOCIALACCOUNT_QUERY_EMAIL = ACCOUNT_EMAIL_REQUIRED
SOCIALACCOUNT_AUTO_SIGNUP = True
SOCIALACCOUNT_EMAIL_REQUIRED = False
ACCOUNT_ADAPTER = "myproject.adapter.MyLoginAccountAdapter"
LOGIN_URL = "/"
LOGIN_REDIRECT_URL = "/users/{id}/mytags"

How can I stop this redirecting to signup, and have provider login redirect to LOGIN_REDIRECT_URL in the particular of an already existing user with same emailid?

NOTE: This I have tried

UPDATES:

  1. Thanks to this answer, I realized that the login via facebook will redirect to signup page in a case: when the email retrieved from facebook profile matches already existing user's emailid.
  2. I have updated the question in order to account for the above case.
  3. To summarize the problem, this is a case where multiple provider accounts have same email id and django-allauth doesn't allow interchangeably login (e.i if i registered once using facebook, django-allauth will require me to use only facebook and not google or anything other provider with same email id)
  4. I have solved it by using @receiver(pre_social_login) and raise ImmediateHttpResponse (look at my answer) with useful links: this and thisone

Thanks, Amit

Community
  • 1
  • 1
amulllb
  • 3,036
  • 7
  • 50
  • 87
  • You want unique email address of the user. ? If yes, then the behavior is something which is require. – vibhor Jun 23 '14 at 04:54
  • I just want the ability to interchangeably login using facebook and google with same email id. I have updated the question. I have found the answer too. Please take a look at it. – amulllb Jun 23 '14 at 05:01

2 Answers2

28

I have solved it after digging in deep into google and source code of django and django-allauth

Problem being solved: I just want the ability to interchangeably login using facebook and google with same email id and always redirect to LOGIN_REDIRECT_URL after successful login, but django-allauth doesn't let me do that. Instead, it presents me with a signup page which I don't want.

Solution:: Use @receiver(pre_social_login) to call a function link_to_local_user() which logs in 1st and then raises ImmediateHttpResponse which in turn redirects to LOGIN_REDIRECT_URL

#! myproject.adapter.py
from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.exceptions import ImmediateHttpResponse
from allauth.socialaccount.signals import pre_social_login
from allauth.account.utils import perform_login
from allauth.utils import get_user_model
from django.http import HttpResponse
from django.dispatch import receiver
from django.shortcuts import redirect
from django.conf import settings
import json


class MyLoginAccountAdapter(DefaultAccountAdapter):
    '''
    Overrides allauth.account.adapter.DefaultAccountAdapter.ajax_response to avoid changing
    the HTTP status_code to 400
    '''

    def get_login_redirect_url(self, request):
        """ 
        """
        if request.user.is_authenticated():
            return settings.LOGIN_REDIRECT_URL.format(
                id=request.user.id)
        else:
            return "/"


class MySocialAccountAdapter(DefaultSocialAccountAdapter):
    '''
    Overrides allauth.socialaccount.adapter.DefaultSocialAccountAdapter.pre_social_login to 
    perform some actions right after successful login
    '''
    def pre_social_login(self, request, sociallogin):
        pass    # TODOFuture: To perform some actions right after successful login

@receiver(pre_social_login)
def link_to_local_user(sender, request, sociallogin, **kwargs):
    ''' Login and redirect
    This is done in order to tackle the situation where user's email retrieved
    from one provider is different from already existing email in the database
    (e.g facebook and google both use same email-id). Specifically, this is done to
    tackle following issues:
    * https://github.com/pennersr/django-allauth/issues/215

    '''
    email_address = sociallogin.account.extra_data['email']
    User = get_user_model()
    users = User.objects.filter(email=email_address)
    if users:
        # allauth.account.app_settings.EmailVerificationMethod
        perform_login(request, users[0], email_verification='optional')
        raise ImmediateHttpResponse(redirect(settings.LOGIN_REDIRECT_URL.format(id=request.user.id)))


#! settings.py
ACCOUNT_AUTHENTICATION_METHOD = "email" # Defaults to username_email
ACCOUNT_USERNAME_REQUIRED = False       # Defaults to True
ACCOUNT_EMAIL_REQUIRED = True           # Defaults to False
SOCIALACCOUNT_QUERY_EMAIL = ACCOUNT_EMAIL_REQUIRED
SOCIALACCOUNT_AUTO_SIGNUP = True
SOCIALACCOUNT_EMAIL_REQUIRED = False
ACCOUNT_ADAPTER = "myproject.adapter.MyLoginAccountAdapter"
SOCIALACCOUNT_ADAPTER = 'myproject.adapter.MySocialAccountAdapter'
LOGIN_URL = "/"
LOGIN_REDIRECT_URL = "/users/{id}/mytags"
amulllb
  • 3,036
  • 7
  • 50
  • 87
  • maybe you want to respect the `next` redirect feature as well: `raise ImmediateHttpResponse(redirect(sociallogin.get_redirect_url(request)))` – sspross Oct 07 '14 at 10:38
  • @abarik - this makes a lot of sense to me, and I've implemented the same approach in my code. However, before logging the user in and raising the ImmediateHttpResponse it seems like you should first check that the user is not already associated with a social_account. If the user is connected then it seems like no action should be taken and they should be allowed to continue on. My suggestion is a simple check for `user.socialaccount_set.count() == 0` before logging the user in. – Andrew Chase Jan 16 '15 at 02:36
  • 2
    It looks like there is still going to be an issue with this approach, that after logging in via the social account, the social account will not get connected to the user account. – Devang Feb 12 '15 at 07:06
  • I use allauth for socialaccounts only and for local accounts i use the default django authentication system. So this solution is not for me. And I also like to keep all the accounts (whether Its social or local) to keep a unique email. Is that right thing to do btw ? – Ahtisham Jan 10 '18 at 16:23
  • 1
    I still don't understand how years later the authors of this library think it's a security issue. If you don't fundamentally trust OAuth providers, why do they create this library? – Evan Zamir Feb 07 '18 at 21:58
  • @sspross in the above solution when I try to implement from the default account adaptor Iam getting a TypeError: 'bool' object is not callable from the line" if request.user.is_authenticated():"? Any idea why this is happening? –  Oct 18 '20 at 07:41
  • 1
    @VishnuChalil in older Django versions `is_authenticated` was a function, therefore you had to call it. In recent Django versions it's a property now and you can remove the calling `()`, just `if request.user.is_authenticated:` – sspross Oct 19 '20 at 08:05
  • Is this solution still up to date? – Vincent Roye Jun 23 '21 at 02:37
  • Thank you somuch, @abarik for your help, I really appreciated. – Darsh Modi Sep 13 '21 at 04:37
0

this was happened with me and the reason was after submitting access_token to Facebook again to get the data determined on scope don't return any email or user name.

  1. you need to make sure that the face_book configuration on settings.py has scope at least as follow 'SCOPE': ['email', 'public_profile'],
  2. got to apps setting in facebook developers make email granted for users
Mohamed Beltagy
  • 593
  • 4
  • 8