web-dev-qa-db-ja.com

Django allauthソーシャルログイン:登録済みメールを使用してソーシャルサイトプロファイルを自動的にリンクする

私は、Djangoサイトのユーザーに可能な限り最も簡単なログインエクスペリエンスを作成することを目指しています。次のようなものを想像します:

  1. ログイン画面がユーザーに表示されます
  2. ユーザーはFacebookまたはGoogleでログインすることを選択します
  3. ユーザーが外部サイトにパスワードを入力します
  4. ユーザーは認証済みユーザーとして自分のサイトを操作できます

わかりました、この部分は簡単です-- Django-allauth をインストールして設定するだけです。

しかし、ローカルユーザーでサイトを使用するオプションも提供したいと思います。それには別のステップがあります:

  1. ログイン画面がユーザーに表示されます
  2. ユーザーが登録することを選択
  3. ユーザーが資格情報を入力する
  4. サイトから確認メールが送信されます
  5. ユーザーがメールリンクをクリックして、認証済みユーザーとしてサイトとやり取りできる

はい、デフォルトの認証とallauthの両方で実行できます。しかし、今は百万ドルの問題です。

ログイン方法を変更した場合、どうすれば自動的にGoogle、FB、ローカルアカウントを関連付けることができますか?

彼らがログインするどのような方法でも、私は彼らのメールアドレスを持っていることを確認してください。 Django-allauthを使用してそれを行うことは可能ですか? ユーザー介入 でそれができることを知っています。今日のデフォルトの動作は、メールがすでに登録されているというログインを拒否することです。

構成だけでは不可能な場合は、このワークフローをサポートするためにallauthコードをどのように変更すればよいかについての方向性を示す答えを受け入れます。

これを行う理由はたくさんあります。ユーザーは認証に使用した方法を忘れ、Google、FB、ローカルユーザーアカウントを使用することもあります。すでに多くのローカルユーザーアカウントがあり、ソーシャルアカウントが新しい機能になります。ユーザーに自分のアイデンティティを維持してほしい。私はユーザーの友達リストを尋ねる可能性を想定しているので、もし彼らがグーグルを使ってログインしたなら、私も彼らのFBアカウントを持ちたいと思っています。

これは趣味のサイトであり、大きなセキュリティ要件はないので、これは賢明なセキュリティ実装ではないと答えないでください。

後で、メールをログインIDとして持つ カスタムユーザーモデル を作成します。しかし、私は、必要なユーザー名を持つデフォルトのユーザーモデルのアカウントを自動的に関連付けることができるという回答で満足します。

Django == 1.5.4とDjango-allauth == 0.13.0を使用しています

38
neves

Socialloginアダプター、具体的には pre_social_login メソッドをオーバーライドする必要があります。このメソッドは、ソーシャルプロバイダーによる認証の後、このログインがallauthによって処理される前に呼び出されます。

my_adapter.pyでは、次のようにします

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

そして、設定で、ソーシャルアダプターをアダプターに変更します

SOCIALACCOUNT_ADAPTER = 'myapp.my_adapter.MyAdapter`

この方法で、複数のソーシャルアカウントを1人のユーザーに接続できるはずです。

28
elssar

メモ(2018-10-23):これはもう使用していません。マジックが多すぎます。代わりに、SOCIALACCOUNT_EMAIL_REQUIRED'facebook': { 'VERIFIED_EMAIL': False, ... }を有効にしました。したがって、allauthは、ソーシャルサインアップフォームのソーシャルログインをリダイレクトして、有効なメールアドレスを入力します。すでに登録されている場合は、最初にログインしてからアカウントに接続するとエラーが表示されます。私にとっては十分公正なATMです。


私はこの種のユースケースを改善しようとしていて、次の解決策を考え出しました:

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)

私がテストできる限り、それはうまくいくようです。しかし、入力や提案は大歓迎です!

33
sspross

この関連スレッド に関するbabusコメントに従って、提案された回答はこの前に投稿されました( 12 )は、allauthドキュメントに記載されている大きなセキュリティホールを導入します。

「アカウントが確認されたという事実が電子メールアドレスも確認されていることを意味するかどうかは、Facebookのドキュメントからは明らかではありません。たとえば、確認は電話またはクレジットカードで行うこともできます。一方、デフォルトでは、Facebookからの電子メールアドレスは未確認として扱われます。」

そう言って、あなたのメールIDでFacebookにサインアップするか、Facebookで私のメールをあなたのものに変更し、ウェブサイトにログインしてアカウントにアクセスすることができます。

これを考慮して、@ ssprossの回答に基づいて、私のアプローチは、ユーザーをログインページにリダイレクトし、重複をユーザーに通知し、他のアカウントでログインしてリンクするように招待することですいったんログインすると、元の質問とは異なりますが、そうすることでセキュリティホールは生じません。

したがって、私のアダプターは次のようになります。

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'))
9

私はソースコードでこのコメントを見つけました:

        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.

したがって、設計で行うことは不可能です。

3
neves

ログイン方法を変更した場合、Google、FB、ローカルアカウントを自動的に関連付けるにはどうすればよいですか?

可能ですが、セキュリティの問題に注意する必要があります。チェックシナリオ:

  1. ユーザーはあなたのサイトで電子メールとパスワードを介してアカウントを作成します。ユーザーはFacebookを持っていません。
  2. 攻撃者は、ユーザーのメールを使用してFacebookにアカウントを作成します。 (仮説的なシナリオですが、ソーシャルネットワークがメールを確認するかどうかは制御できません)。
  3. 攻撃者はFacebookを使用してサイトにログインし、ユーザーの元のアカウントに自動的にアクセスします。

しかし、あなたはそれを修正することができます。チケットの解決策について説明します https://github.com/pennersr/Django-allauth/issues/1149

幸せなシナリオは:

  1. ユーザーはあなたのサイトで電子メールとパスワードを介してアカウントを作成します。ユーザーがログアウトしました。
  2. ユーザーは自分のアカウントを忘れて、自分のFacebookからログインを試みます。
  3. システムはFacebook経由でユーザーを認証し、他の方法でアカウントを作成済みであることを確認します(メールは同じです)。システムは、「メールアドレスとパスワードを使用してすでにアカウントを作成しています。この方法でログインしてください。ログインすると、Facebookを使用してログインできるようになります」というメッセージが表示されて、ユーザーを通常のログインページにリダイレクトします。
  4. メールとパスワードによるユーザーログイン。
  5. システムは彼のFacebookログインを彼のアカウントに自動的に接続します。次回ユーザーはFacebookログインまたは電子メールとパスワードを使用できます。
1
quick