Django django-rest-auth + Nuxt.js auth-module で作る SPA JWT OAuth ログインシステム その3
2020.06.14
この記事は最終更新日から1年以上が経過しています。
どもです。
前回の「Django django-rest-auth + Nuxt.js auth-module で作る SPA JWT OAuth ログインシステム その2」の続きとなります。
前回では、Django rest-authを用いて、JWTを用いてOAuth認証を実装いたしましたので、今回はフロント側の実装となります。
フロント側は、Nuxt.jsと認証モジュールにNuxt auth-moduleを利用します。
Nuxt auth-module Document
今回は、SPAでのJWTを用いてOAuth認証の実装に関してなので、Nuxt.jsやauth modeuleの解説などは行わないのでご了承ください。
今回作成したソース
GitHub
https://github.com/webcyou-org/simple-rest-login-front
バージョン
- TypeScript 3.9.5
- Nuxt.js 2.12.2
- @nuxtjs/auth 4.9.1
- @nuxtjs/axios 5.3.6
- bootstrap-vue 2.15.0
画面作成
早速画面を作成していきます。
1から作成もよいのですが、既存のライブラリ等も使いながらなるべく手を動かさず作成していきたいので、@nuxtjs/auth(auth-module)のデモページを流用しつつ、ちょっと手を加える形で作成していきます。
デモページ
https://nuxt-auth.herokuapp.com/login
GitHub
https://github.com/nuxt-community/auth-module/tree/dev/demo
auth-moduleレポジトリの「demo」ディレクトリが該当のソースとなります。
デモページを見ていただくと分かるのですが、以下のように、色々なOAuthログインに対応している形となっております。
ですが、今回はサーバー側でもOAuthログインは、「Google」のみの対応となっておりますので、それ以外の使用しない分は削除していきます。
また、topページも少し改修し、以下の様な表示にしました。
ログインページも簡素なものにします。
ユーザーネームログインとなっていたのを、メールアドレスによる認証と変更したいので、UIもそれに伴い修正しております。
ページ(page)改修の詳細説明は割愛させて頂き、重要な要点を絞った解説をさせて頂きますので、
詳細を知りたい方は、GitHubのソースを参照していただければと思います。
GitHub
https://github.com/webcyou-org/simple-rest-login-front/tree/master/pages
nuxt.config作成
それでは、nuxt.configを作成(改修)していきます。
まず、SPAで作成していきたいので、以下の様にmodeを「spa」に設定します。
nuxt.config.ts
mode: 'spa',
サーバー(Django Rest Framework (localhost:8000で立ち上げている))側にリクエストする際のエンドポイントを「/server」とし、proxyを設定します。実際には「/server」とリクエストされたものは、localhost:8000/に送信される形となります。これを行わないとNuxt.jsのaxios側でCORS(Cross-Origin Resource Sharing)でエラーとなりますので、その回避方法となります。
proxy: { '/server/': { target: 'http://localhost:8000/', pathRewrite: { '^/server/': '' } } },
続いて、@nuxtjs/auth(auth-module)の設定となります。
auth: { cookie: false, redirect: { callback: '/callback', logout: '/signed-out', home: false }, ...
まず、localStorageに保存する形にしたいので「cookie」をfalseとし、リダイレクト設定は「callback: ‘/callback’」「logout: ‘/signed-out’」「home: false」と設定します。
ここで、主に手を加えた点は「home: false」となります。
こちらは、ログイン後の遷移するページの指定となりますが、ここで設定を行ってしまうとOAuth認証フローのcallback時に、自動的に@nuxtjs/auth(auth-module)側でログインした形となってしまうのでそれを回避するためです。
callbackの際に、自動で遷移させず、サーバー(Django Rest Framework (localhost:8000で立ち上げている))側にリクエストし、その結果でログイン、ログイン失敗を判定させるためです。
ハイブリッドフローのポイントの箇所となります。
続いて、@nuxtjs/auth(auth-module)のstrategiesの設定となります。
strategies: { local: { tokenType: 'JWT', endpoints: { login: { url: '/server/rest-auth/login/', method: 'post', propertyName: 'token' }, user: { url: '/server/rest-auth/user/', method: 'get', propertyName: '' } } }, google: { client_id: 'YOUR_GOOGLE_CLIENT_ID', response_type: 'code token', scope: ['email', 'profile'], userinfo_endpoint: '/server/rest-auth/user/' } }
まず、localですが、「メールアドレス」と「パスワード」でログインする時のAPIエンドポイントを設定しております。
ログイン時のエンドポイントと、メソッドとプロパティ名となります。
「propertyName: ‘token’」は、レスポンスのキーが、「token」で返却されますのでそれを受け取る為の指定となります。
endpointsのuserは、ユーザープロフィールを取得するフローの際、リクエストのエンドポイント設定となります。
ログイン後、自動的にこちらのエンドポイントが叩かれるのと、SSR処理(ブラウザリロード)など走った時にも、ログインしている状態であれば自動的にリクエストする形となっております。
また、ここで、注意したい点が「tokenType: ‘JWT’」となります。
先程の「ユーザープロフィール取得」のAPIなど、自動的に取得するAPIはJWTトークンが、リクエストヘッダーのAuthorizationに付与されてリクエストされる形となります。
django-rest-auth側では「’JWT dqwfryth439rt34gninf’」と、「JWT」の文字列が先頭に付与された形で受け取る様になっているので、その形送信する為の指定となります。
最後にGoogleの設定ですが、Google Cloud Platformに登録された「client_id」の指定を行います。
google: { client_id: 'YOUR_GOOGLE_CLIENT_ID', response_type: 'code token', scope: ['email', 'profile'], userinfo_endpoint: '/server/rest-auth/user/' }
response_typeは’code token’,と設定。
これによって、OAuth 2.0 Multiple Response Type の指定を行いハイブリッドフローを開始していることとなります。
scopeは取得したいデータを任意で設定します。
最後に「userinfo_endpoint」ですが、メールアドレス認証(local)同様、「ユーザープロフィール取得」のエンドポイントの指定となります。
メールアドレス認証と、キーは異なりますが上記のメールアドレス認証の際に説明した同様の処理の為の指定となります。
これで、nuxt.configの設定完了。
const config: Configuration = { mode: 'spa', build: { extractCSS: true }, buildModules: [ '@nuxt/typescript-build' ], modules: [ 'bootstrap-vue/nuxt', '@nuxtjs/axios', '@nuxtjs/auth' ], axios: { proxy: true }, proxy: { '/server/': { target: 'http://localhost:8000/', pathRewrite: { '^/server/': '' } } }, auth: { cookie: false, redirect: { callback: '/callback', logout: '/signed-out', home: false }, strategies: { local: { tokenType: 'JWT', endpoints: { login: { url: '/server/rest-auth/login/', method: 'post', propertyName: 'token' }, user: { url: '/server/rest-auth/user/', method: 'get', propertyName: '' } } }, google: { client_id: 'YOUR_GOOGLE_CLIENT_ID', response_type: 'code token', scope: ['email', 'profile'], userinfo_endpoint: '/server/rest-auth/user/' } } } }
callback
続いて「callback」の作成をしていきます。
「callback.vue」は、IDプロバイダの認可エンドポイントにリクエストし、表示されたログインページよりログイン後、「認可コード」と「アクセストークン」が返却され、表示するページとなっております。
、@nuxtjs/auth(auth-module)のデモでは、callback時に自動的にログイン後のページへと遷移しますが、今回、ハイブリッドフローの為「callback.vue」にて、返却された「認可コード」と「アクセストークン」を、サーバー(Django Rest Framework (localhost:8000で立ち上げている))側にリクエストします。
this.$auth.$state.strategyで、現在認証を行っているプロバイダ名が取得できますので、リクエスト成功した際に、レスポンスよりJWTトークンを受けとり@nuxtjs/auth(auth-module)の方に該当するプロバイダのJWTトークンをセットします。
user情報と、user tokenも同様に’JWT トークン’の形でセットします。
成功した際は、ログイン後表示する「secure」ページに遷移。
失敗の際は、一旦ログアウト処理を行い、ルートページに遷移させます。
created() { const provider = this.$auth.$state.strategy const callbackParams = queryString.parse(this.$auth.ctx.from.hash) this.$axios.post(`/server/rest-auth/${provider}/`, { access_token: callbackParams.access_token, code: callbackParams.code }) .then((response) => { const data = response.data this.$auth.setToken(provider, data.token) this.$auth.setUser(data.user) this.$auth.setUserToken(`JWT ${data.token}`) this.$router.push('/secure') }) .catch(() => { this.$auth.logout() this.$router.push('/') }) }
ログインテスト
それでは、一通り揃ったので、前回作成したユーザーでログインを行って行きたいと思います。
まずは、メールアドレスでの認証を試してみます。
ログイン成功し、認証済みのユーザーのみ表示するページの「secure」ページに遷移し、ログインしたユーザー情報が表示しているのが確認できます。
OAuth(google)でのログインも試してみます。
こちらは、リアルなアカウントを利用しているので画像は省略させていただきますが、メールアドレス認証と同様で問題なくログインでき「secure」ページに遷移し、ログインしたユーザー情報が表示しているのが確認できました。
ユーザー登録がされていない場合は、新規作成され、既にアカウントが存在する場合はログインとなります。
登録ページ作成
メールアドレスでのログイン、OAuth(Google)でのログインがそれぞれ確認できましたので、最後はユーザー登録できるように作成していきます。
ログインページの「login.vue」をコピーし、「registration.vue」を作成、以下の様に「Email」「Password」「PasswordConfirm」が入力できるように改修しました。
GitHub
https://github.com/webcyou-org/simple-rest-login-front/blob/master/pages/registration.vue
ユーザー登録のAPIエンドポイントは「/server/rest-auth/registration」となります。
こちらに、入力された「email」「password1」「password2」をパラメータとして送信します。
すべて必須の値となっており、漏れがあるとDjango側でエラーとなります。
this.$axios.post(`/server/rest-auth/registration/`, { email: this.email, password1: this.password, password2: this.passwordConfirm }) .then(() => { this.$router.push('/login') }) .catch((e) => { this.error = e.response.data })
ヘッダーにも登録ページへの遷移を追加しました。
それでは、「fuga@fuga.com」のメールアドレスでアカウント作成
Django側のコンソールに登録後送信されるメールの内容が表示します。
アカウントアクティベート用のURLが表示し、アクセスするとアカウントがアクティベートされますが、今回特に制限入れていないので、アカウントアクティベートされていなくてもログインは行なえます。
ログインページで先程作成したアカウントでログインしてみます。
無事、ログイン成功し、認証済みのユーザーのみ表示するページの「secure」ページに遷移し、ログインしたユーザー情報が表示しているのが確認できます。
という訳で、簡易ではありますが、ユーザー登録、ログインが行えるようになりました。
アカウントアクティベートもそうですが、比較的簡易に作成したのですが、一通り作成完了しました。
最後に
その他にもパスワード変更やアクティベート処理などなどありますが、今回は割愛させて頂いたのと、エラー処理やJWTトークン期限設定、認証フローなど簡易にしてきたのもありますので、もし本番などで利用する際はその辺りの考慮や作り込みを行って頂ければと思います。
という訳で、3回に渡って書いてきた、「Django django-rest-auth + Nuxt.js auth-module で作る SPA JWT OAuth ログインシステム」でした。
何か、参考になればと。
ではではぁ。
今回作成したソース
GitHub