Django django-rest-auth + Nuxt.js auth-module で作る SPA JWT OAuth ログインシステム その2
2020.06.08
この記事は最終更新日から1年以上が経過しています。
どもです。
前回の「Django django-rest-auth + Nuxt.js auth-module で作る SPA JWT OAuth ログインシステム その1」の続きとなります。
DjangoのNuxt.jsもそうですが、公開されているパッケージを活用しつつ、極力記述の少なくすむように構築していきます。
では、早速 Django django-rest-authで OAuthログインできるようにAPIを作成していきましょう。
バージョン
- django 2.2.13
- python 3.7
使用パッケージ
- djangorestframework 3.10.3
- django-allauth
- django-rest-auth
- djangorestframework-jwt
完成ソースを先に見たい方は、こちらから
GitHub
https://github.com/webcyou-org/simple-rest-login-server
環境作成
まずは、ディレクトリ作成します。
$ mkdir simple-rest-login-server && cd simple-rest-login-server
仮想化環境を開始します。
$ python3 -m venv venv $ source venv/bin/activate
pipenv用いて、必要なパッケージのインストールを行います。
$ pipenv install django==2.2.10 djangorestframework==3.10.3 django-allauth django-rest-auth djangorestframework-jwt
アプリケーションと分別できるように、分かりやすいプロジェクト構成にするため、「config」という名前でstartprojectコマンドでモジュール作成します。
startappでは「accounts」の名前のモジュール作成します。
$ django-admin startproject config . $ python manage.py startapp accounts
config/settings修正
INSTALLED_APPS app追加
「config/settings.py」を修正していきます。
まずは、django-allauthパッケージの追加を行っていきます。
config/settings.py
'django.contrib.sites', 'rest_framework', 'rest_framework.authtoken', 'rest_auth', 'rest_auth.registration', 'allauth', 'allauth.account', 'allauth.socialaccount', 'allauth.socialaccount.providers.google'
「allauth.socialaccount.providers.〇〇〇〇」は、利用したいprovider毎に追加する必要があります。
今回は「Google」のみ追加する形で行っていきます。
TEMPLATESの箇所には、以下を追加
'DIRS': [os.path.join(BASE_DIR, 'templates')],
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
AUTHENTICATION_BACKENDS
AUTHENTICATION_BACKENDSは、
「’django.contrib.auth.backends.ModelBackend’」
「’allauth.account.auth_backends.AuthenticationBackend’」を追加していきます。
管理画面はユーザー名/パスワードで認証することが可能となります。
AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', 'allauth.account.auth_backends.AuthenticationBackend', )
REST_FRAMEWORK
DEFAULT_PERMISSION_CLASSESは「rest_framework.permissions.IsAuthenticated」
DEFAULT_AUTHENTICATION_CLASSESは「rest_framework.authentication.TokenAuthentication」と
「rest_framework_jwt.authentication.JSONWebTokenAuthentication」を指定します。
DEFAULT_AUTHENTICATION_CLASSES の指定は、
「rest_framework_jwt.authentication.JSONWebTokenAuthentication」のみで大丈夫なのかと思っていましたが、
どうもうまく行かず、ハマった結果、「rest_framework.authentication.TokenAuthentication」も指定することによって回避できました。
REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ], 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.TokenAuthentication', 'rest_framework_jwt.authentication.JSONWebTokenAuthentication' ) }
アカウント周りの設定
# 1: 認証⽅式を 「メールアドレスとパスワード」 に変更しユーザー名は使⽤しない設定にします。
# 2: django-allauthに必要記述
# 3: セッションでのログイン無効化、cors origin 有効化
# 4: ユーザー登録時にメールアドレス確認。ユーザー登録画面でにEmailを必須項目
# 5: JWTでの認証ができるようと、トークンの有効期限の設定(開発環境なので取り敢えず期限なし)
# 1 ACCOUNT_AUTHENTICATION_METHOD = 'email' ACCOUNT_USERNAME_REQUIRED = False # 2 SITE_ID = 1 # 3 REST_SESSION_LOGIN = False CORS_ORIGIN_ALLOW_ALL = True # 4 ACCOUNT_EMAIL_VARIFICATION = 'mandatory' ACCOUNT_EMAIL_REQUIRED = True # 5 REST_USE_JWT = True JWT_AUTH = { 'JWT_VERIFY_EXPIRATION': False, }
メールアドレス確認の為、メール配信を行うのですが、設定が手間なのでとりあえず開発中はコンソールに表示させる設定にしておきます。
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
その他
言語とタイムゾーンを日本に設定しておきます。
LANGUAGE_CODE = 'ja' TIME_ZONE = 'Asia/Tokyo'
一旦、すべてのホストを 有効化します。(本番で利用の際は制限してください)
ALLOWED_HOSTS = ['*']
データベーステーブル作成(migrate実行)
$ python manage.py migrate
問題がなければ、テーブルが作成されます。
管理者ユーザー作成
管理画面を利用できる管理者ユーザー (スーパーユーザー) を作成します。
管理コマンドは次の通りです。
$ python manage.py createsuperuser
$ python manage.py createsuperuser Username (leave blank to use 'user1'): admin Email address: admin@webcyou.com Password: # パスワードを入力 Password (again): This password is too common. # パスワードが単純過ぎる場合はエラー発生 Password: Password (again): Superuser created successfully.
ここまで、完了したらソーシャル連携認証を行うため、各サービスでのOAuth アプリケーションを登録していきます。
Googleのソーシャルログイン設定
今回は「Google」でログインを行いますので、サービスにて OAuth アプリケーションを登録していきます。
こちらの登録の解説は前回の「Django django-allauthで、サクッとソーシャルログイン機能を実装」でも説明しておりますので、割愛させていただきます。
登録方法がわからない場合は、こちらを参照していただければと思います。
socialaccount用のviews作成
必要最低限のソーシャルログイン用のviewsを作成します。
accounts/views.py
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter from allauth.socialaccount.providers.oauth2.client import OAuth2Client from rest_auth.registration.views import SocialLoginView from rest_framework.authentication import SessionAuthentication, BasicAuthentication class CsrfExemptSessionAuthentication(SessionAuthentication): def enforce_csrf(self, request): return class GoogleLogin(SocialLoginView): adapter_class = GoogleOAuth2Adapter callback_url = 'http://127.0.0.1/callback' client_class = OAuth2Client authentication_classes = (CsrfExemptSessionAuthentication, BasicAuthentication)
GoogleLogin classを作成します。
adapter_classには、allauth.socialaccount.providers.google.viewsからimportしたGoogleOAuth2Adapterを設定。
callback_urlは任意のURLを設定。
client_classには、allauth.socialaccount.providers.oauth2.clientからimportしたOAuth2Clientを設定。
そのままだと、APIでのリクエストに対して、CSRFトークンがない為エラーになってしまうので、それを回避するためCsrfExemptSessionAuthentication classを作成し、スルーするようにします。
urlルーティング設定
urlsには、以下の項目を追加します。
config/urls.py
from django.conf.urls import include, url from accounts.views import GoogleLogin path('api-auth/', include('rest_framework.urls')), url(r'^rest-auth/', include('rest_auth.urls')), url(r'^rest-auth/registration/', include('rest_auth.registration.urls')), url(r'^rest-auth/google/$', GoogleLogin.as_view(), name='google_login'),
api-auth/は、ログイン後のAPIとして設定しておきます。
rest-auth/は、include(‘rest_auth.urls’)で、django-rest-authのurlを設定しているのですが、registrationは別の指定しないといけないぽい(django-rest-authのdemoをみると)ので指定してます。
また、ソーシャルアカウント毎に指定も必要となってきますので、作成したviewsを呼び出す様に指定します。
APIでのログインテスト
ここまで、修正が完了しましたら一通り実行できるかと思いますので、Postmanなどを使ってAPIのテストを行っていきましょう。
まずは、ユーザー登録から実行していきます。
django-rest-authのAPIドキュメントを見ていきます。
https://django-rest-auth.readthedocs.io/en/latest/api_endpoints.html
ユーザー登録
APIエンドポイント: /rest-auth/registration/(POST)
params:
- username
- password1
- password2
ここでは、usernameを必要とされていますが、settingで必須項目としては外したので「email」「password1」「password2」の項目をリクエストします。
localhost:8000ポートで起動しているので、URLは以下の様になります。
以下の様に送信すると、レスポンスでtokenのvalueでJWTトークンが返却されるのと、user情報が返却されているのが確認できました。
django adminで、(http://localhost:8000/admin)
で、ユーザーを確認すると新規でユーザーが作成されているのが確認できます。
また、コンソールを確認すると、送信するメールの内容が表示されるが確認できます。
メールを確認すると、以下の様にURLが発行されているのが確認できます。
http://localhost:8000/rest-auth/registration/account-confirm-email/{activation-key}/
発行されたactivation-keyを使い、アカウントのアクティベート(emailの確認)を実行します。
アカウントのアクティベート
APIエンドポイント: /rest-auth/registration/verify-email/ (POST)
params:
- key
emailの存在確認ができ、アカウントをアクティベートすることができます。
現状、アクティベートせずにログインできますが、本番運用などの際は、設定を行ったほうが良いでしょう。
ログイン
それでは、登録されたアカウントでログインを試みて見ましょう。
APIエンドポイント: /rest-auth/login/ (POST)
params:
- username
- password
Returns Token key
ここでも、usernameが必要項目となっておりますが、emailとpasswordでログインしたかったので、setting.pyでusernameを外しているので、「email」「password」を送信しログインを行います。
ログアウトに関しては、SPAによるJWT認証の形となりますので、ログイン状態のセッションを保持しない(参照しない)フローとなりますので、クライアント側でのログアウトとなりますので、特別送信する必要がありません。
APIエンドポイント: /rest-auth/logout/ (POST)
ユーザー情報取得
UserInfoのエンドポイントは以下のとおりとなります。
APIエンドポイント: /rest-auth/user/ (GET, PUT, PATCH)
params:
- username
- first_name
- last_name
Returns pk, username, email, first_name, last_name
こちらは、リクエストbodyでのパラメータは必要とされません。
リクエストヘッダーのAuthorizationにJWTトークンを付与して送信するとユーザー情報が返却されます。
django-rest-authでのAuthorizationのValueパラメータは、「JWT JWT-Token」となります。
「JWT」の後に、半角スペース。その後に、ログイン成功後のレスポンスに含まれるtokenの JWTトークンを付与してリクエストする形となります。
ログイン後のJWTトークンをリクエストヘッダーのAuthorizationに付与して送信するとユーザー情報が返却されるのが確認できました。
ログイン後は、このJWTトークンをクライアント側で保持しセキュアなリクエストの際は、リクエストヘッダーに付与しリクエストすることとなります。
JWTトークンの期限が切れた場合、リメンバートークンを送信するのですが、今回は開発環境という事で、省略し期限なしとしております。
その他、パスワードリセットなどがありますが、そちらも今回割愛させていただきます。(またの機会にでも)
ソーシャルログインのテスト
と、最低限メールでのアカウント登録、ログインなどの確認ができましたので、続いてソーシャルアカウントでの登録、ログインを試して行きたいと思います。
ソーシャルアカウントのAPIエンドポイントは以下のとおりとなります。
APIエンドポイント: /rest-auth/google/ (POST)
params:
- access_token
- code
facebookも「/rest-auth/facebook/ (POST)」とエンドポイントだけ異なり、パラメータは「access_token, code」となります。
ここで、前回の「ハイブリッドフロー」を思い出して欲しいのですが、response_typeに「code token」を設定し「ハイブリッドフロー」を行う予定だったと思います。
なので、ソーシャルログインを開始する際に、response_typeに「code token」を設定しレスポンスに、codeとaccess_tokenを受け取ります。
前回紹介させて頂いた「OAuth Social Login Checker」などを利用すれば、容易に取得する事ができます。
GitHub
上記のツールなどを利用し、「code」と「access_token」を取得し、以下の様にソーシャルログイン用のエンドポイントに送信します。
すると、メールでの登録、ログイン同様にレスポンスに「token」と「user」が返却されているのが確認できます。
アカウントが登録されていない場合は、新規作成。既にある場合はログインという形になります。
(レスポンスはどちらも上記の形となる。)
メールでの登録同様にUserInfoのエンドポイントを叩いてみます。
APIエンドポイント: /rest-auth/user/ (GET, PUT, PATCH)
- username
- first_name
- last_name
Returns pk, username, email, first_name, last_name
すると、メールでの登録同様にユーザー情報の取得が行えているのが確認できたかと思います。
という訳で、メールアドレスでの登録、ログイン、ソーシャルログインがREST APIのJWTトークンで行えたのが確認できましたので、次回はフロント側を実装していきたいと思います。
続きは次回に。
ではではぁ。
Django django-rest-auth + Nuxt.js auth-module で作る SPA JWT OAuth ログインシステム その3
GitHub