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オブジェクトを返すようにする
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ログイン処理の前に何かユーザ登録の済/未済を判定するエンドポイントを用意する必要がありそう