Udemy 12. サーバーサイドのログイン認証 #08
2020年10月01日に公開

【Rails×JWT】ログインコントローラーのテストとHerokuデプロイ

今回達成すること

前回作成したuser_token_controller.rbのテストを行います。

そして、本チャプターでの変更をHerokuにデプロイし、本番環境でもトークンのユーザーが正しく返されるかを確認します。

テスト環境のCookieの保存先

実装に入る前に、テスト環境ではCookieの保存先が違うためその説明をします。

(ハマった…)

Railsのテスト環境でCookieが保存されるクラスは、Rack::Test::CookieJar(ラック・テスト・クッキージャー)になります。

# テスト環境
> cookies
=> #<Rack::Test::CookieJar:0x0000559ef7bd91d8 
 @cookies=[], 
 @default_host="www.example.com">

コード: Class: Rack::Test::CookieJar — Documentation for brynary …

対して、開発環境、本番環境ではActionDispatch::Cookies::CookieJar(アクションディスパッチ・クッキーズ・クッキージャー)クラスに保存されます。

# 開発環境、本番環境
> ActionDispatch::Cookies::CookieJar.new([])
=> #<ActionDispatch::Cookies::CookieJar:0x00005640811815b0
 @committed=false,
 @cookies={},
 @delete_cookies={},
 @request=[],
 @set_cookies={}>

コード: Class: ActionDispatch::Cookies::CookieJar - RubyDoc.info

開発環境のCookieに保存されたか?を検証するには

user_token_controller.rbcreateアクションを介して保存されたクッキーは、後者のActionDispatchクラスに保存されます。

よって、ActionDispatchクラスのCookieに保存されたかを検証する必要があります。

ActionDispatchのCookieを参照するには、@request.cookie_jar[<キー>]メソッドを使用します。

参考: https://www.rubydoc.info/docs/rails/4.1.7/ActionDispatch/Request:cookie_jar

以上を理解した上で、ログインコントローラーをテストしていきましょう。

createアクションのCookieをテストする

まずは、user_token_controller_test.rbに、ログインコントローラーを介したログイン状態を作ります。

api/test/controllers/api/v1/user_token_controller_test.rb(以下、省略)
require 'test_helper'

class Api::V1::UserTokenControllerTest < ActionDispatch::IntegrationTest

  # 追加
  def user_token_logged_in(user)
    params = { auth: { email: user.email, password: "password" } }
    post api_url("/user_token"), params: params
    assert_response 200
  end

  def setup
    @user = active_user
    @key = UserAuth.token_access_key
    user_token_logged_in(@user)
  end
  # ここまで
end

Cookieが保存されているかテストする

ActionDispatchクラスにCookieが保存されているかをテストします。

  ...
  def setup
    @user = active_user
    @key = UserAuth.token_access_key
    user_token_logged_in(@user)
  end

  # 追加
  test "create_action" do
    # アクセストークンはCookieに保存されているか
    cookie_token = @request.cookie_jar[@key]
    assert cookie_token.present?
  end
end

保存されたCookieのオプションを取得する

Cookieのオプションは、ActionDispatchのインスタンス変数である@set_cookiesに保存されています。

@set_cookiesの中身
@set_cookies=
{"access_token"=>
  {:value=> "eyJ0eXAiOiJKV1QiLCJhbGciOiJ...",
   :expires=>2020-10-14 17:22:41 +0900,
   :secure=>false,
   :http_only=>true,
   :path=>"/"}}

このインスタンス変数を取得するには、Rubyのinstance_variable_get(インスタンス バリアブル ゲット)メソッドを使用します。

 ...
  test "create_action" do
    # アクセストークンはCookieに保存されているか
    cookie_token = @request.cookie_jar[@key]
    assert cookie_token.present?

    # 追加
    ## Cookieオプションの取得
    cookie_options = @request.cookie_jar.instance_variable_get(:@set_cookies)[@key.to_s]
  end
end

参考: Ruby 2.7.0 リファレンスマニュアル

Cookieのオプションをテストする

Cookieのオプションをテストします。

