ここ一年ほど 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 を用いる方法など、いくつか方法はあるようです。この記事を書く上で参考にしたサイトを最後に追加してあるので、よかったらそちらも参考にしてください。
以上余談でした。
インストール
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
を忘れずに
class ApplicationController < ActionController::Base
include DeviseTokenAuth::Concerns::SetUserByToken
# protect_from_forgery with: :exception から下記へ
# これをしないと API で POST の時にCSRFエラー
protect_from_forgery with: :null_session
end
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
# :confirmable をコメントアウトする
# これをしないと User を作成するときにエラーとなる
# それ以外は自由に。
include DeviseTokenAuth::Concerns::User
end
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のエンドポイントは このドキュメントを参考してください。(次の項目で動作確認します)
module API
class Root < Grape::API
mount V1::Root
end
end
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 という 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 の有効期限や、有効期限が切れた後の更新などを考える必要がある点に注意です。
参考
-
AngularJSとRailsをTokenベースの認証で繋ぐ方法(devise_token_auth + ng-token-auth)
- devise_token_auth 以外の認証方法の紹介
- devise_token_auth の説明
- ng-token-auth との連携
-
- AngularJS と Rails の分離について
-
- AngularJS で認証するためのライブラリ
-
- 今回利用した Token ベースの認証を提供する Gem
-
- Grapeで認証のためのヘルパーの参考コード
- 今後デフォルトで devise_token_auth が Grape をサポートすることがあるかも?