LoginSignup
9
7

More than 3 years have passed since last update.

Rails 6+Grapeで作るAPIサーバーにDeviseトークン認証を付ける

Last updated at Posted at 2020-01-21

はじめに

iOS・AndroidアプリやWebアプリをクライアントとしたAPIサーバーをRuby on Railsで実装するケースがあります。この記事では、Grape(REST APIフレームワーク)を利用して作るAPIサーバーにDevise(認証機能を提供するgem)を組み合わせ、アクセストークンを介した認証方式を実装する手順を紹介します。

同様の紹介事例について

Devise + Grape で認証付きAPIの実装 - Qiita

上記の記事では本記事と同様に、GrapeとDeviseに加えてトークン認証機能を付加するgemであるdevise_token_authを用いて実装する手法が紹介されています。ただし、

Token の更新に関してですが、今回の例では一度発行した Token は再度ログインするまで更新されません。

とあるようにdevise_token_authの本来の機能であるリクエストごとにアクセストークンを更新する仕様は有効になっていません。ここでは、上記記事のコードをベースに、Grapeとdevise_token_authの仲立ちをするgrape_devise_token_authを加えることでその点をクリアし、現行のRails 6で動作するサンプルを作ります。

前提条件

以降の手順解説では、Ruby 2.6.5とRails 6.0.2.1を使用することを想定しています。

Railsアプリを作成する

# rails new token-auth-sample

Railsアプリを新規作成して、以下のGemを追加します。

Gemfile.rb
gem 'devise'
gem 'devise_token_auth'
gem 'grape'
gem 'grape_devise_token_auth'
$ bundle install

Devise関連の環境設定を行う

# rails g devise:install
# rails g devise_token_auth:install User auth

新たに作成されたUserモデルのマイグレーションを行います。

# rails db:migrate

基本部分のコード修正

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include DeviseTokenAuth::Concerns::SetUserByToken

  protect_from_forgery with: :null_session
end

protect_from_forgeryは一般的なRailsアプリにおけるCSRF対策の仕組みに関する設定で、指定しない場合はPOSTリクエストを受け付けた際にActionController::InvalidAuthenticityTokenエラーとなります。これを無効化するには protect_from_forgery with: :null_session とします。

app/models/user.rb
class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  include DeviseTokenAuth::Concerns::User
end

Userモデルでは、devise メソッドで有効化する機能が列挙されていますが、 :trackable が含まれていたら外しておきましょう。前述の rails g devise_token_auth:install User auth コマンドで作成されたマイグレーションでtrackable用のカラムが作成されておらず、サインインリクエストの際に NoMethodError (undefined method `current_sign_in_at' 〜 が発生するためです。(末尾の参考情報を参照)

config/routes.rb
Rails.application.routes.draw do
  devise_for :users

  namespace :api do
    mount_devise_token_auth_for 'User', at: '/v1/auth'

    mount Api::Root => '/'
  end
end

APIエンドポイント部分のコード追加

app/api/api.rb
module Api
  class Root < Grape::API
    GrapeDeviseTokenAuth.setup! do |config|
      config.authenticate_all = true
    end

    mount V1::Root
  end
end
api/v1/root.rb
module V1
  class Root < Grape::API
    version 'v1', using: :path
    format :json

    auth :grape_devise_token_auth, resource_class: :user

    helpers GrapeDeviseTokenAuth::AuthHelpers

    desc 'GET /api/v1/test'
    get 'test' do
      authenticate_user!

      {
        message: 'test',
        current_user_uid: current_user.uid,
        authenticated?: authenticated?,
      }
    end
  end
end

ここまでで、以下のエンドポイントが利用可能になっています。

  • ユーザーアカウント作成
    • POST http://localhost:3000/api/v1/auth
  • サインイン
    • POST http://localhost:3000/api/v1/auth/sign_in
  • サンプルGETリクエスト
    • GET http://localhost:3000/api/v1/test

動作確認

ローカルサーバーを起動しておきます。

# rails s
=> Booting Puma
=> Rails 6.0.2.1 application starting in development
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 4.3.1 (ruby 2.6.5-p114), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:3000
* Listening on tcp://[::1]:3000
Use Ctrl-C to stop

サインアップ(ユーザーアカウント作成)

まず、新しいユーザーを作成します。

0584E428-FCF5-4FE1-BBE2-AE3C2770069C.png

cURLでのリクエスト例
# curl --request POST \
  --url http://localhost:3000/api/v1/auth \
  --header 'content-type: application/x-www-form-urlencoded' \
  --data email=user01@example.com \
  --data password=user01 \
  --data password_confirmation=user01

サインアップリクエストのレスポンスヘッダーには、このユーザーの認証情報が含まれています。次のリクエストの際には認証情報として access-token, client, uid の値を指定します。

GETリクエスト

次に、作成されたユーザーでGETリクエストを行います。認証情報を収めた3つのリクエストヘッダーを付加します。

5B2BA75C-6C45-4C61-B669-364146A169CC.png

cURLでのリクエスト例
# curl --request GET \
  --url http://localhost:3000/api/v1/test \
  --header 'access-token: b638qOBLJ_sIEEJEiMC1ug' \
  --header 'client: 1XcTtaq2uA3DK0qvY6qM9Q' \
  --header 'uid: user01@example.com'

レスポンスヘッダーには access-token, client, uid が含まれており、 access-token の値が更新されています。 access-token はリクエストを行うたびに新しい値と置き換わることから、毎回異なるアクセストークンを用いてアクセスする仕様で動作していることが分かります。

おわりに

RailsとGrapeを使ったAPIプロジェクトで、Deviseの認証機能を用いてトークン認証を行う手順を紹介しました。devise_token_authのトークン更新機能が有効になっており、トークンの有効期間を絞れるという点で安全上望ましい仕様になっています。一方APIクライアントの方では、トークンの値が変更されるのに応じて書き換わったトークンを適切に更新管理することが大切になります。

参考情報

9
7
0

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
9
7