LoginSignup
2
6

More than 3 years have passed since last update.

REST 化した Django の JWT 認証を dj_rest_auth に丸投げする

Last updated at Posted at 2021-01-04

これまでの経緯

  1. ローカルに Ubuntu Server を用意した
  2. そこに Docker Compose をインストールした
  3. Django+MySQL+nginx の開発環境を Docker Compose で構築した
  4. Django を AWS Fargate で動作させようとした → 失敗した
  5. AbstractBaseUser を継承したカスタムユーザーで E-mail ログインができるようにした
  6. Git で晒すとヤバい変数を django-environ と環境変数の設定で隠蔽した

GitHub のレポジトリはこちら:https://github.com/hajime-f/octave

今回やりたいこと

前々回で、カスタムユーザーで E-mail ログインができるようになったところから始めます。

今回やりたいことは、
- Django REST Framework を導入する
- dj_rest_auth を使って JWT 認証を実現する
の2つ。

「REST ってなんやねん」という人は、こちらの記事を読みましょう。
0からREST APIについて調べてみた

「JWT(ジョット)認証ってなんやねん」という人は、このあたりが参考になります。
JWT認証と流れのやわらかい解説

手順

  1. Django を動かすコンテナにパッケージをインストールする
  2. settings.py を編集する
  3. urls.py でルーティングを設定する
  4. 静的ファイルをベースディレクトリに集める

1. パッケージをインストールする

まずは REST Framework のパッケージと、JWT 認証用のパッケージをインストールします。コンテナにこれらをインストールするために、requirements.txt を編集します。なお、パスは docker-compose.dev.yml があるプロジェクトディレクトリからの相対パスです。

./python/requirements.txt
Django==3.1.4
uwsgi==2.0.18
mysqlclient==1.4.6
django-environ==0.4.5
djangorestframework==3.12.2           # 追加
djangorestframework-simplejwt==4.6.0  # 追加
dj-rest-auth==2.1.2                   # 追加

編集したらビルドしておきましょう。

$ make dev  # docker-compose -f docker-compose.dev.yml build と同じ

Makefile の全内容は、以前の記事を読んでね。

2. settings.py を編集する

公式ドキュメントに書いてあるとおりに、settings.py を編集します。

./src/octave/settings.py
INSTALLED_APPS = [
    ・・・
    'django.contrib.sites',      # 追加

    # 3rd party apps
    'rest_framework',            # 追加
    'rest_framework.authtoken',  # 追加
    'dj_rest_auth',              # 追加

    #My applications
    'users',
]

# 以下すべて追加
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'dj_rest_auth.jwt_auth.JWTCookieAuthentication',
    )
}
SITE_ID = 1
REST_USE_JWT = True
JWT_AUTH_COOKIE = 'user'
AUTH_USER_MODEL = 'users.User'

SIMPLE_JWT = {
    'USER_ID_FIELD': 'uuid',
}

ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_USER_MODEL_USERNAME_FIELD = None

ユーザーを特定する primary key を uuid としたので、そのための設定が必要です。

また、以前の記事のとおり、メールアドレスのみ(ユーザーネームは使わない)で認証できるようにしたいので、そのための設定がいろいろと入ってます。

3. urls.py でルーティングを設定する

これも公式ドキュメントに書いてあるとおりに、urls.py でルーティングを設定します。

./src/octave/urls.py
from django.contrib import admin
from django.urls import path
from django.urls import include  # 追加

urlpatterns = [
    path('admin/', admin.site.urls),
    path('dj-rest-auth/', include('dj_rest_auth.urls')),   # 追加
]

4. 静的ファイルをベースディレクトリに集める

REST Framework は独自の静的ファイルを持っているので、ベースディレクトリにその静的ファイルを集めておきましょう。

$ docker-compose -f docker-compose.dev.yml run python ./manage.py collectstatic

動作確認

ブラウザで http://[開発環境のIPアドレス]:[開発環境のポート番号]/dj-rest-auth/login/ にアクセスし、テストユーザーでログインできることを確認します。

