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のエンドポイントは [このドキュメントを参考](https://github.com/lynndylanhurley/devise_token_auth#usage-tldr)してください。(次の項目で動作確認します)

```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.rb` に `mount API::Root => '/'` を加える(前述)のを忘れずに。

## 動作確認
以下動作確認です。[Postman](https://chrome.google.com/webstore/detail/postman-rest-client-short/mkhojklkhkdaghjjfdnphfphiaiohkef?utm_source=chrome-ntp-icon) という chrome のプラグインを用いてリクエストを作成しています。`curl` コマンドでも、他のクライアントでも基本的に動作します。

##### 1. `POST /api/v1/auth` でユーザ作成と Token の取得

-  `email`, `password`, `password_confirmation` が必須
-  レスポンスの Header に `Uid`, `Client`, `Access-Token` 

![Screen Shot 2015-06-30 at 11.37.39 AM.png](https://qiita-image-store.s3.amazonaws.com/0/31970/0b108a03-2fa8-e218-9e46-c93de2f4037d.png)

##### 2. `GET /api/v1/test` でデータの取得

- ヘッダーに `Client`, `Access-Token`, `Uid` をセットしてリクエスト
- 正しくデータが帰ってくることを確認

![Screen Shot 2015-06-30 at 11.38.56 AM.png](https://qiita-image-store.s3.amazonaws.com/0/31970/8e175b8d-882e-4a41-35e0-8e5b16150e45.png)

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

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

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

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

## 参考

- [AngularJSとRailsをTokenベースの認証で繋ぐ方法(devise_token_auth + ng-token-auth)](http://qiita.com/jwako/items/d9f966a5edca1b07c393)
  - devise_token_auth 以外の認証方法の紹介
  - devise_token_auth の説明
  - ng-token-auth との連携

- [AngularとRailsを分離し、セキュアに繋ぐ方法](http://qiita.com/jwako/items/4b134a0d7c539971bded)
  - AngularJS と Rails の分離について

- [ng-token-auth](https://github.com/lynndylanhurley/ng-token-auth)
  - AngularJS で認証するためのライブラリ

- [devise_token_auth](https://github.com/lynndylanhurley/devise_token_auth)
  - 今回利用した Token ベースの認証を提供する Gem

- [devise_token_auth/issues/73](https://github.com/lynndylanhurley/devise_token_auth/issues/73)
  - Grapeで認証のためのヘルパーの参考コード
  - 今後デフォルトで devise_token_auth が Grape をサポートすることがあるかも？