Django(RDF)のGoogleソーシャルログインの覚書

## やりたいこと
バックエンドとフロントエンドを分離して、API経由でソーシャルログインを実現したいのでごちゃごちゃやっていて、色々見えてきたので、自分のメモとして書き置きしておきます。

使用したモジュール
- dj-rest-auth

基本的には、[公式ドキュメント](https://dj-rest-auth.readthedocs.io/en/latest/installation.html)の通りにやっておけば問題ないかと思います。

setting.pyの詳細な設定はここでは省略してます。

## Googleの認証方法2つのやり方？

2023年12月調べたところ、Googleの認証方法には2通りあり
- Authorization Code Grant
- Implicit Grant
があるらしく、これによってViewの書き方が一行変わります。

GPTによると

>OAuth 2.0の認証フローについては、一般的にAuthorization Code Grantが推奨されています。
Implicit Grantフローは、以前はブラウザベースのアプリケーションで使用されていましたが、セキュリティ上の理由から現在は推奨されていません。特に、Implicit Grantフローではアクセストークンがブラウザを通じて直接クライアントに送信されるため、トークンが漏洩するリスクがあります。
一方、Authorization Code Grantフローでは、認証コードがクライアントに送信され、その後サーバーサイドでアクセストークンと交換されます。これにより、アクセストークンが公開されるリスクが軽減されます。

とのことなので、Authorization Code Grantを採用していきたいと思います。

## 実装方針
認証フローを何も知らないと、やってて訳わからなくなると思うので、一旦以下の神記事を読むことをお勧めします。

https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be

神記事の通りに実装する方法を書いていきます。
具体的なコードは下にまとめとくので、適時実装してください。

### 1. ユーザーにリンクを踏ませる

![画像](https://camo.qiitausercontent.com/f9d8c091e1e97358e581a889b41b357a7f7e3558/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3130363034342f36306635346438322d636132642d616131332d353062342d6164393932666163663633302e706e67)
(https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be より拝借)

まずは、ユーザーにリンクを踏ませる必要があるので、以下のリンクにアクセスしてもらいます。

https://accounts.google.com/o/oauth2/v2/auth?redirect_uri=<CALLBACK_URL_YOU_SET_ON_GOOGLE>&prompt=consent&response_type=code&client_id=<YOUR CLIENT ID>&scope=openid%20email%20profile&access_type=offline

ただし、ここで、
- CALLBACK_URL_YOU_SET_ON_GOOGLE
- YOUR CLIENT ID
が必要となるので、[GCP](https://console.cloud.google.com/)にアクセスして、クライアントIDを入手して、コールバックURLを設定してください。ここら辺は、いっぱい記事があるので省略します。

コールバックURLっては、googleでログインした後に飛ばされるページのことです。

飛ばされるページは以下のViewを設定して、そこに飛ばせば良いです。

### View

```
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from dj_rest_auth.registration.views import SocialLoginView


class GoogleLoginView(SocialLoginView):
    adapter_class = GoogleOAuth2Adapter
    client_class = OAuth2Client
    callback_url = "http://127.0.0.1:8000/api/social/login/google/"

```

ちなみに、
callback_url =　''は、Authorization Code Grantの場合必須になります。


### 2. リンクを踏んで遷移する

![画像](https://camo.qiitausercontent.com/49b8e091c737723515880bc70930c4d0e314e1fb/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3130363034342f33313964643465382d373262372d316166322d386265642d3634353132303139366234372e706e67)
(https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be より拝借)

あとは、dj-rest-authがよしなにやってくれます。

さっきのリンクを踏んでみて、ログインしてみましょう。
すると

![スクリーンショット 2023-12-25 7.12.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363430/e76fc011-ea62-df3d-a6e9-d171b7fac79f.png)

のような感じになると思います。

ここで、codeが上にあると思うので、それをコピペしてPOSTすれば終わりです！！！！
っと言いたいところですが、URLエンコードという概念があり、URL経由でコピペされたものはエンコードされてしまします。
なので、残念ですが、このやり方で確認したい場合、モジュールのコードを直いじりする必要があります。

lib/python3.10/site-packages/allauth/socialaccount/providers/oauth2/client.py
```
from urllib.parse import unquote

    def get_access_token(self, code):
        data = {
            "redirect_uri": self.callback_url,
            "grant_type": "authorization_code",
            "code": unquote(code), #unquoteを追加
        }
```
っとモジュールのソースコードを変更してあげれば、無事いつものソーシャルログインのように自動でユーザーが作られ、アクセストークンとリフレッシュトークンが発行されるはずです。


また、DRFのView経由ではなく、curlを使った場合

```
curl -d "client_id=*****.apps.googleusercontent.com" -d "client_secret=*****" -d "redirect_uri=http://127.0.0.1:8000/api/social/login/google/" -d "grant_type=authorization_code" -d "code=*****" https://oauth2.googleapis.com/token
```
でいけると思います。

## 沼ったところ
- ソーシャルログイン自体を理解していなかった
なんか適当にやってたけど、ちゃんとOAuthの概念を理解した方が結果的に早かった
- モジュールのバージョン
dj-rest-authのモジュールを昔のバーションで固定で入れてたので、URLが変わっていることに気が付かなかった
- URLエンコード
以下のエラーに死ぬほど悩まされた。
```
  File "/Users/****/next-drf-blog-auth-book/env/lib/python3.10/site-packages/allauth/socialaccount/providers/oauth2/client.py", line 109, in get_access_token
    raise OAuth2Error("Error retrieving access token: %s" % resp.content)
allauth.socialaccount.providers.oauth2.client.OAuth2Error: Error retrieving access token: b'{\n  "error": "invalid_grant",\n  "error_description": "Malformed auth code."\n}'
```

上記の通り、URLエンコードの概念を知らなかったので、URLのコピペでは認証コードが変換されてるから、そりゃ不正なコードっていいますわな。色々疑ってすまんかったな。ワイが無知やったで。