はじめに
本記事はAPIをRailsのAPIモードで開発し、フロント側をVue.js 3で開発して、認証基盤にdevise_token_authを用いてトークンベースの認証機能付きのSPAを作るチュートリアルになります。
RailsやVue.jsの環境構築には触れませんのであしからず。
Vue.js編(その1)はこちら
https://qiita.com/kk-icare/items/401114a1284f8e9ca135
Rails newでAPI作成
今回はsample-apiというプロジェクト名とします。
$ rails new sample-api -d postgresql -T --api
$ cd sample-api
$ rails db:create
DBはPostgreSQLを使用し、testディレクトリの生成をスキップし、APIモードでRailsアプリの雛形を作成するように実行します。
--api を指定することで、以下の設定がなされます。
・利用するミドルウェアを通常よりも絞り込んでアプリケーションを起動するよう設定します。特に、ブラウザ向けアプリケーションで有用なミドルウェア(cookiesのサポートなど)を一切利用しなくなります。
・ApplicationControllerを、通常のActionController::Baseの代わりにActionController::APIから継承します。ミドルウェアと同様、Action Controllerモジュールのうち、ブラウザ向けアプリケーションでしか使われないモジュールをすべて除外します。
・ビュー、ヘルパー、アセットを生成しないようジェネレーターを設定します。
deviseおよびdevise_token_authのインストール
Gemfileを編集します。
gem 'jbuilder'
gem 'rack-cors'
gem 'devise'
gem 'devise_token_auth'
$ bundle
jbuilderおよびrack-corsはデフォルトでコメントアウトされているので、それを外せばOKです。
簡単に使用意図を説明すると、jbuilderはRailsでJSONをシンプルに扱うため、rack-corsはCORS(Cross Origin Resource Sharing)の設定を簡単に行うために導入しています。
CORSとはなんぞや?については徳丸浩さんのYou Tubeが詳しかったです。
CORSの原理を知って正しく使おう
bundle installが終了したら、以下のコマンドを実行します。
sample-api $ rails g devise:install
sample-api $ rails g devise_token_auth:install User auth
上記のコマンドを実行すると、deviseのinitializerファイルやlocaleファイルが作成されます。
deviseの詳しい使い方については以下の記事が非常に詳しいです。
次に作成されたmigrationファイルを修正します。
## Rememberable
t.datetime :remember_created_at
# 追加
## Trackable
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string :unconfirmed_email # Only if using reconfirmable
修正したら以下のコマンドを実行します。
$ rails db:migrate
rack-corsの設定
config/initializers/cors.rbを以下のように編集します。
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'localhost:8080'
resource '*',
headers: :any,
expose: ['access-token', 'expiry', 'token-type', 'uid', 'client'],
methods: [:get, :post, :options, :delete, :put, :patch, :head]
end
end
上記の設定を行うことで、異なるオリジン(localhost:8080 から localhost:3000への通信等)間での通信を行うことができるようになります。
ワイルドカード(*)で指定すると、どのオリジンからの通信も許可してしまうようになり、SameOriginPolicyが意味を為さなくなってしまうため、ワイルドカードでの指定は避けましょう。
APIの疎通確認を行う
デフォルトの設定だと、リクエスト毎にtokenが更新されてしまうので、configをいじります。
# config/initializers/devise_token_auth.rb
# By default the authorization headers will change after each request. The
# client is responsible for keeping track of the changing tokens. Change
# this to false to prevent the Authorization header from changing after
# each request.
config.change_headers_on_each_request = false # <= コメントアウトを外す
# By default, users will need to re-authenticate after 2 weeks. This setting
# determines how long tokens will remain valid after they are issued.
config.token_lifespan = 2.weeks # <= コメントアウトを外す
# コメントアウトを外す
config.headers_names = {:'access-token' => 'access-token',
:'client' => 'client',
:'expiry' => 'expiry',
:'uid' => 'uid',
:'token-type' => 'token-type' }
ここまでできたら、認証機能を試してみましょう。
まずは以下のコマンドを実行してください。
$ rails c
> User.create!(name: 'テストユーザー', email: 'test-user+1@example.com', password: 'password')
次に、rails s コマンドでローカルサーバーを起動し以下のcurlコマンドを実行します。
$ curl -D - localhost:3000/auth/sign_in -X POST -d '{"email":"test-user+1@example.com", "password":"password"}' -H "content-type:application/json"
ログインに成功すると、以下のような値が返ると思います。
HTTP/1.1 200 OKn"
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: application/json; charset=utf-8
access-token: q1wL0eAS3IwKGcs5-8vEyA
token-type: Bearer
client: sd74van0pd3Sxs4O-fowvQ
expiry: 1641540499
uid: test-user+1@example.com
ETag: W/"12ac3053b26f91ca234280ac13a0790c"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 707fe01b-d25a-4167-b0f2-95e009c9271a
X-Runtime: 0.403161
Vary: Origin
Transfer-Encoding: chunked
{"data":{"id":1,"email":"test-user+1@example.com","provider":"email","uid":"test-user+1@example.com","allow_password_change":false,"name":"テストユーザー"}}
access-tokenやclientの値はログインしているかどうか、の判定に用いることになります。
curlで指定しているオプションについて説明しておくと、
-D - # レスポンスヘッダーを表示する。
-X # HTTPメソッドの指定に用いる
-d # POSTリクエストでRequest bodyにdataを入れて送信
-H # ヘッダ情報の付与
を行っています。
tokenの検証を行う
devise_token_authは、token検証のための機能も提供しています。
先ほど取得したtokenを活用して、以下のcurlリクエストを送ってみます。
$ curl -D - -H "access-token:取得したtoken" -H "client:取得したclient" -H "expiry:取得したexpiry" -H "uid:取得したuid" -H "content-type:application/json" localhost:3000/auth/validate_token
成功すると以下のレスポンスが返る
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: application/json; charset=utf-8
access-token: q1wL0eAS3IwKGcs5-8vEyA
token-type: Bearer
client: sd74van0pd3Sxs4O-fowvQ
expiry: 1641540499
uid: test-user+1@gmail.com
ETag: W/"f3e45c8f2942619bd67981aead0bc740"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 0b9e57df-1f3b-4597-9c0f-01a6b3f904be
X-Runtime: 0.086486
Vary: Origin
Transfer-Encoding: chunked
{"success":true,"data":{"id":1,"provider":"email","uid":"test-user+1@example.com","allow_password_change":false,"name":"テストユーザー"}}
scaffoldで簡単なCRUDを行う
deviseを用いているので、authenticate_user!メソッドが使えるようになります。
なので、scaffoldを用いて作成したcontrollerにauthenticate_user!メソッドを記述して、
きちんと認証判定が行われるかをテストしてみましょう。
$ rails g scaffold Post title:string body:text user:references
Running via Spring preloader in process 21
invoke active_record
create db/migrate/20210112131218_create_posts.rb
create app/models/post.rb
error rspec [not found] # Rspecを導入していないため
invoke resource_route
route resources :posts
invoke scaffold_controller
create app/controllers/posts_controller.rb
error rspec [not found] # Rspecを導入していないため
invoke jbuilder
create app/views/posts
create app/views/posts/index.json.jbuilder
create app/views/posts/show.json.jbuilder
create app/views/posts/_post.json.jbuilder
いくつか編集を加えます。
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :set_post, only: [:show, :update]
before_action :authenticate_user! # authenticate_user!を各アクション実行前に呼ばれるようにする
def index
@posts = Post.all
end
def show
end
def create
@post = Post.new(post_params)
if @post.save
render :show, status: :created # locationを削除
else
render json: @post.errors, status: :unprocessable_entity
end
end
def update
if @post.update(post_params)
render :show, status: :ok # locationを削除
else
render json: @post.errors, status: :unprocessable_entity
end
end
private
def set_post
@post = Post.find(params[:id])
end
def post_params
params.permit(:title, :body).merge(user: current_user) # require を削除し、current_userをmerge
end
end
# config/routes.rb
# 自動で追加されたresources :posts は消す
scope format: 'json' do # json形式のリクエストに対応
resources :posts
end
# app/models/user.rb
has_many :posts, dependent: :destroy
# app/views/_post.json.jbuilder
json.extract! post, :id, :title, :body, :created_at
json.user_name post.user.name
# json.url post_url(post, format: :json)を削除する
この状態でマイグレーションを実行してください。
$ rails db:migrate
先ほどと同様、curlコマンドで動作確認をしてみます。
# createアクション
$ curl localhost:3000/posts -X POST -d '{"title":"レビュー", "body":"面白い"}' \
-H "content-type:application/json" \
-H "access-token:取得したaccess-token" \
-H "client:取得したclient" \
-H "expiry:取得したexpiry" \
-H "uid:取得したuid"
{"id":1,"title":"レビュー","body":"面白い","user_name":"テストユーザー","created_at":"2021-01-12T22:27:28.290+09:00"}
# indexアクション
$ curl localhost:3000/posts -H "content-type:application/json" \
-H "content-type:application/json" \
-H "access-token:取得したaccess-token" \
-H "client:取得したclient" \
-H "expiry:取得したexpiry" \
-H "uid:取得したuid"
[{"id":1,"title":"レビュー","body":"面白い","user_name":"テストユーザー","created_at":"2021-01-12T22:27:28.290+09:00"}
]
きちんと認証をパスしていて、正常にjsonを取得することができました。
次回はVue.js側を実装していきます。