酒と泪とRubyとRailsと

Ruby on Rails と Objective-C は酒の肴です!

Garage RailsでOauth認証付きのRest APIをお手軽開発!

CookpadさんがOSSで先日OSSで公開されたGarageはRestfulなAPI + Oauth(Doorkeeper)をワンストップで提供してくれるgemです。 ちょうど触る機会が出てきたので、今回四苦八苦しながら使ってみたのでそのメモです!


今回のサンプル実装

今回はOauthで認証して、以下のシンプルなAPIにアクセスできるようにするまでのサンプルを作成します。

GET /v1/users => ユーザーのリスト出力
GET /v1/users/:id => 個々のユーザー情報の出力

Gemの追加

Gemfileに以下を追加して、bundle install

1
2
3
4
5
6
7
gem 'garage', github: 'cookpad/garage'
gem 'responders', '~> 2.0' # If you use Rails4.2+

group :development, :test do
  gem 'factory_girl_rails', '~> 4.5.0'
  gem 'rspec-rails', '~> 3.1.0'
end

DBの設定(Migration)

GagrageやRspecの初期設定とか、マイグレーションとかを実行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Doorkeeper(Oauth認証)の初期設定
$ bundle exec rails generate doorkeeper:install

# Doorkeeper(Oauth認証)のMigrationファイル生成
$ bundle exec rails generate doorkeeper:migration

# DBの作成
$ bundle exec rake db:create

# 認証用のユーザーモデル作成
$ bundle exec rails g model user name:string email:string

# マイグレーション処理の実行
$ bundle exec rake db:migrate

Garageの設定

config/initializers/garage.rbを作成して、Garageの設定を記述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Garage.configure {}

Garage::TokenScope.configure do
  register :public, desc: 'accessing publicly available data' do
    access :read,   User
    access :write,  User
  end
end

Doorkeeper.configure do
  orm :active_record

  # デフォルトのスコープ
  default_scopes :public

  optional_scopes(*Garage::TokenScope.optional_scopes)

  # アプリケーションのオーナーの認証
  resource_owner_from_credentials do |routes|
    User.find_by(email: params[:username])
  end
end

ルーティングの設定

config/routes.rbにルーティングを追記。

1
2
3
4
5
6
7
Rails.application.routes.draw do
  use_doorkeeper

  scope :v1 do
    resources :users, only: %i(index show update)
  end
end

コントローラの作成

app/controllers/application_controller.rbに共通の設定を追記。

1
2
3
4
5
6
7
8
class ApplicationController < ActionController::Base
  # ↓ 以下追記する内容
  include Garage::ControllerHelper

  def current_resource_owner
    @current_resource_owner ||= User.find(resource_owner_id) if resource_owner_id
  end
end

app/controllers/users_controller.rbを作成して、設定を追記。

1
2
3
4
5
6
7
8
9
10
11
12
13
class UsersController < ApplicationController
  include Garage::RestfulActions

  # index
  def require_resources
    @resources = User.all
  end

  # show
  def require_resource
    @resources = User.find(params[:id])
  end
end

モデルの設定

更にapp/models/user.rbにモデルの設定を追記。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class User < ActiveRecord::Base
  include Garage::Representer
  include Garage::Authorizable

  property :id
  property :name
  property :email

  # index
  def self.build_permissions(perms, other, target)
    perms.permits! :read
  end

  # create/update/show/destory
  def build_permissions(perms, other)
    perms.permits! :read
    perms.permits! :write
  end
end

動作確認

1
2
3
4
5
# テストユーザーの作成
$ bundle exec rails runner 'User.create(name: "morizyun", email: "morizyun@example.com")'

# サーバーの起動
$ bundle exec rails s

http://localhost:3000/oauth/applications

上のURLにアクセスして、テスト用のクライアントを登録します。 登録したら、APPLICTION_IDAPPLICATION_SECRETを登録します。

1
2
3
4
5
6
7
8
# APPLICTION_IDとAPPLICATION_SECRETを使って、ACCESS_TOKENを取得
curl -u "$APPLICTION_ID:$APPLICATION_SECRET" -XPOST http://localhost:3000/oauth/token -d 'grant_type=password&username=morizyun@example.com'

