Rails / Ruby / OAuth2.0

Doorkeeperを使うとデフォルトの認証画面を使うのが定番のようですが、自前で認証画面を用意したりRakeタスクでアクセストークンを発行したいことがあったりします。

ということで、Railsコンソールからアクセストークンの発行までやってみました。

Doorkeeper インストール

まずはRailsプロジェクトのGemfileにDoorkeeper gemを追加してbundle installします。

# Gemfile
gem 'doorkeeper'

今回はdoorkeeper-2.1.4が入りました。

続いてActiveRecord用の設定&テーブルを作成します。

$ rails generate doorkeeper:install
$ rails generate doorkeeper:migration
$ rails db:migrate

こうするとデータベースに

  • oauth_applications
  • oauth_access_grants
  • oauth_access_tokens

というテーブルが作られます。

Doorkeeperを設定

Doorkeeperをインストールするとconfig/initializers/doorkeeper.rbに設定ファイルも作られます。

Deviseユーザーを認証する場合は

# config/initializers/doorkeeper.rb
resource_owner_authenticator do
  current_user || warden.authenticate!(:scope => :user)
end

という感じでresource_owner_authenticatorを設定します。

また、インストール時にconfig/routes.rbも更新されますが、今回はDoorkeeperのアプリケーションは使わないので元に戻しておきます。

OAuth2アプリケーションの作成

続いてRails ConsoleからOAuth2アプリケーションを作成します。

ActiveRecordの場合はDoorkeeper::Applicationオブジェクトを作ればOKです。

> client = Doorkeeper::Application.create(name: 'アプリケーション名', redirect_uri: '認証後のリダイレクトURL')

作成したアプリケーションのID(Client ID)とClient Secretを確認します。

> client.uid
=> "094ce26134c0cbee97c91511b7f91137cb1963d7186066948b3126bb8e0f3ad76fce522e01634ee51e20954977c5b77f45b682c1ff4570a66598fcf19699d23b"
> client.secret
=> "49f947fceebbfd3c5e8100cbde8e410530e6a7a0d1c5a9af90705760968af4e6e719ec1776e49bf2f21673868f0ce2aa5d24fe697229e6fc6d3661614fded3cd"

ということで無事OAuth2認証用アプリケーションが作成できました。

認証コードの生成

続いて認証コードの生成です。

認証コードの生成にはDoorkeeper::OAuth::CodeRequestクラスを使います。

params =
  ActionController::Parameters.new(
    client_id: client.uid,
    redirect_uri: client.redirect_uri,
    response_type: 'code'
  )

pre_auth = Doorkeeper::OAuth::PreAuthorization.new(Doorkeeper.configuration, client, params)

request = Doorkeeper::OAuth::CodeRequest.new(pre_auth, user)

response = request.authorize # Doorkeeper::OAuth::CodeResponseオブジェクト

code = response.auth # Doorkeeper::OAuth::Authorization::Codeオブジェクト

grant = code.token # Doorkeeper::AccessGrantオブジェクト

こんな感じでDoorkeeper::AccessGrantモデルのオブジェクトを取得します。

取得した認証コードを確認してみます。

> grant.token
=> "f001b3327b4f13f6aea35ffea3601f5d2eea0e18fa5393f928e25b52a314df36"

アクセストークンの生成

認証コードを取得したので、続いてアクセストークンを生成します。

params =
  ActionController::Parameters.new(
    client_id: client.uid,
    client_secret: client.secret,
    code: grant.token,
    grant_type: 'authorization_code',
    redirect_uri: client.redirect_uri
  )

request =
  Doorkeeper::OAuth::AuthorizationCodeRequest.new(
    Doorkeeper.configuration,
    grant,
    client,
    params
  )

response = request.authorize # Doorkeeper::OAuth::TokenResponseオブジェクト
access_token = response.token # Doorkeeper::AccessTokenオブジェクト

取得したアクセストークンを確認してみます。

> access_token.token
=> "c8f32d02ea56979882ca8ee659a64b4088774a973655ba6cbf91ef1fcb0f739f"

これでOAuth2認証に必要なアクセストークンが手に入りました。

OAuth2.0認証対応のAPIを作る

アクセストークンは作りましたが、APIがないと試せないのでGrapeを使って簡単なAPIを作ります

Grape gemをインストール

Gemfileにgrapeとgrape-active_model_serializersを追加してbundle installします。

# Gemfile
gem 'grape'
gem 'grape-active_model_serializers'

バージョンはgrape-0.11.0, grape-active_model_serializers-1.3.2が入りました。

API用のディレクトリを設定

app/apiを読み込むようにします。

# config/application.rb
...
config.paths.add 'app/api', glob: '**/*.rb'
...

サンプルAPIを作成

とりあえず簡単なAPIを作ります。

# app/api/api.rb
class API < Grape::API
  prefix 'api'

  format :json
  formatter :json, Grape::Formatter::ActiveModelSerializers
  content_type :json, 'application/json; charset=UTF-8'

  mount V1::SampleAPI
end
# app/api/v1/sample_api.rb
require 'doorkeeper/grape/helpers'

module V1
  class SampleAPI < Grape::API
    ## OAuth2認証用の設定を追加
    helpers Doorkeeper::Grape::Helpers
    before { doorkeeper_authorize! }

    version 'v1', using: :path

    resource :samples do
      get '/' do
        {foo: :bar}
      end
    end
  end
end

サンプルAPIにアクセスしてみる

APIにアクセスしてみると401 Unauthorizedになっています。

$ curl -I http://localhost:3000/api/v1/samples/
HTTP/1.1 401 Unauthorized
...

アクセストークンを使ってAPIにアクセスしてみる

アクセストークンの発行もAPIの用意もできたので、OAuth2 gemを使ってAPIを呼び出してみます。

require 'oauth2'

client_id     = 'CLIENT_ID'
client_secret = 'CLIENT_SECRET'
site          = 'http://localhost:3000'

client = OAuth2::Client.new(client_id, client_secret, :site => site)

access_token = 'ACCESS_TOKEN'

token = OAuth2::AccessToken.new(client, access_token)
response = token.get('/api/v1/samples')

レスポンスの内容を確認してみます。

> JSON.parse(response.body)
=> {"foo"=>"bar"}

ちゃんとOAuth2認証APIを呼び出すことができました。

まとめ

今回はDoorkeeper + GrapeでOAuth2認証APIを作ってみました。

認証機能付きAPIだと定番かと思いますのでよろしければご参考にどうぞ。

おまけ

今回GrapeでAPIを作りましたが、最初に試した時はGrapeのバージョンが古くて

undefined local variable or method `settings' for #<Grape::Endpoint:0x007fb15f9fa7c8>

とか

undefined method `head' for #<Grape::Endpoint:0x007f9f172bd280>

というエラーが出たりしました。

こういう時はbundle update grape grape-active_model_serializersでバージョンを上げればOKです。

また、grape-active_model_serializersのバージョンを上げるとactive_model_serializersのバージョンも上がります。

このとき古いDraperを使っている場合は

uninitialized constant ActiveModel::ArraySerializerSupport

というエラーが出たりします。

そういう場合はbundle update draperでDraperのバージョンも上げましょう。