前回の記事で OAuth 認証を試みました。 今回は OAuth 認証したうえで、API へアクセスしてみます。 リフレッシュトークンの取得も試みることができたので、まとめておきます。
目次
参考
作成したもの OAuth 認証したうえで API へのアクセスできるように実装する。 もしトークンの有効期限が切れていたなら、トークンの更新の上でデータを取得する。
準備 前段として、OAuth の環境を整備する で OAuth 認証を実装しています。 こちらを基に拡張してゆきます。
OAuth クライアントの拡張 データベース拡張 OAuth 認証した時に返却される token をデータベースに保持してゆくために、User テーブルのカラムを拡張します。
以下のマイグレーションファイルを作成してトークンと、リフレッシュトークンを保管できるようにします。
db/migrate/[数字列]_add_columns_to_users.rb 1 2 3 4 5 6 class AddColumnsToUsers < ActiveRecord::Migration [5.2 ] def change add_column :users , :token ,:string add_column :users , :refresh_token ,:string end end
作成できたら、マイグレーションしておきます。
モデルの改修 User モデルを改修して、追加した token カラム・refresh_token カラムへトークンを保管するようにします。
app/models/user.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 class User < ApplicationRecord devise :database_authenticatable , :registerable , :recoverable , :rememberable , :validatable , :omniauthable def self .find_for_myapp(auth) puts auth puts auth.credentials.refresh_token user = User .find_or_create_by(provider: auth.provider, uid: auth.uid) unless user.new_record? return user end user.uid = auth.uid user.email = "#{auth.uid} -#{auth.provider} @myapp.com" user.password = Devise .friendly_token[0 , 20 ] user.save user 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 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class User < ApplicationRecord devise :database_authenticatable , :registerable , :recoverable , :rememberable , :validatable , :omniauthable def self .find_for_myapp(auth) puts auth puts auth.credentials.refresh_token user = User .find_or_create_by(provider: auth.provider, uid: auth.uid) unless user.new_record? user.token = auth.credentials.token user.refresh_token = auth.credentials.refresh_token return user end user.uid = auth.uid user.email = "#{auth.uid} -#{auth.provider} @myapp.com" user.password = Devise .friendly_token[0 , 20 ] user.token = auth.credentials.token user.refresh_token = auth.credentials.refresh_token user.save user end end
もし、新しいユーザーであったときは、トークンを更新する仕組みを作っておくことは必要です。
これで、token と refresh_token を保管できるようになりました。
OAuth プロバイダを拡張 OAuth プロバイダを拡張して、プロバイダに API を生やして上げます。
今回はシンプルに json を返すだけにします。
トークン有効期限と、refresh_token の有効化 今回は動作確認も目的なので、以下のようにトークンの有効期限を 1 分とし、refresh_token を有効化します。
config/initializers/doorkeeper.rb 1 2 3 4 5 6 7 Doorkeeper .configure do access_token_expires_in 1 .minutes use_refresh_token end
API のコントローラーを拡張 app/controllers/api/users_controller.rb 1 2 3 4 5 6 7 8 9 10 11 12 class Api::UsersController < Api::ApiController before_action :doorkeeper_authorize! respond_to :json def info respond_with current_resource_owner end def data respond_with ({ a: "AA" , b: "BB" }) end end
もともとユーザー情報を返していたコントローラーに json を返すだけのメソッド追加しました。 ルーティングが足りていないので、以下のようにルーティングを修正します。
config/routes.rb 1 2 3 4 5 6 7 8 9 10 11 12 Rails .application.routes.draw do use_doorkeeper devise_for :users , controllers: { sessions: 'users/sessions' } namespace :api do get '/info' => 'users#info' get '/data' => 'users#data' end end
Outh クライアントから PAuth プロバイダに問い合わせる クライアントと、プロバイダの用意ができたので、実際のデータ取得処理を記述してゆきます。
コントローラーの追加 データを取得しビューに渡すコントローラを、作成します。 名前を test コントローラとします。(安直ですね。)
以下のようにします。
app/controllers/test_controller.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 require 'rest-client' class TestController < ApplicationController before_action :authenticate_user! def index token = current_user.token url = 'http://localhost:5000/api/data' res = RestClient .get url, { :Authorization => "bearer #{token} " , content_type: :json } @res = JSON .parse(res.body) end end
こちらを用意したら、test#index
へのルーティングを追加します。 以下のようになります。
config/routes.rb 1 2 3 4 5 6 7 Rails .application.routes.draw do devise_for :users , :controllers => { :omniauth_callbacks => "omniauth_callbacks" } get 'index' , to: 'test#index' root to: 'test#index' end
ログインしたらリダイレクトされる root_path にもしておきました。
表示用の.erb
を作成します。 データ表示したいだけなので、かなり適当です。
app/views/test/index.html.erb 1 2 3 4 <%= @res %><br /> <%= @res .inspect %><br /> <%= @res ["a" ] %><br /> <%= @res ["b" ] %><br />
一旦動作確認します。/index
にアクセスすると、以下の様に表示されます。
データの取得、表示ができました。
エラーを考慮して修正する準備 実は今の実装だと、トークンの有効期限が切れるとデータの取得できない問題があります。
とりあえず、トークンを更新する仕組みを実装します。
reflesh トークンを使用してトークンを更新してみる 2 通りの作成ができたので、両方記載します。 どちらかで実装すればいいです。
app/controllers/test_controller.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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 require 'rest-client' class TestController < ApplicationController def refresh strategy = OmniAuth::Strategies::Myapp .new('APP_SECRET' ,'APP_ID' ,:site => "http://localhost:5000" ) client = strategy.client your_expired_at_from_your_provider = Time .now.to_i hash = { access_token: current_user.token, refresh_token: current_user.refresh_token, expires_at: your_expired_at_from_your_provider, } access_token_object = OAuth2 : :AccessToken .from_hash(client, hash) refresh = access_token_object.refresh! current_user.token = refresh.token current_user.refresh_token = refresh.refresh_token current_user.save end def refresh2 client = OAuth2::Client .new('APP_ID' ,'APP_SECRET' ,:site => "http://localhost:5000" ) your_expired_at_from_your_provider = Time .now.to_i hash = { access_token: current_user.token, refresh_token: current_user.refresh_token, expires_at: your_expired_at_from_your_provider, } access_token_object = OAuth2 : :AccessToken .from_hash(client, hash) refresh = access_token_object.refresh! current_user.token = refresh.token current_user.refresh_token = refresh.refresh_token current_user.save end end
APP_ID
,APP_SECRET
は、config/initializers/devise.rb
に書いてあるのですが、こちらから取得ができなかったので、べた書きします。 確認してゆくなかで 2 つのパターンでAPP_ID
,APP_SECRET
の渡す順番が逆転していることがわかりました。 ライブラリがどこで入れ替えているのか調べてみたんですがはっきりせず。 それどころかAPP_SECRET
は ''
で実行も可能でした。(何故だろう?)
作成できたら、ルーティングを追加します。
config/routes.rb 1 2 3 4 5 6 7 8 Rails .application.routes.draw do devise_for :users , :controllers => { :omniauth_callbacks => "omniauth_callbacks" } get 'index' , to: 'test#index' get 'refresh' , to: 'test#refresh' root to: 'test#index' end
続けてapp/views/test/refresh.html.erb
を作成しておきます。 エラーを出さないだけが目的なので、中身は空です。
/refresh
にアクセスすると、users テーブルの token と refresh_token が更新されます。 DB を直接参照して確認しましょう。
token が更新されたので、再度/index
にアクセスするとデータの取得・更新ができます。
トークンの更新ができました。
エラーを考慮したデータ取得を再度実装 トークンの更新ができたので、今度はデータ取得と関連付けて実装します。
app/controllers/test_controller.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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 require 'rest-client' class TestController < ApplicationController before_action :authenticate_user! def index token = current_user.token url = 'http://localhost:5000/api/data' fail_count = 0 res=nil loop{ begin res = RestClient .get url, { :Authorization => "bearer #{token} " , content_type: :json } break ; rescue =>e fail_count += 1 refresh_token token = current_user.token return nil if fail_count >5 end } @res = JSON .parse(res.body) end :private def refresh_token strategy = OmniAuth::Strategies::Myapp .new('APP_SECRET' ,'APP_ID' ,:site => "http://localhost:5000" ) client = strategy.client your_expired_at_from_your_provider = Time .now.to_i hash = { access_token: current_user.token, refresh_token: current_user.refresh_token, expires_at: your_expired_at_from_your_provider, } access_token_object = OAuth2 : :AccessToken .from_hash(client, hash) refresh = access_token_object.refresh! current_user.token = refresh.token current_user.refresh_token = refresh.refresh_token current_user.save end end
データ取得処理でエラーがあった時、トークンを更新する処理を行います。 5 回失敗した場合を想定し、.erb
も編集しておきます。 以下のように修正します。
app/views/test/index.html.erb 1 2 3 4 5 6 <% if @res .present? %> <%= @res %><br /> <%= @res .inspect %><br /> <%= @res ["a" ] %><br /> <%= @res ["b" ] %><br /> <% end %>
こちらの実装ができたら動作確認します。/index
にアクセスすると、データを取得・表示できます。
OAuth で認証し、データの取得・トークンの更新を実装できました。 今回は固定の値を取得しています。 本来のところならユーザーのトークンで認証してもいるので、ユーザーの関連データを取得できるようにするとよさそうです。
ではでは。