はじめに
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を追加します。
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
基本部分のコード修正
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
とします。
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' 〜
が発生するためです。(末尾の参考情報を参照)
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エンドポイント部分のコード追加
module Api
class Root < Grape::API
GrapeDeviseTokenAuth.setup! do |config|
config.authenticate_all = true
end
mount V1::Root
end
end
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
サインアップ(ユーザーアカウント作成)
まず、新しいユーザーを作成します。
# 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つのリクエストヘッダーを付加します。
# 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クライアントの方では、トークンの値が変更されるのに応じて書き換わったトークンを適切に更新管理することが大切になります。
参考情報
-
Devise + Grape で認証付きAPIの実装 - Qiita
https://qiita.com/travelist/items/ec0b08a9a19cbe9ec78b -
Grapeとdevise_token_authを併用する方法について
-
Usage with Grape · Issue #73 · lynndylanhurley/devise_token_auth
https://github.com/lynndylanhurley/devise_token_auth/issues/73 -
How can I use this gem with Grape?・FAQ - devise-token-auth
https://devise-token-auth.gitbook.io/devise-token-auth/faq#how-can-i-use-this-gem-with-grape
-
-
モデルでのTrackableオプションについて
- Remove Trackable option from generator by SugiKent · Pull Request #1362 · lynndylanhurley/devise_token_auth
https://github.com/lynndylanhurley/devise_token_auth/pull/1362 - Why is Trackable option still active? · Issue #1356 · lynndylanhurley/devise_token_auth
https://github.com/lynndylanhurley/devise_token_auth/issues/1356
- Remove Trackable option from generator by SugiKent · Pull Request #1362 · lynndylanhurley/devise_token_auth