今回達成すること
今回は、Railsにデモプロジェクトを返すコントローラーの作成し、ここまでの変更をHerokuにデプロイします。
これにてログイン認証が完成です。
Railsにデモプロジェクトを用意する
現状のNuxt.jsは、Vuexに用意したプロジェクト一覧を表示させています。
これを、Railsにリクエストを行いAPIを介して取得するよう変更します。
Railsにデモプロジェクトを返すコントローラーを作成します。
root $ docker-compose run --rm api rails g controller Api::V1::Projects
作成したコントローラーに、ユーザーが持っているであろうデモプロジェクトを用意します。
本来は、ユーザーと関連づいたプロジェクト一覧をデータベースから取得します。
api/app/controllers/api/v1/projects_controller.rb
class Api::V1::ProjectsController < ApplicationController
# 以下、追加
before_action :authenticate_user
def index
# 本来はDBから取得する => current_user.projects
projects = [
{ id: 1, name: 'Rails MyProject01', updatedAt: '2020-04-01T12:00:00+09:00' },
{ id: 2, name: 'Rails MyProject02', updatedAt: '2020-04-05T12:00:00+09:00' },
{ id: 3, name: 'Rails MyProject03', updatedAt: '2020-04-03T12:00:00+09:00' },
{ id: 4, name: 'Rails MyProject04', updatedAt: '2020-04-04T12:00:00+09:00' },
{ id: 5, name: 'Rails MyProject05', updatedAt: '2020-04-01T12:00:00+09:00' }
]
render json: projects
end
end
このコントローラーにアクセスするルートを追加します。
api/config/routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
...
# login, logout
resources :user_token, only: [:create] do
delete :destroy, on: :collection
end
# 追加
# projects
resources :projects, only: [:index]
end
end
end
curlコマンドで確認する
Railsのレスポンスが正しく返ってきているか、ターミナルから確認してみましょう。
コンテナを起動してログインし、Cookieからトークンをコピーします。
このトークンをヘッダーに付けてcurl
コマンドを実行します。
root $ curl http://localhost:3000/api/v1/projects \
-H "Authorization: Bearer \
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.\
eyJleHAiOjE2MDUxNjQxOTAsImF1ZCI6ImxvY2FsaG9zdDo4MDgwIiwic3ViIjoyfQ.\
x7aP9wywAvTuZ5acZEswCk-ov0Ihwic77iaf0YJBQGI" | python -mjson.tool
python -mjson.tool
... レスポンスJSONを見やすく整形する。
OK!プロジェクト一覧が返ってきてますね。
[
{
"id": 1,
"name": "Rails MyProject01",
"updatedAt": "2020-04-01T12:00:00+09:00"
},
{
...
これでRails側の準備が整いました。
Nuxt.jsからプロジェクト一覧を取得する
ここからは、Nuxt.jsからRailsにプロジェクト一覧をリクストする処理を行います。
ミドルウェアファイルを作成する
Nuxt.jsでは、ログイン後ぺージに遷移した場合にRailsへリクエストを行い、ユーザーが持つプロジェクト一覧を取得します。
この実装には、ミドルウェアを使えば簡単に実装できます。
「middleware」ディレクトリ内に
root $ touch front/middleware/getProjects.js
ミドルウェアを追加する
作成したミドルウェアを、
- ログイン後のレイアウトファイルである
default.vue と - ログイン後のトップページを表示する
index.vue に追加します。
front/layouts/default.vue
...
<script>
export default {
// 変更
middleware: ['authenticator', 'getProjects']
// 削除
// middleware: 'authenticator'
}
</script>
front/pages/index.vue
...
<script>
import homeImg from '~/assets/images/loggedIn/home.png'
export default {
// 追加
middleware: ['authenticator', 'getProjects'],
layout ({ $auth }) {
...
},
...
}
</script>
これでミドルウェアの読み込み設定ができました。
ミドルウェアからRailsにリクエストする
リクエストを送るタイミングは、
- ログインしている、かつ
- Vuexにプロジェクトが存在しない場合です。
ミドルウェアは、設定したレイアウト内のページ遷移毎に呼ばれるため、無駄なAPIリクエストを行わないように分岐処理をしています。
front/middleware/getProjects.js
export default async ({ $auth, store, $axios }) => {
// ログイン済みかつ、プロジェクト一覧が存在しないとき
if ($auth.loggedIn && !store.state.projects.length) {
await $axios.$get('/api/v1/projects')
.then(response => store.dispatch('getProjects', response))
}
}
Vuexにアクションを追加する
リクエスト成功時に取得したプロジェクト一覧をVuexに保存するために、
front/store/index.js
export const state = () => ({
...
// 空の配列に変更
projects: [],
// 削除
// projects: [
// { id: 1, name: 'MyProject01', updatedAt: '2020-04-01T12:00:00+09:00' },
// { id: 2, name: 'MyProject02', updatedAt: '2020-04-05T12:00:00+09:00' },
// { id: 3, name: 'MyProject03', updatedAt: '2020-04-03T12:00:00+09:00' },
// { id: 4, name: 'MyProject04', updatedAt: '2020-04-04T12:00:00+09:00' },
// { id: 5, name: 'MyProject05', updatedAt: '2020-04-01T12:00:00+09:00' }
// ],
...
})
export const getters = {}
export const mutations = {
...
setToast (state, payload) {
state.toast = payload
},
// 追加
setProjects (state, payload) {
state.projects = payload
}
}
export const actions = {
...
// トーストデータをセットする
getToast ({ commit }, toast) {
toast.color = toast.color || 'error'
toast.timeout = toast.timeout || 4000
commit('setToast', toast)
},
// 追加
// ユーザーのプロジェクト一覧をセットする
getProjects ({ commit }, projects) {
commit('setProjects', projects)
}
}
確認してみよう
ここまでの変更ができたら、ログイン後のトップページを確認してみましょう。
Railsからのプロジェクトが返ってきていたら成功です。
プロジェクトを表示する
これは、ページ描画前にミドルウェアが稼働し、Railsにリクエストを行なっているためです。
ログアウト処理を行う
最後にログアウト時にプロジェクト一覧を空にします。
front/plugins/auth.js
...
class Authentication {
...
// ログアウト業務
logout () {
this.$axios.$delete('/api/v1/user_token')
this.removeStorage()
this.store.dispatch('getCurrentUser', null)
// 追加
this.store.dispatch('getProjects', [])
}
...
}
...
以上でログイン認証の実装が完了です。
RailsとNuxt.jsのリポジトリをデプロイする
Rails、Nuxt.js共にデプロイを行います。
まずは「api」ディレクトリに移動してHerokuにpushします。
root $ cd api
api $ git add -A && git commit -m "add_projects_controller.rb"
api $ git push && git push heroku && cd ../front
「front」ディレクトリ ではブランチを切っているので、マージした後にpushします。
front $ git add -A && git commit -m "add_getProjects_middleware"
front $ git checkout master
front $ git merge 20201013_login_authentication
front $ git push && git push heroku && cd ..
Nuxt.jsデプロイ時の警告メッセージへの対応
Nuxtデプロイ時に下記のようなエラーメッセージが出た場合。
WARN mode option is deprecated. Please use ssr: true for universal mode or ssr: false for spa mode and remove mode from nuxt.config
Nuxt v2.14で変更があり、mode: 'spa'
の書き方が非推奨となりました。
この変更はNuxt v2.14以下でも適用されます。(筆者の場合 v.2.13.3)
対応するには、
front/nuxt.config.js
export default {
// 変更
ssr: false,
// 削除
// mode: 'spa',
...
}
ちなみに、universal
モードを指定する場合は、ssr: true
にします。
ただ、このプロパティはデフォルト値がtrue
なので、追加しなくても自動でuniversal
モードとなります。
変更した場合は、「front」ディレクトリ をもう一度pushしてください。
root $ cd front && git commit -am "edit_nuxt.config.js mode ssr: false"
front $ git push && git push heroku
本番環境を確認してみよう
「front」ディレクトリ以下のでHerokuを開きましょう。
front $ heroku open
ログインしてみると...。
あれ、有効なユーザーなのにログインできない...!!
これは、Cookieが正しく保存できていないことが原因です。
RailsのCookieにSameSite属性を追加する
2020年の2月よりセキュリティ強化のため、Chrome 80においてCookieにSameSite(セイムサイト)属性のデフォルト値が追加されました。
デフォルトはLax
で、この値はGET以外のリクエストにCookieを含めることができません。
クロスサイトでCookieを使用するには、SameSiteの値をNone
にし、かつSecure(セキュア)属性をtrue
にする必要があります。
しかし、(現状の)RailsのコードではSameSite属性の変更ができません。
そこでSameSiteをNone
に設定するGemをインストールします。
api/Gemfile
...
gem 'jwt', '~> 2.2'
# 追加
# CookieのSameSite属性をNoneにする(Chrome 80対応)
# Doc: https://qiita.com/ahera/items/0c8276da6b0bed2b580c
gem 'rails_same_site_cookie'
group :development, :test do
...
apiサービスを再ビルドして、Gemをインストールしましょう。
root $ docker-compose build api
ビルドが完了したら、Gemのインストール確認を行います。
root $ docker-compose run --rm api bundle info rails_same_site_cookie
* rails_same_site_cookie (0.1.8)
...
それでは、「api」ディレクトリ上で再デプロイを行いましょう。
root $ cd api
api $ git add -A && git commit -m "add_gem_rails_same_site_cookie"
api $ git push && git push heroku
完了したら、もう一度アプリを開いてログインを確認してください。
api $ cd ../front && heroku open && cd ..
参考:
Chrome 80が密かに呼び寄せる地獄 ~ SameSite属性のデフォルト変更を調べてみた - Qiita
rails_same_site_cookie gemで、RailsアプリにChrome 80向けのSameSite属性を指定する - Qiita
本番環境のCookieを確認する
本番環境のCookieを確認するには、デベロッパーツールの
- 「Network」タブから
- 左の検索バーよりapiドメインを検索
- そのURLをクリックした状態で
- 「Cookie」タブを選択します
Cookieが
- HttpOnlyにチェック
- Secureにチェック
- SameSiteがNone
になっていることを確認してください。
Cookieの有効期限内であれば、リロードしてもログインが維持できていますね。
最後は「root」ディレクトリをpushして終わりましょう。
root $ git commit -am "finished_login_authentication" && git push
まとめ
今回は、Railsにデモプロジェクトを返すコントローラーを作成し、Nuxt.jsのミドルウェアから取得する設定を行いました。
これにより、本番の挙動に近いアプリケーションができました。
今回はログイン認証を目的にここまでの記事を書いてきたので、当初はデモプロジェクトを返すコントローラーの用意はありませんでした。
しかし、実際のアプリケーションでは、プロジェクトの取得は認証直後の動作になります。
より本番に近づけた方が良いだろうと思い今回の実装を行いました。。。結果!!
Safari上で致命的なバグを見つけることになりました。とほほ。。
これは次の記事で対応します。(より本番環境に近づけるって大切ね。)
このチャプターまとめ
チャプター「フロントエンドのログイン認証」では以下のことを行いました。
- クロスオリジン通信でのCookie共有設定
- Railsからのログイン成功レスポンスを保存
- ローカルストレージの有効期限を暗号化
- JWT有効期限内のユーザーをログインしたままにする
- ログイン前後のリダイレクト処理
- ログイン失敗時のトースターを作成
- エラーページの作成
- デモプロジェクトの作成とHerokuデプロイ(今ここ)
さて次回は?
この記事でログイン認証の実装が終われないのは、Safariのブラウザでログインできない問題を抱えているためです。
次回は、「Safariでログインできない問題」に対応します。
現状のherokuapp.com
のドメインから、SSL化した独自ドメインに変更するため月$7の料金が発生します。
1ヶ月だけなら...という方だけお試しください。