テスト項目は、

  • expires(エクスパイアーズ)の値は正しいか
  • secure(セキュア)は開発環境でfalse
  • http_onlytrueであるか

の3つです。

  ...
  test "create_action" do
    ...

    ## Cookieオプションの取得
    cookie_options = @request.cookie_jar.instance_variable_get(:@set_cookies)[@key.to_s]

    # 以下、追加
    # expiresは一致しているか
    exp = UserAuth::AuthToken.new(token: cookie_token).payload["exp"]
    assert_equal(Time.at(exp), cookie_options[:expires])

    # secureは開発環境でfalseか
    assert_equal(Rails.env.production?, cookie_options[:secure])

    # http_onlyはtrueか
    assert cookie_options[:http_only]
    # ここまで
  end
end

それではテストを実行してみましょう。

root $ docker-compose run --rm api rails t controllers

Finished in 2.27994s
9 tests, 69 assertions, 0 failures, 0 errors, 0 skips

OK!!続いてレスポンスをテストします。

createアクションのレスポンスをテストする

user_token_controller.rbのレスポンスは、有効期限とログインしたユーザーオブジェクトです。

レスポンス有効期限のテスト

有効期限は、デコードしたpayloadexpの値とレスポンスのexpが一致していればOKです。

  ...
  test "create_action" do
    ...
    # http_onlyはtrueか
    assert cookie_options[:http_only]

    # 追加
    ## レスポンスのテスト

    # レスポンス有効期限は一致しているか
    assert_equal exp, response_body["exp"]
  end
end

レスポンスユーザーのテスト

ユーザーは、トークンを発行したユーザーとレスポンスユーザーが一致していればOKです。

  ...
  test "create_action" do
    ...
    ## レスポンスのテスト

    # レスポンス有効期限は一致しているか
    assert_equal exp, response_body["exp"]

    # 追加
    # レスポンスユーザーは一致しているか
    assert_equal @user.my_json, response_body["user"]
  end
end

テストを実行してみましょう。

root $ docker-compose run --rm api rails t controllers

Finished in 1.73585s
9 tests, 71 assertions, 0 failures, 0 errors, 0 skips

destroyアクションをテストする

user_token_controller.rbdestroyアクションは、クッキーが削除されているかをテストします。

		...
    # レスポンスユーザーは一致しているか
    assert_equal @user.my_json, response_body["user"]
  end

  # 追加
  test "destroy_action" do
    assert @request.cookie_jar[@key].present?

    delete api_url("/user_token")
    assert_response 200

    # Cookieは削除されているか
    assert @request.cookie_jar[@key].nil?
  end
  # ここまで
end

最後のテスト実行です。

root $ docker-compose run --rm api rails t controllers

Finished in 1.99193s
10 tests, 75 assertions, 0 failures, 0 errors, 0 skips

以上でuser_token_controller.rbのテストは完了です。

pushする

サーバーサイドのログイン機能は完成です。

今のブランチをマージしてpushしましょう。

root $ cd api
api $ git commit -am "add_user_token_controller_test.rb"
api $ git checkout master
api $ git merge <ブランチ名>
api $ git push

Herokuにもpushします。

api $ git push heroku

pushが終わったら、

  • ユーザー一覧ページ(/users)が削除されているか
  • current_userページ(/users/current_user)がアクセス不能か

を確認しましょう。

# "ページが見つかりません"になる
api $ heroku open /api/v1/users

# "HTTP ERROR 401"が返ってくる
api & heroku open /api/v1/users/current_user

本番環境の動作確認

curlコマンドを使って本番環境の動作確認を行います。

curlコマンドはHTTPリクエストを行うためのコマンドで、Macに標準装備されています。

curlのインストール確認

インストールを確認するには-vもしくは--versionを実行します。

api $ curl -V

curl 7.64.1 (x86_64-apple-darwin19.0) ...

もし、エラーが出る場合はインストールされていません。

その場合、Homebrew経由でインストールが可能です。

api $ brew install curl

インストールを詳しく => curl入門(mac編) - Qiita

ログインリクエストの動作確認

ログインするには、/user_tokenPOSTメソッドでアクセスします。

