LoginSignup
28
29

More than 1 year has passed since last update.

Rails APIモード + devise_token_auth + Vue.js 3 で認証機能付きのSPAを作る(Rails編)

Last updated at Posted at 2021-01-18

はじめに

本記事は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モジュールのうち、ブラウザ向けアプリケーションでしか使われないモジュールをすべて除外します。

・ビュー、ヘルパー、アセットを生成しないようジェネレーターを設定します。

出典: Rails による API 専用アプリケーション

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の詳しい使い方については以下の記事が非常に詳しいです。

Devise入門64のレシピ その1

次に作成された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側を実装していきます。

28
29
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
29