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


ここ一年ほど Python でバックエンドエンジニアやってます。自分の勉強も兼ねて、デモ用 API を Ruby on Rails で作成 したので、その時の実装と参考にした記事をまとめます。

間違った内容を記述してしまうことがあるかもしれません。その際はコメント欄やツイッターなどで指摘していただけると助かります。

対象

Ruby on Rails で作成したアプリに API の実装を考えている方。
主に Grape と Devise という gem を使って API に認証機能を作成する方法について紹介します。
Ruby on Rails の環境構築や、フレームワークのメリット、API の仕組みなどについては紹介しません。

余談: 実装する背景と選択肢

Rails は API を提供するのみで、残りはすべてクライアントに委譲する、というのが目的です。
フロントエンドは AngularJS などを用いて、Ajax で必要なデータを API 叩いて取ってくる、という形になります。
ほかのクライアント (たとえば python で書かれたクライアントなど) も API を利用することができるので、一般的な

1.コントローラを Web ページ用に記述
2. API の実装

という作業のうち、コントローラを記述する工程を省略し、API の実装に集中できるというのがメリットです。デメリットとしては、Ruby on Rails が生成してくれる HTML タグなどをフロントエンドが使えないことです。API の提供のみなので、基本的にサーバとの通信は JSON のみになります。

この記事では Grape (Rails の API 作成用 gem) で作成するAPIに Devise (認証機能用の gem) の認証機能を追加する、という方法を説明します。これは、Rails の認証機能に Devise を用いるケースは多く、また API を実装する上でも Grape を利用するケースは多いと考えたからです。これ以外にも doorkeeper を用いる方法など、いくつか方法はあるようです。この記事を書く上で参考にしたサイトを最後に追加してあるので、よかったらそちらも参考にしてください。
以上余談でした。

インストール

Gemfile.rb
gem 'devise'
gem 'devise_token_auth'
gem 'grape'
gem 'omniauth', '>= 1.0.0'

設定

rails g devise_token_auth:install [User] [auth]
  • User: 認証で利用するモデル
  • auth: 自動で生成してくれる認証用APIのエンドポイント
    • この場合 http://locahost:3000/auth/ 以下に認証用APIが生成される
    • 後から変更する場合は config/routes.rb を編集するだけなので特に気にせず

注: このコマンドで migration ファイルが生成されるので、編集した後、rake db:migrate を忘れずに

applicatoin_controller.rb
class ApplicationController < ActionController::Base
  include DeviseTokenAuth::Concerns::SetUserByToken
  # protect_from_forgery with: :exception から下記へ
  # これをしないと API で POST の時にCSRFエラー
  protect_from_forgery with: :null_session
end

model/user.rb
class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
          :recoverable, :rememberable, :trackable, :validatable, :omniauthable
          # :confirmable をコメントアウトする
          # これをしないと User を作成するときにエラーとなる
          # それ以外は自由に。
  include DeviseTokenAuth::Concerns::User
end
config/routes.rb
Rails.application.routes.draw do

  devise_for :users

  namespace :api do
    # localhost:3000/api/v1/auth に認証API
    mount_devise_token_auth_for 'User', at: '/v1/auth'

    # mount API::Root => '/' この行は下記の Grape の実装後に追加

  end
end

これで認証用の API が 'localhost:3000/v1/auth/' に定義されている。
APIのエンドポイントは このドキュメントを参考してください。(次の項目で動作確認します)

app/api/api.rb
module API
  class Root < Grape::API
    mount V1::Root
  end
end
app/api/v1/root.rb
module V1
  class Root < Grape::API

    version 'v1', using: :path
    format :json
    formatter :json, Grape::Formatter::Jbuilder

    helpers do
      def authenticate_error!
        # 認証が失敗したときのエラー
        h = {'Access-Control-Allow-Origin' => "*", 
             'Access-Control-Request-Method' => %w{GET POST OPTIONS}.join(",")}
        error!('You need to log in to use the app.', 401, h)
      end

      def authenticate_user!
        # header から認証に必要な情報を取得
        uid = request.headers['Uid']
        token = request.headers['Access-Token']
        client = request.headers['Client']
        @user = User.find_by_uid(uid)

        # 認証に失敗したらエラー
        unless @user && @user.valid_token?(token, client)
          authenticate_error!
        end
      end

    end

    desc 'GET /api/v1/test'
    get 'test' do
      authenticate_user!
      {message: 'test'}
    end
    # サンプルなので、単純に root.rb に記述
  end
end

注: config/routes.rbmount API::Root => '/' を加える(前述)のを忘れずに。

動作確認

以下動作確認です。Postman という chrome のプラグインを用いてリクエストを作成しています。curl コマンドでも、他のクライアントでも基本的に動作します。

1. POST /api/v1/auth でユーザ作成と Token の取得
  • email, password, password_confirmation が必須
  • レスポンスの Header に Uid, Client, Access-Token

2. GET /api/v1/test でデータの取得
  • ヘッダーに Client, Access-Token, Uid をセットしてリクエスト
  • 正しくデータが帰ってくることを確認

余談2: Token の更新や AngularJS との連携

以上で API に認証機能が追加されていることが確認できたと思います。
Token の更新に関してですが、今回の例では一度発行した Token は再度ログインするまで更新されません。セキュリティの理由から例えばリクエストのたびに Token を更新したい場合はこのページが参考になります (このページではAuthenticateRequest クラスを作って認証を行い、Grape の after 構文の中で Token の更新および更新された Token をレスポンスのヘッダーに入れています。)。個人的には一回のリクエストごとに Token の更新をするのは実装的に面倒だと考えたのと(例えば、iphone と web ページで同じユーザが操作している場合の対応など)、HTTPS を利用しているのであれば Token の更新は30日ごとなどでも十分だと思ったので深入りはしませんでした。

AngularJS との連携ですが、ng-token-auth というライブラリがあり、これを用いたところ楽にログインを実装できました。これに関しては AngularJSとRailsをTokenベースの認証で繋ぐ方法(devise_token_auth + ng-token-auth) や、ng-auth-token の README が参考になると思います。

まとめ

Grape で提供している API に Devise で認証機能を追加しました。これによって HTTP ヘッダーを利用して Token を用いた認証が可能となります。この記事では最低限の認証の仕組みを実装したにすぎないですが、本番環境では、Token の有効期限や、有効期限が切れた後の更新などを考える必要がある点に注意です。

参考