Django Rest FrameworkでOAuth認証をする

# 本記事のゴール
* OAuthの認可サーバーに対してリクエストをしてアクセストークンが返ってくること
* OAuthのリクエスト&レスポンスをDjango Rest Swaggerのdocsに表示させること

# 環境
python==3.7.2
Django==2.1.7
django-rest-framework==3.9.3
django-rest-swagger==2.2.0
django-oauth-toolkit==1.2.0

OAuth認証には
django-oauth-toolkitを使用します
[パッケージによるDRF認証](https://www.django-rest-framework.org/api-guide/authentication/#third-party-packages)

# 解説
## 基本的な設定
基本的には[公式](https://django-oauth-toolkit.readthedocs.io/en/latest/index.html)に習ってます
細かいところは独自に実装したので、その辺も踏まえて解説します

公式の[1分でプロバイダを作る](https://django-oauth-toolkit.readthedocs.io/en/latest/tutorial/tutorial.html)という項目があるように、このページを見れば簡単に実装できます
上記の項目を見れば誰でもできるので、この辺りは駆け足で解説します

```settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'oauth2_provider',
    'corsheaders',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
    )
}

OAUTH2_PROVIDER = {
    # 発行するアクセストークンにアクセス制御をつけるオプション
    'SCOPES': {
        'read': 'Read scope',
        'write': 'Write scope',
        'groups': 'Access to your groups'
    },

    # アクセストークンの有効期限秒数 2592000 -> １ヶ月
    'ACCESS_TOKEN_EXPIRE_SECONDS': 2592000

```

プロジェクトルートのurls.pyを以下のように設定します

```urls.py
urlpatterns = [
    path('oauth/', include('oauth2_provider.urls', namespace='oauth2_provider')),

]
```
## クライアントID・クライアントシークレットの生成
完了後にmigrateするとテーブルが４つ追加されます
これらはクライアントID・アクセストーンなどの情報を保存するためのテーブルです。

その後、スーパーユーザーで以下のURLへアクセスしてください
`http://127.0.0.1:8000/oauth/applications/`
こんな画面が出ると思います

<img width="1163" alt="スクリーンショット 2019-05-11 11.32.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186116/2542d543-d285-2113-e61b-81cc7e079d19.png">

`Click here`を押してアプリケーション登録をしましょう。

<img width="803" alt="スクリーンショット 2019-05-11 11.38.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186116/ca75008b-da9d-2a0b-72f6-fd9f50c62ee5.png">

`name` -> 何でもいいです
`Client id` -> 自動で生成されます
`Client secret` -> 自動で生成されます
`Client type` -> confidential
`Authorization grant type` -> Recource ~~ passwordを選択
`Redirect urls` -> 空欄で大丈夫です

`Save`を押すと情報がoauth_provider.applicationテーブルに保存されます
これでアプリケーション登録は完了です。

## アクセストークン発行
次は生成したクライアント情報を使用してアクセストークンを発行しましょう。
http通信ができれば何でもいいのでコマンドでもいいのですが、個人的にはPostmanがオススメです

こうします（投げやり）
<img width="980" alt="スクリーンショット 2019-05-11 11.54.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186116/0af3633a-f2f2-9bc8-862c-e388fe1dc3bb.png">

レスポンスにアクセストークンが返ってきています
有効期限（expires_in）も`settings.py`で指定した値となっていますね
この時のレスポンス情報はoauth_prodider.accesstokenテーブルに保存されますので、ご確認ください

以上で基本的な説明は終わりですが、ここからはこのリクエスト&レスポンスをDjango Rest Swaggerに表示させるところまでやります
そこも知りたい！という方はもう少しお付き合いください。

## Oauth認証のリクエスト&レスポンスをdocsへ表示
dJango-oauth-toolkitのパッケージにはDRF用に作成されたシリアライザーなどがないので、独自に実装する必要があります
djangorest-jwtは事前に用意されているので、docsに表示させるのも簡単にできるのですが・・・。
多少強引な実装となっておりますが、それでも良いって方はぜひ。

まずはシリアライザーがないとどうしようもないので、作りましょう

```serializers.py
class OAuthPostTokenSerializer(serializers.Serializer):
    grant_type = serializers.CharField(required=True)
    username = serializers.CharField(required=True)
    password = serializers.CharField(required=True)
    client_id = serializers.CharField(required=True)
    client_secret = serializers.CharField(required=True)
```
次にシリアライザーを使ってviewを作ります

```views.py
from rest_framework.generics import CreateAPIView
from .serializers import OAuthPostTokenSerializer

class OAuthPostToken(CreateAPIView):
    """ユーザー認証をしてアクセストークンを返す"""
    serializer_class = OAuthPostTokenSerializer

    def post(self, request, *args, **kwargs):
        protocol = self.request.scheme
        host = request.get_host()

        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            r = requests.post(protocol + '://' + host + '/oauth/token/',
                              data={
                                  'grant_type': 'password',
                                  'username': OAUTH_USERNAME,
                                  'password': OAUTH_PASSWORD,
                                  'client_id': OAUTH_CLIENT_ID,
                                  'client_secret': OAUTH_CLIENT_SECRET,
                              },
                              )
            return Response(r.json(), status=status.HTTP_200_OK)
        else:
            return Response(serializer.errors)

```

一旦docsに持ってきてOauth認可サーバーに渡すという男らしい実装です笑
`grant_type`, `client_id`, `client_secret`は固定なので、環境変数として始めから値を持ってしまっても良いでしょう
最後にurls.pyに実装したviewを追加しましょう

```urls.py
API_ROOT 　= 'api/test/'
urlpatterns = [
    path(API_ROOT + 'oauth/token/', OAuthPostToken.as_view()), # <- 追加
    path('oauth/', include('oauth2_provider.urls', namespace='oauth2_provider')),

]
```

するとdocsにOauthが追加されています

<img width="1195" alt="スクリーンショット 2019-05-11 12.23.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186116/b9eae611-2bcf-6075-790b-2b4a042c57b5.png">

レスポンスも問題なく返ってきていますね
<img width="1162" alt="スクリーンショット 2019-05-11 12.33.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186116/5fe7f35c-be8f-e638-704e-e2942667c251.png">

## tips1 レスポンスににユーザー情報も一緒に返す
レスポンスとしてアクセストークンが返ってきますが、そこにユーザー情報も付与したいという方向け
アクセストークンレスポンスは`TokenView`というviewを使用しているのでそれを継承して自分でカスタマイズしましょう
以下は例です

```views.py
class OAuthResponseTokenView(TokenView):
    """OAuthのトークンレスポンスにユーザー情報を追加して返す処理"""
    @method_decorator(sensitive_post_parameters("password"))
    def post(self, request, *args, **kwargs):
        url, headers, body, status = self.create_token_response(request)
        if status == 200:
            body = json.loads(body)
            access_token = body.get("access_token")
            if access_token is not None:
                token = get_access_token_model().objects.get(
                    token=access_token)
                app_authorized.send(
                    sender=self, request=request,
                    token=token)
                body['user_info'] = {
                    'id': token.user.id,
                    'username': token.user.username,
                    'email': token.user.email
                }
                body = json.dumps(body)
        response = HttpResponse(content=body, status=status)
        for k, v in headers.items():
            response[k] = v
        return response
```

```urls.py
    path(API_ROOT + 'oauth/token/', users_views.OAuthPostToken.as_view()),
    path('oauth/token/', users_views.OAuthResponseTokenView.as_view()), # <-追加
    path('oauth/', include('oauth2_provider.urls', namespace='oauth2_provider')),
```

レスポンスにユーザー情報が追加されて返ってきます
<img width="1164" alt="スクリーンショット 2019-05-11 12.54.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186116/1d9353a3-bdb0-7c49-c447-660c095f8f61.png">

# 終わりに
多少荒い説明となってしまいましたが、いかがでしょうか

上記の方法でリフレッシュトークンによるアクセストークン発行も可能です

リソースサーバーに対してアクセストークンを使用した認証方法は時間があったら記事にしようと思います