脱・とりあえず動く[DRF JWT編]

# 課題
「業務で使っているけど、よく知らない。けどとりあえず動いている」ということが非常に多いので、調べたこと、試したことをまとめていこうと思います。
今回はDjango REST framework JWT編です。

# そもそもJWTって？
参考
https://tools.ietf.org/html/rfc7519
https://jwt.io

- JSON形式で認証・証明するための仕様
- Header.Payload.Signatureからなる
- Headerにはトークンの情報と暗号化の情報、Payloadにはエンティティ(ユーザー)の情報、Signatureには署名が入っている
- これらをくっつけて暗号化したものを渡す

流れとしては、

- ログイン成功するとサーバーからJWTが返される
- 以降、リクエストのAuthorizationに受け取ったJWTを含めると、制限されている処理が許可される

# DRFでどうやって使うの？
[Django REST Framework JWT](https://getblimp.github.io/django-rest-framework-jwt/)を利用します。

>this module only generates authentication tokens that will verify the user who is requesting one of your DRF protected API resources

(引用元：https://getblimp.github.io/django-rest-framework-jwt/)
「DRFで認証が必要なAPIでユーザー認証するために必要なトークンを発行します」

# 実際に試してみる
参考:http://racchai.hatenablog.com/entry/2016/05/08/070000

インストール
`pip install djangorestframework-jwt`

プロジェクト作成
`django-admin startproject sample`

マイグレーション
`python manage.py migrate`

ユーザー追加
`python manage.py createsuperuser`

settting.pyに追加

```setting.py
REST_FRAMEWORK = { 
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),  
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),  
}
```

設定の内容(参考：http://www.django-rest-framework.org/api-guide/settings/)
DEFAULT_PERRMISSION_CLASSES:認証の設定。IsAuthenticatedは認証されているユーザのみ許可。
DEFAULT_AUTHENTICATION_CLASSES: 認証はどのクラスを使うかの設定。JWTを使う。


url.pyに追加

```url.py
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
    path('obtain_jwt_token/', obtain_jwt_token)
]
```

サーバー起動
`python manage.py runserver`

とりあえずPOSTしてみる。
`curl -X POST http://localhost:8000/obtain_jwt_token/`
怒られた。
`{"username":["This field is required."],"password":["This field is required."]}`

さっき作成したユーザ情報を追加してもう一度POST。
`curl -X POST http://localhost:8000/obtain_jwt_token/ -d "username=<ユーザー名>&password=<パスワード>"`

返ってきた！
`{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2(省略).kNRNxQr-zWCykMPmA(省略)"}`
Header.Payload.Signature形式になっています。


# もっと試してみる
発行→なんらかの操作が認証されることを確認してみます。
参考:http://www.django-rest-framework.org

アプリの追加
`python manage.py startapp sample_app`

setting.pyの追加

```setting.py
# 差分のみ
# DRFを使えるようにする
INSTALLED_APP = [
    'rest_framework'
]

REST_FRAMEWORK = { 
    'DEFAULT_PERMISSION_CLASSES': (
        # 確認のために認証方法を変更する
        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
    ),  
}
```

serializer,viewset,urlsの編集

```sample_app/serializer.py
from django.contrib.auth.models import User
from rest_framework import serializers

class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ('url', 'username', 'email', 'is_staff')
```

```sample_app/viewset.py
from rest_framework import viewsets
from .serializer import UserSerializer
from django.contrib.auth.models import User

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
```

```sample_app/urls.py
from rest_framework import routers
from .views import UserViewSet

router = routers.DefaultRouter()
router.register(r'users', UserViewSet)

urlpatterns = router.urls
```

sample直下のurlsに追加

```sample/urls.py
# 差分のみ
from django.urls import path, include

urlpatterns = [
    path('sample_app/',include('sample_app.urls'))
]
```

これでユーザーへのAPIができました。
確認します。
`http://localhost:8000/sample_app/users/`

`[{"url":"http://localhost:8000/api/users/<ユーザーID>/","username":"<ユーザー名>","email":"<メールアドレス>","is_staff":true}]`
sample_app/users/へGETを投げたので、ユーザーのリストが取得できました。

では、認証の設定を戻してもう一度。

```setting.py
# 差分のみ
REST_FRAMEWORK = { 
    'DEFAULT_PERMISSION_CLASSES': (
        # 元に戻す
        'rest_framework.permissions.IsAuthenticated',
    ),  
}
```

`http://localhost:8000/sample_app/users/`

トークンがないので怒られます。
`{"detail":"Authentication credentials were not provided."}`

では、トークンを発行し、ヘッダーに追加してからもう一度。
`curl http://localhost:8000/obtain_jwt_token/ -X POST -d "username=<ユーザー名>&password=<パスワード>"`

`{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2V(省略).67Gwc-Wsm(省略)"}`

`http://localhost:8000/sample_app/users/ -H "Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2V(省略).67Gwc-Wsm(省略)`

`[{"url":"http://localhost:8000/api/users/<ユーザーID>/","username":"<ユーザー名>","email":"<メールアドレス>","is_staff":true}]`
先ほどと同じデータが取得できました！

# 終わりに
公式ドキュメントや参考ページの手順を追うだけになってしまいましたが、理解が進みました。
アウトプットって大事ですね。
DRF JWTの前にDRF自体もなんとなく触っているだけなので、そっちも調べてみようと思っています。
ありがとうございました。