0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

django-allauth で、認証済みのユーザーに別の認証を追加(統合)する方法

Posted at

 検索しても「認証機能自体を『追加』する」記事しかヒットしなかったり、ChatGPT 4 君に聞いても上手く動かなかったりして、けっこう試行錯誤したので、誰かの助けになるかなと思って記事を書きます。

背景

 イーロンが Twitter の方針をアレコレ変えるせいで、半永続的なインフラと思われていた Twitter の OAuth 認証がマジで使えなくなる危険が出てきました。万が一の事態に備えて、別の認証手段を追加し、既存ユーザーにそれを統合させる手段を用意しておいた方が安全です。

 本記事では Discord 認証を想定しています。

Discord 認証を追加

 この記事が必要になる方は、既に他のサービスでの認証は実装していると思われるので、細かい説明は省きますが、Discord でも同様にプロバイダーの追加や、コールバック URL の設定などをしていきます。

 この時点で認証はできますが、認証済のユーザーが Discord ログインを行った場合、新規ユーザーとして登録されてしまう、という問題が起きます。

 理想は、

・認証済み→統合される
・認証済みでない→新規ユーザーとして登録

 なので、これはちょっと期待した動きではありません。

ChatGPT 君に聞こう

 こういう時は ChatGPT に聞くに限ります。
image.png
 ふむふむ、アダプターというのを設定すればいいのね。

 settings.py に以下を追記した上で、

settings.py
AUTHENTICATION_BACKENDS = (
   ...
   'myapp.adapters.CustomAccountAdapter',
   'myapp.adapters.CustomSocialAccountAdapter',
   ...
)

 adapters.py を作って以下をコピペ。

adapters.py
from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter

class CustomAccountAdapter(DefaultAccountAdapter):
    pass

class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
    def is_auto_signup_allowed(self, request, sociallogin):
        return False

    def save_user(self, request, sociallogin, form=None):
        if request.user.is_authenticated:
            sociallogin.connect(request, request.user)
            return request.user

        return super().save_user(request, sociallogin, form)

 結果:解決せず

 おいおい、AI さんよお……。ちゃんと仕事してくれなきゃ困るぜ。

解決手段

 コードをよくみると、ChatGPT 君は save_user の段階で認証済みかどうかをチェックしているのですが、これは認証済みであっても匿名ユーザー扱いになってしまうようです。いろいろ調べた結果(海外のどっかの掲示板だっけ?)、pre_social_login というメソッドをオーバーロードすると、既存ユーザーの認証具合を調べられるようです。

 結果的に、以下のようにコードを改造するとうまくいきました。

adapters.py
from django.http import HttpResponseRedirect
from django.urls import reverse
from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter

class CustomAccountAdapter(DefaultAccountAdapter):
    pass

class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):

    def pre_social_login(self, request, sociallogin): 
        # print(request.user)
        if request.user.is_authenticated:
            sociallogin.connect(request, request.user)
        return

    def is_auto_signup_allowed(self, request, sociallogin):
        return True

    def save_user(self, request, sociallogin, form=None):
        if request.user.is_authenticated:
            sociallogin.connect(request, request.user)
            return request.user

        return super().save_user(request, sociallogin, form)

 さらっと、is_auto_signup_allowedTrue を返すようにしていますが、これにしないとメールアドレスを記入する画面とかが挟まれるようになります。後半の save_userif 文は無意味になってる気がしますが、まあとりあえずこれで動いている以上、余計な改変や解説はせずにそのまま載せています。詳しい人から見たら無駄な実装や間違いがあるかもしれませんが、とりあえず解決事例を報告した方がいいと思ったので。

注意事項

 これをやる場合、既に連携しているソーシャルプロバイダを更に追加しようとするとアサーションエラーが起こります。例えば、Discord に認証していない時のみ、ブラウザ上で「Discord 認証」ボタンを押せるようにするとかすれば、普通のユーザーがトラブルになることはないと思います。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?