ログイン処理をOmniauth認証のみで行なう必要があるので最低限ログインが出来る部分までを試したときのことを忘れないために投稿
omniauthで使うのはGoogle/Twitter/Doorkeeper
環境はCentOS6.9とPostgreSQL9系
これはRails5(API)だけど実際にRailsでSPAを作るなら、Rails5のAPIモードとGrapeのどっちがいいんだろう
プロジェクト作成
$ rails new Sample -d postgresql -B -T --api
使うgemは以下のとおり
gem 'rack-cors'
gem 'devise'
gem 'devise_token_auth'
gem 'omniauth'
gem 'omniauth-oauth2'
gem 'omniauth-google-oauth2'
gem 'omniauth-twitter'
インストールしたらdatabase.ymlを編集してDBの作成をしておく
devise_token_authのインストールと準備
$ rails g devise_token_auth:install User auth
生成されるmigrationファイルを以下のように編集して$ rails db:migrate
class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[5.1]
def change
create_table(:users) do |t|
## Required
t.string :provider, :null => false, :default => "email"
t.string :uid, :null => false, :default => ""
## Database authenticatable
t.string :encrypted_password, :null => false, :default => ""
## Recoverable
#t.string :reset_password_token
#t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, :default => 0, :null => false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
#t.string :confirmation_token
#t.datetime :confirmed_at
#t.datetime :confirmation_sent_at
#t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, :default => 0, :null => false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
## User Info
t.string :name
t.string :nickname
t.string :image
t.string :email
## Tokens
t.json :tokens
t.timestamps
end
add_index :users, :email, unique: true
add_index :users, [:uid, :provider], unique: true
#add_index :users, :reset_password_token, unique: true
#add_index :users, :confirmation_token, unique: true
#add_index :users, :unlock_token, unique: true
end
end
モデルは以下のように編集
:rememberable
は不要な気もする
class User < ActiveRecord::Base
# Include default devise modules.
devise :rememberable, :omniauthable
include DeviseTokenAuth::Concerns::User
end
これでomniauthを使ったログインのみになる
$ rails routes
Prefix Verb URI Pattern Controller#Action
auth_validate_token GET /auth/validate_token(.:format) devise_token_auth/token_validations#validate_token
auth_failure GET /auth/failure(.:format) users/omniauth_callbacks#omniauth_failure
GET /auth/:provider/callback(.:format) users/omniauth_callbacks#omniauth_success
GET|POST /omniauth/:provider/callback(.:format) users/omniauth_callbacks#redirect_callbacks
omniauth_failure GET|POST /omniauth/failure(.:format) users/omniauth_callbacks#omniauth_failure
GET /auth/:provider(.:format) redirect(301)
googleやtwitterでログインする際に呼び出されるomniauth_callbacks_controller.rb
の一部をオーバーライド
今回はお試しなのでユーザ情報の保存に成功したらその情報をjsonで返す。
本来ならrender_data_or_redirect()
を使うのが正しいやり方。
以下は変更したメソッドのみ抜粋
module Users
class OmniauthCallbacksController < DeviseTokenAuth::OmniauthCallbacksController
include Devise::Controllers::Rememberable
def omniauth_success
get_resource_from_auth_hash
create_token_info
set_token_on_resource
create_auth_params
# ここは使わないのでコメントアウト
#if resource_class.devise_modules.include?(:confirmable)
# # don't send confirmation email!!!
# @resource.skip_confirmation!
#end
sign_in(:user, @resource, store: false, bypass: false)
# 動作確認用にユーザ情報を保存できたらjsonをそのまま返す処理
if @resource.save!
# update_token_authをつけることでレスポンスヘッダーに認証情報を付与できる。
update_auth_header
yield @resource if block_given?
render json: @resource, status: :ok
else
render json: { message: "failed to login" }, status: 500
end
# 本実装時はこちらを使用する
# @resource.save!
#
# update_auth_header # これは自分で追加する
# yield @resource if block_given?
#
# render_data_or_redirect('deliverCredentials', @auth_params.as_json, @resource.as_json)
end
protected
def get_resource_from_auth_hash
# find or create user by provider and provider uid
@resource = resource_class.where({
uid: auth_hash['uid'],
provider: auth_hash['provider']
}).first_or_initialize
if @resource.new_record?
@oauth_registration = true
# これが呼ばれるとエラーになるのでコメントアウトする
#set_random_password
end
# sync user info with provider, update/generate auth token
assign_provider_attrs(@resource, auth_hash)
# assign any additional (whitelisted) attributes
extra_params = whitelisted_params
@resource.assign_attributes(extra_params) if extra_params
@resource
end
end
end
上記の処理を呼び出すようにするためroutes.rbを編集
Rails.application.routes.draw do
mount_devise_token_auth_for 'User', at: 'auth', controllers: { omniauth_callbacks: "users/omniauth_callbacks" }
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
initializerは以下のようにする
DeviseTokenAuth.setup do |config|
config.change_headers_on_each_request = false
config.token_lifespan = 1.month
config.headers_names = {:'access-token' => 'access-token',
:'client' => 'client',
:'expiry' => 'expiry',
:'uid' => 'uid',
:'token-type' => 'token-type' }
end
Omniauthの準備
application.rbに以下を追加
これをしないとAPIモードではエラーになる。
corsはクライアントとのやり取りのために追加
module Sample
class Application < Rails::Application
config.load_defaults 5.1
config.api_only = true
# 主にdeviseを使うのに必要
config.middleware.use Rack::MethodOverride
config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore
config.middleware.use ActionDispatch::Flash
# クロスドメイン対策は入れておいたほうが良い
config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*',
:headers => :any,
:expose => ['access-token', 'expiry', 'token-type', 'uid', 'client'],
:methods => [:get, :post, :options, :delete, :put]
end
end
end
end
各サービスの設定をomniauth.rbに記載
App_IDとSecret_keyについてはomniauth.ymlを作成して格納
取得方法については省略
もちろん環境変数に入れておいてそれを読み取る形でも問題ない
require File.expand_path('lib/omniauth/strategies/doorkeeper', Rails.root)
Rails.application.config.middleware.use OmniAuth::Builder do
OAUTH_CONFIG = YAML.load_file("#{Rails.root}/config/omniauth.yml")[Rails.env].symbolize_keys!
provider :doorkeeper, OAUTH_CONFIG[:doorkeeper]['key'], OAUTH_CONFIG[:doorkeeper]['secret']
provider :google_oauth2, OAUTH_CONFIG[:google]['key'], OAUTH_CONFIG[:google]['secret'], name: :google, scope: %w(email)
provider :twitter, OAUTH_CONFIG[:twitter]['key'], OAUTH_CONFIG[:twitter]['secret]
end
production: &production
doorkeeper:
key: XXXXXXXXXXXXXXXXXXXXXXXXXX
secret: XXXXXXXXXXXXXXXXXXXXXXXX
google:
key: XXXXXXXXXXXXXXXXXX
secret: XXXXXXXXXXXXXXXXXXXx
twitter:
key: XXXXXXXXXXXXXXx
secret: XXXXXXXXXXXXXXXXXXX
development: &development
<<: *production
test:
<<: *development
SiteURLとCallbackURLはそれぞれの開発者向けのサービスで以下のように設定
またGoogleはIPアドレスでは登録できず、twitterはIPアドレスじゃないと登録できない
twitterはIPアドレスとlocalhostどっちでも登録できる
site_url = http://localhost:3000
callback_url = http://localhost:3000/omniauth/google/callback
site_url = http://127.0.0.1:3000
callback_url = http://127.0.0.1:3000/omniauth/twitter/callback
site_url = http://localhost:3000
callback_url = http://localhost:3000/omniauth/doorkeeper/callback
lib配下にdoorkeeper用のstrategyを作成
require 'omniauth-oauth2'
module OmniAuth
module Strategies
class Doorkeeper < OmniAuth::Strategies::OAuth2
RAW_INFO_URL = 'api/v1/me'
option :name, :doorkeeper
option :client_options, {
site: 'doorkeeperのURL'
}
uid { raw_info['uid'] }
info do
{
email: raw_info['email'],
name: raw_info['name']
}
end
extra do
{ raw_info: raw_info }
end
def raw_info
@raw_info ||= JSON.parse(access_token.get(RAW_INFO_URL).response.body)
end
def callback_url
full_host + script_name + callback_path
end
end
end
end
動作確認
一通り設定できたはずなので$ rails s
でサーバを立ち上げて
ブラウザでhttp://localhost:3000/auth/googleと叩けば認証画面が表示されるので認証を行なうとDBに保存される
そしてResponseヘッダーにclient_idとuidとaccess_tokenとexpireが付与された状態で
DBに保存したユーザ情報がjsonで返ってくる。
本来は以下のように入力しないとだめ
http://localhost:3000/auth/google?omniauth_window_type=newWindow
え?クライアント側?知らない子ですね・・・
認証をキャンセルした場合の処理について【11/21追記】
facebookの認証も追加し、試しに「後で」をクリックしたらflashなんてメソッドねーぞ怒られた。
deviseのflashなんて使わないはずなのになぜ?と思ってログを見ると
・・・
Started GET "/omniauth/facebook?resource_class=User"
・・・
Processing by Devise::OmniauthCallbacksController#failure as HTML
・・・
DeviseのOmniauthCallbacksController#failureが呼ばれていた。
そこでomniauthのWikiにある以下の方法を試したけど反映されたようには見えず・・・
OmniAuth.config.on_failure = Proc.new { |env|
OmniAuth::FailureEndpoint.new(env).redirect_to_failure
}
改めて調べているとstackoverflowにこのような書き込みを見つけた。
Google先生でざっくり翻訳してみると
「devise-token-authはdevies上のレイヤーであり、deviseはomniauth上のレイヤーなので、どこかon_failureコードはすでにmonkeypatchedされています。したがって、再monkeypatchするには、after_initalizeブロックに設定コードを入れなければなりません」
つ、つまりどういうことだってばよ・・・?
モンキーパッチがどこかで適用されているので、on_failureを使うにはモンキーパッチを再度当てる必要があるってこと?
えぇ・・・
とりあえず調べていく中で見つけた以下の方法でうまくいった。
Rails.application.config.to_prepare do
Devise::OmniauthCallbacksController.class_eval do
def failure
# 認証をキャンセルした場合
render json: { message: "Login failed." }, status: 401
end
end
end
また/auth/:provider
というパスも/api/v1/auth/:provider
とかに変更してしまうと、
Devise(多分)が/omniauth/:provider/callbackを見つけらずエラーになるということもわかったのでなるべくモデル作成時に設定したパスは弄らないほうが良い。
参考
RailsでいろんなSNSとOAuth連携/ログインする方法
DoorkeeperとDeviseでOAuth2によるログイン機能を作る
devise token auth を使って簡単に早くAPIを作る
クライアントAngularJS サーバーサイドRails5 におけるOmniauth 認証を試してみる
OmniAuth OAuth2 1.4.0 以降で Invalid Credentials
エラー