パラメーターとして、DBに保存されているユーザーのメールアドレスとパスワードが必要です。

api $ curl -v -X POST \
https://<Herokuアプリ名>.herokuapp.com/api/v1/user_token \
-H 'Content-Type: application/json' \
-d '{"auth":{"email":"user0@example.com", "password":"password"}}'
  • -v … HTTPリクエストの詳細を表示する。
  • -X … HTTPメソッドの指定。
  • -H or --header … HTTPヘッダーの追加。
  • -d or --data … リクエストパラメーターの追加。

成功すれば、有効期限とユーザーが返ってきます。

{"exp":1602732306,
"user":{"id":1,
"name":"user0",
"email":"user0@example.com",
"created_at":"2020-08-25T10:39:06.160+09:00"}}

curlのJSONレスポンスを見やすくする

レスポンスのJSONを見やすくするには、curlコマンドの最後にpython -m json.toolをパイプで繋ぎます。

...
-d '{"auth":{"email":"user0@example.com", "password":"password"}}' \
| python -m json.tool

ユーザーアクションの動作確認

ユーザーコントローラーのshowアクションにアクセスし、トークンのユーザーが返ってきているか確認します。

まず、本番環境で使用するトークンを用意する必要があります。

herokuコマンドを使って、本番環境のRailsコンソールに入りましょう。

api $ heroku run rails c

コンソールに入ったら、ユーザーID「1」のユーザーのトークンを発行し、コピーします。

> User.find(1).to_token | pbcopy
=> "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleH..."

コピーできたら、コンソールからは抜けます。

> exit

--cookieオプションを使って、/users/current_userにアクセスします。

api $ curl -v \
--cookie "access_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleH..." \
https://<Herokuアプリ名>.herokuapp.com/api/v1/users/current_user \
| python -m json.tool
  • --cookie "key=value" … Cookieをセットする。

    keyには「access_token」、valueにはトークンを指定します。

ユーザーID「1」のユーザーが返されました。

{
    "created_at": "2020-08-25T10:39:06.160+09:00",
    "email": "user0@example.com",
    "id": 1,
    "name": "user0"
}

全ての確認が取れたら、「root」ディレクトリに戻ります。

api $ cd ..

まとめ

今回はuser_token_controller.rbのテストと、認証実装をHerokuへデプロイしました。

これでサーバーサイドのログイン認証の実装を終わります。

お疲れさまでした。

このチャプターまとめ

チャプター「サーバーサイドのログイン認証」では、以下のことを行いました。

次回からは?

次回から、Nuxt.jsサイドのログイン認証の実装に入ります。

Nuxt.jsでは、トークンではなく有効期限でログイン状態を判定します。

それではまた次回お会いしましょう。

