今回達成すること
前回作成した
そして、本チャプターでの変更を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に保存されたか?を検証するには
create
アクションを介して保存されたクッキーは、後者のActionDispatch
クラスに保存されます。
よって、ActionDispatch
クラスのCookieに保存されたかを検証する必要があります。
ActionDispatch
のCookieを参照するには、@request.cookie_jar[<キー>]
メソッドを使用します。
参考: https://www.rubydoc.info/docs/rails/4.1.7/ActionDispatch/Request:cookie_jar
以上を理解した上で、ログインコントローラーをテストしていきましょう。
createアクションのCookieをテストする
まずは、
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
Cookieのオプションをテストする
Cookieのオプションをテストします。
テスト項目は、
expires(エクスパイアーズ)
の値は正しいかsecure(セキュア)
は開発環境でfalse
かhttp_only
はtrue
であるか
の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アクションのレスポンスをテストする
レスポンス有効期限のテスト
有効期限は、デコードしたpayload
のexp
の値とレスポンスの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アクションをテストする
destroy
アクションは、クッキーが削除されているかをテストします。
...
# レスポンスユーザーは一致しているか
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
以上で
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_token
にPOST
メソッドでアクセスします。
パラメーターとして、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 ..
まとめ
今回は
これでサーバーサイドのログイン認証の実装を終わります。
お疲れさまでした。
このチャプターまとめ
チャプター「サーバーサイドのログイン認証」では、以下のことを行いました。
- JWTとは何か?
- ログイン認証の流れを解説。JWT初期設定ファイルの作成
- JWTの発行と検証を行うAuthTokenクラスの作成
- JWTを検証するAuthenticatorモジュールの作成
- JWTの発行とデコードをUserクラスに追加
- AuthTokenクラスろAuthenticatorモジュールをテストする
- ログインコントローラーの作成
- ログインコントローラーのテストと本番環境へのデプロイ(今ここ)
次回からは?
次回から、Nuxt.jsサイドのログイン認証の実装に入ります。
Nuxt.jsでは、トークンではなく有効期限でログイン状態を判定します。
それではまた次回お会いしましょう。