1
2

More than 1 year has passed since last update.

【Python Social Auth】PythonSocialAuth - Pipelineの使い方がいまいちわからなかったので真剣にソース確認してみた【Django】

Last updated at Posted at 2022-02-11

Social Core の user.py, social_auth.py の仕様について考察

django REST framework × React で、Pyhton Social Authを用いてSocial Login機能を作成したが、
ログイン機能と新規登録機能を分けるためには
デフォルトで有効になっているsocial_core.pipeline.user.create_userを無効化しなければらならい
(※有効のままだと、ログインボタンでも新規登録できてしまう)

ただし、無効化すると認証が行えなくなり、
convert-token 実行できない

なので、user.py, social_auth.py の仕様を理解して、
カスタムユーザでも利用できるようにしたい

pipelineの確認

まず、pipelineの仕様について

↓はDjangoのsettings.pyに追記することでオーバーライドできるpipelineのデフォルトのリスト

SOCIAL_AUTH_PIPELINE = (
    'social_core.pipeline.social_auth.social_details',
    'social_core.pipeline.social_auth.social_uid',
    'social_core.pipeline.social_auth.auth_allowed',
    'social_core.pipeline.social_auth.social_user',
    'social_core.pipeline.user.get_username',
    'social_core.pipeline.user.create_user',
    'social_core.pipeline.social_auth.associate_user',
    'social_core.pipeline.social_auth.load_extra_data',
    'social_core.pipeline.user.user_details',
)

各関数の詳細はこちらにまとめた↓
python social auth pipeline メソッド内容一覧

create_userをコメントアウトしたときの挙動

ドキュメントによると、ユーザ登録を行わず、ログインのみのpipelineにするには
get_username と create_user をコメントアウトすればいいという記載がある

これをコメントアウトした状態でログイン(DRFのconvert-token)しようとすると、
↓のエラーが出て失敗する

  File "/home/coke/google-oauth-sample/tutorial/lib64/python3.6/site-packages/drf_social_oauth2/oauth2_grants.py", line 113, in validate_token_request
    'Invalid credentials given.', request=request
oauthlib.oauth2.rfc6749.errors.InvalidGrantError: (invalid_grant) Invalid credentials given. <oauthlib.Request SANITIZED>

エラーで出てきたdrf_social_oauth2/oauth2_grants.pyの当該箇所を見てみると

if not user:
    raise errors.InvalidGrantError(
        'Invalid credentials given.', request=request
    )

validate_token_request(self, request)関数で、
userオブジェクトが存在しない場合、このエラーが出ていることが分かった

つまり、create_userをコメントアウトしたことで、userオブジェクトが引数からなくなって、
関数が実行できない状態となっている

確かに、user.py の create_user関数を見ても、
返り値にuserが設定されていることが分かる

def create_user(strategy, details, backend, user=None, *args, **kwargs):
    if user:
        return {'is_new': False}

    fields = {name: kwargs.get(name, details.get(name))
                  for name in backend.setting('USER_FIELDS', USER_FIELDS)}
    if not fields:
        return

    return {
        'is_new': True,
        'user': strategy.create_user(**fields)
    }

ここまでのまとめ

  • create_user がある場合 : 返り値にuserが含まれているため、validate_token_requestが正常に動作する
  • create_user がない場合 : 返り値にuserが含まれなくなるので、validate_token_requestがエラーを出力する

pipeline上で、create_user以外でuserを返す関数がないか確認する

すべてのメソッドを確認したが、返り値にuserを返すものはほかにはなかった
各メソッドの内容は↓にまとめた

  • social_user
  • associate_user
  • associate_by_email(デフォルトではオフ)

これらは返り値にuserを返す部分があったが、
if user:で始まる条件の時のみであったので、無効

これに関してはドキュメントに記載があった よくわからなかったから無視してた

Note that this assumes the user is already authenticated, and thus the user key in the dict is populated. In cases where the authentication is purely external, a pipeline method must be provided that populates the user key. Example:

しかしながらこの設定は、ユーザがすでに認証されていることを想定していることに注意
つまり、dict内にuserキーがすでに追加されている状態である
認証機能を完全に外部にゆだねる場合、userキーを追加するメソッドをパイプラインに含めるようにしなければならない
例えば、以下のようにuserをdictに追加するようなオリジナルのメソッドを追加する

考察結果

上記の考察から、分かったこと

  • デフォルトのcreate_userメソッドをpipelineから外す場合、pipelineメソッドの引数である**kwargs(dict型の変数)内に、userオブジェクトを必ず含める必要がある

  • pipelineでcreate_userをコメントアウトした場合

    • ほかのどの関数もuserを返すことはないので、drf_social_oauth2のvalidate_token_request関数で必ずエラーを起こす
    • social側でuserが作成されてないので、無理やりdjangoでuserを作成しても情報が一致せず、pipelineがうまく動かない
    • なぜかuser_detailsが無いといわれる
  • create_userをコメントアウトしなかった場合

    • userオブジェクトが引数に追加されるため、初回実行以降は認証はうまくいくようになる

回避策

方法1. オリジナルのcreate_userを作成してpipelineに組み込む

socialのstorageにユーザ情報が保存されていないためにuserオブジェクトが返されないことがすべての原因なので、
create_userをオリジナルで作る

この場合、結局create_userを作るので、ログインの時と新規作成の場合で処理を分けるようなメソッドにする必要がある
→不可能?

方法2. userオブジェクトを返すオリジナルメソッドをpipelineに組み込む

ユーザの作成はDjangoのviewで行い、ログイン処理はDjangoのDBを参照して、userオブジェクトを返すようにする

users/pipeline.py
from .models import CustomUser

def login_user(response, user=None, *args, **kwargs):

    if not user:
        email = response['email']
        username = response['name']
        login_user = CustomUser.objects.get(username=username, email=email)
        return {
            'is_new': False,
            'user': login_user
        }

この方法で成功!

usersというapp内にpipeline.pyというファイルを作成

login_user = CustomUser(ユーザモデル).objects.get(username=username, email=email)
として、return に {'user': login_user} で渡してあげると、うまく動くようになった

残りの課題

未登録の状態でログインボタンを押したときに「そのユーザは登録されていません」のエラーを出すには、
やはり自前でGoogleログイン処理の前に何かユーザ登録の済/未済を判定するエンドポイントを用意する必要がありそう

1
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
1
2