# 上で取得したACCESS_TOKENを使ってAPIでuserの一覧を取得
curl -s -XGET -H "Authorization: Bearer $ACCESS_TOKEN" http://localhost:3000/v1/users | jq '.'

# 上で取得したaccess_tokenを使ってAPIでuserの一覧を取得
curl -s -XGET -H "Authorization: Bearer $ACCESS_TOKEN" http://localhost:3000/v1/users/1 | jq '.'

より複雑な処理のためにテストを書く

ここから更に複雑な処理を記述するために、RSpecでテストを記述できるようにします。

RSpecの設定

RSpecの設定。

1
2
# RSpecの初期設定
$ bundle exec rails g rspec:install

spec_helper.rbの設定

spec/spec_helper.rbにFactoryGirl関連の設定を追加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require 'factory_girl_rails'

RSpec.configure do |config|
  config.before :all do
    FactoryGirl.reload
    FactoryGirl.factories.clear
    FactoryGirl.sequences.clear
    FactoryGirl.find_definitions
  end

  config.include FactoryGirl::Syntax::Methods

  config.expect_with :rspec do |expectations|
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end
end

request_helper.rbの設定

RSpec実行時にOauthの認証処理を予めやってくれて、access_tokenを取得してくれるhelperの作成。 spec/request_helper.rbを作成して以下を追加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
require 'active_support/concern'

module RequestHelper
  extend ActiveSupport::Concern

  included do

    let(:params) { {} }

    let(:report_status_env) do
      {
          accept: 'application/json',
          authorization: report_status_authorization_header_value
      }
    end

    let(:report_status_authorization_header_value) { "Bearer #{report_status_access_token.token}" }

    let(:report_status_access_token) do
      FactoryGirl.create(
          :access_token,
          resource_owner_id: resource_owner.id,
          scopes: public,
          application: application
      )
    end

    let(:resource_owner) { FactoryGirl.create(:user) }
    let(:report_status_scopes) { 'public' }
    let(:application) { FactoryGirl.create(:application) }
  end
end

FactoryGirl(Fixture)の設定

spec/factories/users.rbで以下を追加。

1
2
3
4
5
6
7
8
FactoryGirl.define do
  factory :user do
    name "MyString"
    email "MyString"
    sequence(:name) {|n| "user#{n}" }
    email { "#{name}@example.com" }
  end
end

Doorkeeper用の設定として、spec/factories/doorkeeper.rbを追加。

1
2
3
4
5
6
7
8
9
10
11
12
FactoryGirl.define do
  factory :access_token, class: Doorkeeper::AccessToken do
    sequence(:resource_owner_id) { |n| n }
    application
    expires_in 1.hours
  end

  factory :application, class: Doorkeeper::Application do
    sequence(:name){ |n| "Application #{n}" }
    redirect_uri 'https://example.com/callback'
  end
end

Specファイルの作成

spec/requests/users_spec.rbを以下の様に変更。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require 'rspec_helper'
require 'request_helper'

RSpec.describe 'users', type: :request do
  include RequestHelper

  describe 'GET /v1/users' do
    let!(:users) { create_list(:user, 3) }

    it 'returns user resources' do
      get '/v1/users', params, env
      expect(response).to have_http_status(200)
    end
  end
end

RSpecの実施

1
2
3
4
5
6
7
8
# Test環境用のDBの作成
$ RAILS_ENV=test bundle exec rake db:create migrate

# migrationの実施
$ RAILS_ENV=test bundle exec rake db:migrate

# users_specの実施
$ bundle exec rspec -fp spec/requests/users_spec.rb

ここから使いこなすのに参考になりそうな資料

CookpadさんのTech Blog の解説記事です。かなりわかりやすいですし、APIを作る時に参考になります^^

RESTful Web API 開発をささえる Garage - クックパッド開発者ブログ

Cookpadのエンジニアさんが作ってくれているサンプルのおかげでかなり捗りました!

taiki45/garage-example - GitHub

Special Thanks

RESTful Hypermedia APIをRailsで実現するcookpad/garageが凄い - Qiita

おすすめの書籍