スクリーンショット 2021-01-03 22.51.38.png

こんな感じでアクセストークンが返ってきたら成功です。

端末から curl コマンドを叩いても、結果を確認できます。

$ curl \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"email": "test@test.com", "password": "test123456"}' \
  http://[開発環境のIPアドレス]:[開発環境のポート番号]/dj-rest-auth/login/
{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGci...",
"user":{"pk":"de0e84a7-8396-497e-aa07-e143cc0a2811",
"email":"test@test.com",
"first_name":"テスト",
"last_name":"テスト"}}%

いま苦しんでいるバグ

(追記)このバグは、公式に質問したら解決できました。

本当は、こうすればユーザー登録も丸投げできるはずなんですね。

./python/requirements.txt
Django==3.1.4
uwsgi==2.0.18
mysqlclient==1.4.6
django-environ==0.4.5
djangorestframework==3.12.2
djangorestframework-simplejwt==4.6.0
dj-rest-auth==2.1.2
django-allauth==0.44.0  # さらに追加
./src/octave/settings.py
INSTALLED_APPS = [
    ・・・
    'django.contrib.sites',

    # 3rd party apps
    'rest_framework',
    'rest_framework.authtoken',
    'allauth',                    # さらに追加  
    'allauth.account',            # さらに追加
    'dj_rest_auth',
    'dj_rest_auth.registration',  # さらに追加

    #My applications
    'users',
]

・・・

ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_REQUIRED = True              # さらに追加
ACCOUNT_UNIQUE_EMAIL = True                # さらに追加
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'   # さらに追加
./src/octave/urls.py
urlpatterns = [
    path('admin/', admin.site.urls),
    path('dj-rest-auth/', include('dj_rest_auth.urls')),
    path('dj-rest-auth/registration/', include('dj_rest_auth.registration.urls')),   # さらに追加
]

これは公式ドキュメントに書いてあるとおりなのですが、カスタムユーザーを使っているせいか「EmailAddress matching query does not exist」の例外が出て、さっきまで動いていたログインすらうまく動きません。

スクリーンショット 2021-01-04 18.17.41.png

絶賛悩み中。なんでだ。
以下にエラーログを貼っておくので、誰か分かる人、教えて。

Environment:

Request Method: POST
Request URL: http://127.0.0.1:8081/dj-rest-auth/login/

Django Version: 3.1.4
Python Version: 3.9.1
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.sites',
 'rest_framework',
 'rest_framework.authtoken',
 'allauth',
 'allauth.account',
 'allauth.socialaccount',
 'dj_rest_auth',
 'dj_rest_auth.registration',
 'users',
 'orchestra']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']

Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.9/site-packages/django/core/handlers/base.py", line 179, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.9/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/django/views/decorators/debug.py", line 89, in sensitive_post_parameters_wrapper
    return view(request, *args, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/dj_rest_auth/views.py", line 48, in dispatch
    return super(LoginView, self).dispatch(*args, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/usr/local/lib/python3.9/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/usr/local/lib/python3.9/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/usr/local/lib/python3.9/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/dj_rest_auth/views.py", line 138, in post
    self.serializer.is_valid(raise_exception=True)
  File "/usr/local/lib/python3.9/site-packages/rest_framework/serializers.py", line 220, in is_valid
    self._validated_data = self.run_validation(self.initial_data)
  File "/usr/local/lib/python3.9/site-packages/rest_framework/serializers.py", line 422, in run_validation
    value = self.validate(value)
  File "/usr/local/lib/python3.9/site-packages/dj_rest_auth/serializers.py", line 131, in validate
    self.validate_email_verification_status(user)
  File "/usr/local/lib/python3.9/site-packages/dj_rest_auth/serializers.py", line 112, in validate_email_verification_status
    email_address = user.emailaddress_set.get(email=user.email)
  File "/usr/local/lib/python3.9/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/django/db/models/query.py", line 429, in get
    raise self.model.DoesNotExist(

Exception Type: DoesNotExist at /dj-rest-auth/login/
Exception Value: EmailAddress matching query does not exist.

参考

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