あなたの力になれること
私自身が独学でプログラミングを勉強してきたので、一人で学び続ける苦しみは痛いほど分かります。そこで、当時の私がこんなのあったら良いのにな、と思っていたサービスを立ち上げました。周りに質問できる人がいない、答えの調べ方が分からない、ここを聞きたいだけなのにスクールは高額すぎる...。そんな方に向けた単発・短期間メンターサービスを行っています。下のサービスへお進みください。
独学プログラマのサービス
次の記事はこちら
1. このカテゴリーの歩き方 #01
【お知らせ】UdemyでRails × Nuxt.jsの動画を公開することになりました
1. このカテゴリーの歩き方 #02
【随時更新】アプリケーション仕様書
1. このカテゴリーの歩き方 #03
【随時更新】このカテゴリーの歩き方
1. このカテゴリーの歩き方 #04
(Docker+Rails6+Nuxt.js+PostgreSQL)=>Heroku 環境構築~デプロイまでの手順書
2. Docker入門 #01
Docker for Macをインストールする手順
2. Docker入門 #02
分かるDocker解説。仮想環境・コンテナ・Dockerイメージ・Dockerfileとは何か?
2. Docker入門 #03
分かるDocker解説。DockerComposeとは何か?
3. Dockerを使ったRails+Nuxt.js環境構築 #01
【Docker+Rails6+Nuxt.js】今回作成するアプリの開発環境の全体像を知ろう
3. Dockerを使ったRails+Nuxt.js環境構築 #02
【MacOS】Homebrew経由でGitをインストールする方法
3. Dockerを使ったRails+Nuxt.js環境構築 #03
Rails6を動かすAlpineベースのDockerfileを作成する(AlpineLinuxとは何か)
3. Dockerを使ったRails+Nuxt.js環境構築 #04
Nuxt.jsを動かすAlpineベースのDockerfileを作成する(C.UTF-8とは何か)
3. Dockerを使ったRails+Nuxt.js環境構築 #05
.envファイルを使ったdocker-compose.ymlの環境変数設計
3. Dockerを使ったRails+Nuxt.js環境構築 #06
Rails6・Nuxt.js・PostgreSQLを動かすdocker-compose.ymlファイルを作成する
3. Dockerを使ったRails+Nuxt.js環境構築 #07
docker-compose.ymlを使ってRails6を構築する(PostgreSQLパスワード変更方法)
3. Dockerを使ったRails+Nuxt.js環境構築 #08
docker-compose.ymlを使ってNuxt.jsを構築する
4. 複数プロジェクトのGit管理 #01
複数プロジェクトで行うGit管理の全体像を理解しよう(Gitサブモジュール解説)
4. 複数プロジェクトのGit管理 #02
【Git】既存の子ディレクトリをサブモジュール管理に変更する手順
4. 複数プロジェクトのGit管理 #03
【GitHub】秘密鍵の生成・公開鍵を追加・SSH接続するまでを画像で分かりやすく
4. 複数プロジェクトのGit管理 #04
【GitHub】リモートリポジトリの追加・サブモジュールのリンク設定を行う
5. RailsAPI×Nuxt.js初めてのAPI通信 #01
【Rails6】"Hello" jsonを返すコントローラを作成する
5. RailsAPI×Nuxt.js初めてのAPI通信 #02
【Nxut.js】axiosの初期設定を行う(baseURL・browserBaseURLを解説)
5. RailsAPI×Nuxt.js初めてのAPI通信 #03
【Rails6】Gem rack-corsを導入してCORS設定を行う(オリジン・CORSとは何か)
6. Heroku.ymlを使ったDockerデプロイ #01
デプロイ準備。Herokuへ新規会員登録を行いHerokuCLIをインストールする
6. Heroku.ymlを使ったDockerデプロイ #02
heroku.yml解説編。Docker環境のRails6をHerokuにデプロイする(1/2)
6. Heroku.ymlを使ったDockerデプロイ #03
HerokuCLI-manifestのデプロイ解説編。Docker環境のRails6をHerokuにデプロイする(2/2)
6. Heroku.ymlを使ったDockerデプロイ #04
Dockerfile解説編。Docker環境のNuxt.jsをHerokuにデプロイする(1/2)
6. Heroku.ymlを使ったDockerデプロイ #05
デプロイ完結編。Docker環境のNuxt.jsをHerokuにデプロイする(2/2)
7. モデル開発事前準備 #01
【Rails6】application.rbの初期設定(タイムゾーン・I18n・Zeitwerk)
7. モデル開発事前準備 #02
【Rails6】モデル開発に必要なGemのインストールとHirb.enableの自動化
7. モデル開発事前準備 #03
【Docker+Rails】A server is already running. Check /tmp/pids/server.pidエラーの対応
7. モデル開発事前準備 #04
【Docker】<none>タグのイメージを一括削除する & Rails .gitignoreの編集
8. ユーザーモデル開発 #01
Railsユーザーモデル作成。テーブル設計・ユーザー認証設計を理解する
8. ユーザーモデル開発 #02
Railsユーザーモデルのバリデーション設定(has_secure_password解説)
8. ユーザーモデル開発 #03
Railsバリデーションエラーメッセージの日本語化(ja.yml設定方法)
8. ユーザーモデル開発 #04
EachValidatorクラスのカスタムバリデーション設定(Rails6/lib以下読込)
8. ユーザーモデル開発 #05
Rails環境ごとにSeedデータ切り替えるseeds.rbの書き方
8. ユーザーモデル開発 #06
Rails6から導入された並列テストを理解する
8. ユーザーモデル開発 #07
Railsユーザーモデルバリデーションテスティング(name/email/password)
8. ユーザーモデル開発 #08
Nuxt.jsからRailsのユーザーテーブルを取得しHerokuにデプロイする
9. Nuxt.jsフロント開発事前準備 #01
【Nuxt.js2.13超解説】バージョンアップ手順と6つの新機能+2つの変更点
9. Nuxt.jsフロント開発事前準備 #02
Docker AlpineベースのNode.js上で動くNuxt.jsにVuetifyを導入する
9. Nuxt.jsフロント開発事前準備 #03
VuetifyにカスタムCSSを導入してオリジナルブレイクポイントを作る
9. Nuxt.jsフロント開発事前準備 #04
Nuxt.jsにnuxt-i18nを導入して国際化に対応する
10. ログイン前のレイアウト構築 #01
Nuxt.jsのレイアウト・ページ・コンポーネントの役割を理解しよう
10. ログイン前のレイアウト構築 #02
Nuxt.js ウェルカムページを構成するコンポーネントファイル群を作成しよう(1/4)
10. ログイン前のレイアウト構築 #03
Nuxt.js ウェルカムページにアイキャッチ画像・アプリ名・メニューボタンを表示しよう(2/4)
10. ログイン前のレイアウト構築 #04
Nuxt.js addEventListenerでスクロールを検知しツールバーの色を変化させよう(3/4)
10. ログイン前のレイアウト構築 #05
Nuxt.js ウェルカムページをレスポンシブデザインに対応させよう(4/4)
10. ログイン前のレイアウト構築 #06
Nuxt.js 会員登録ページのレイアウトファイルを作成しよう(1/4)
10. ログイン前のレイアウト構築 #07
Nuxt.js 名前、メール、パスワードのコンポーネントファイルを作成しよう(2/4)
10. ログイン前のレイアウト構築 #08
Nuxt.js 親子コンポーネント間の双方向データバインディングを実装する(3/4)
10. ログイン前のレイアウト構築 #09
Nuxt.js Vuetifyのv-text-fieldを使った会員登録フォームのバリデーション設定(4/4)
10. ログイン前のレイアウト構築 #10
Nuxt.js ログインページ実装とHerokuデプロイまで(router. replaceとpushの違いとは)
11. ログイン後のレイアウト構築 #01
Nuxt.js ログイン後のツールバーを作成しよう(inject解説)
11. ログイン後のレイアウト構築 #02
Nuxt.js アカウントメニューページ・ログアウト機能を実装しよう(nuxt-child解説)
11. ログイン後のレイアウト構築 #03
Nuxt.js ログイン後のトップページにプロジェクト一覧を表示しよう
11. ログイン後のレイアウト構築 #04
Nuxt.js プロジェクトページにVuetifyのナビゲーションドロワーを追加しよう
11. ログイン後のレイアウト構築 #05
Nuxt.js paramsIDからプロジェクトを検索してVuexに保存しよう
12. サーバーサイドのログイン認証 #01
JWTとは何か?(ruby-jwtのインストール)
新着
12. サーバーサイドのログイン認証 #02
【Rails×JWT】ログイン認証解説とJWT初期設定ファイルの作成
新着
12. サーバーサイドのログイン認証 #03
【Rails×JWT】トークン発行とデコードを行うAuthTokenクラスの作成
新着
12. サーバーサイドのログイン認証 #04
【Rails×JWT】 ログイン判定を行うAuthenticatorモジュールの作成
新着
12. サーバーサイドのログイン認証 #05
【Rails×JWT】UserクラスからJWTを扱うTokenizableモジュールの作成
新着
12. サーバーサイドのログイン認証 #06
【Rails×JWT】AuthTokenクラスとAuthenticatorモジュールをテストする
新着
12. サーバーサイドのログイン認証 #07
【Rails×JWT】JWTをCookieに保存するログインコントローラーの実装
新着
12. サーバーサイドのログイン認証 #08
【Rails×JWT】ログインコントローラーのテストとHerokuデプロイ
新着
SPA開発
ブログ構築
小ネタ集