Rails API Twitter認証(devise token auth)
環境
・Rails 6.0.0(APIモード)
・devise token auth 1.1.3
REST API作る前にログイン機能実装したほうがのちのち楽かなと思いまして、こっちを優先しました
ここでやっていくのが、Twitter認証と管理者とユーザーのアクセス制御です
メール認証はいろいろと面倒だったのでやめました
今回認証はTwitterだけですが他にFacebookとかGoogleとかLINEとか色々ありますが基本パズガイジはTwitterやってるのでほかはいらないかもですね
まあ、ライブラリに頼るので追加も楽でしょう
Railsのログイン機能といえばdeviseが有名ですがそれのトークン認証に特化したdevise_token_authというgemがあるらしいのでこれつかっていきます
主に公式ドキュメントを参考にしました
ざっと実装手順
基本的に上の見ればいいのですがつまづいたとこもあるのでメモ程度に実装手順かいていきます
ちなみにUserモデルは作ってないところからのスタートです
gemの追加
Gemfileに以下追加してbundle install
# Gemfile
gem 'devise_token_auth'
gem 'omniauth-twitter'
トークン認証用のファイル生成
rails g devise_token_auth:install User auth
Deviseで何使うか
# app/models/user.rb
# frozen_string_literal: true
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable,
:registerable,
:recoverable,
:rememberable,
:trackable,
:validatable,
:omniauthable # ←こいつ
include DeviseTokenAuth::Concerns::User
end
今回、OAuth認証がいるのでomniauthableを追加しました
注意点として「include DeviseTokenAuth::Concerns::User」より後にかくとダメみたいです
マイグレートファイルの修正
# db/migrate/xxxxxxxxxxx_devise_token_auth_create_users.rb
...
## User Info 追加
t.boolean :is_admin, default: false
...
## 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
管理者と権限分けたいのでis_admimカラムを追加しました。
それと、ユーザー分析に便利なTrackableが↑をみるにデフォルトで有効になってますが、必要なカラムが書き込まれていないので、↑の:trackableをけすか、カラムを追加する必要がありました。これバグなのでしょうか?
あと、メール認証しないのでConfirmableに必要なカラムはコメントアウトしてます(こっちはデフォルトで無効なのに・・・)
マイグレーション
rake db:migrate
ここでエラーが出ました
NoMethodError: undefined method `devise' for User (call 'User.connection' to establish a connection):Class
Userモデルにextend Devise::Modelsを追記すればいいらしい。なんでかは不明。再度マイグレーションしたら成功しました
# app/models/user.rb
# frozen_string_literal: true
class User < ActiveRecord::Base
extend Devise::Models # ←こいつ
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable,
:registerable,
:recoverable,
:rememberable,
:trackable,
:validatable,
:omniauthable
include DeviseTokenAuth::Concerns::User
end
devise token authの設定
# config/initializers/devise_token_auth.rb
# frozen_string_literal: true
DeviseTokenAuth.setup do |config|
config.change_headers_on_each_request = false
end
change_headers_on_each_requestをfalseにしたのはリクエストのたびにトークンを変えなくするためです
テストでトークンを流用したいので一時的にfalseに。テスト終わったらtrueにします
Twitter認証の設定
# config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :twitter, 'key', 'secret'
end
'key'と'secret'の場所にTwitterのダッシュボードにかいてあるやついれます。このへんのキーはハードコーディングしないほうがいいので環境変数から読み込んどいたほうがいいですね
それとTwitter認証後のコールバックURLをhttp://~~~/omniauth/twitter/callbackに設定してあげます。
CORS設定
# config/application.rb
config.middleware.use 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
別ドメイン間許可の設定です。開発時は全許可でいいとおもうのでドキュメントからコピペしました。ここで重要なのはexposeのとこでしょう。
セッション・クッキー設定
# config/application.rb
config.session_store :cookie_store, key: '_session_mechaco'
config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore, config.session_options
railsをAPIモードで作るとセッション機能が無効化されているので、有効にする必要がありました。とりあえずファイルストアでセッション管理する感じです
Twitter認証してみる
さっそくTwitter認証でユーザー作ってみます。
/auth/twitter にアクセスするとアプリ認証画面が出てきて許可するとアカウント作成される予定でしたが「ActiveModel::ForbiddenAttributesError」というエラーがでました
原因はStrongParametersにひっかかってるみたいです
対処方法としてまずomniauthのコールバックを処理するコントローラをオーバーライドするので、config/route.rbをかきかえます
# config/route.rb
# Before
# mount_devise_token_auth_for 'User', at: 'auth'
# After
mount_devise_token_auth_for 'User', at: 'auth', controllers: {
omniauth_callbacks: 'overrides/omniauth_callbacks'
}
オーバーライド先のファイルを作ります
# app/controllers/concerns/overrides/omniauth_callbacks_controller.rb
module Overrides
class OmniauthCallbacksController < DeviseTokenAuth::OmniauthCallbacksController
def redirect_callbacks
super
end
def omniauth_success
super
end
def omniauth_failure
super
end
protected
def assign_provider_attrs(user, auth_hash)
case auth_hash['provider']
when 'twitter'
user.assign_attributes({
nickname: auth_hash['info']['nickname'],
name: auth_hash['info']['name'],
image: auth_hash['info']['image'],
email: auth_hash['info']['email']
})
else
super
end
end
end
end
上記だとTwitterしか対応してませんのでFacebookとかも必要な場合case文の分岐増やして適宜attributeセットしてあげてください
あと、オーバーライドが弊害して、routingされるpublicメソッドはsuperで再定義してあげないといけないみたいです。これのせいでレスポンスヘッダーにトークンが格納されず、2時間くらいつまってました・・・
これで再度/auth/twitterへアクセス
Authorize appしたら無事アカウントも作成されTwitterで設定したhttp://~~~/omniauth/twitter/callbackへリダイレクトしてくれました
ログやDBみるとnameにTwitterID、nicknameにTwitter表示名、imageにアイコンURLが格納されています。
で、レスポンスヘッダーにaccess-token、uid、clientが格納されていて、これを次のリクエストから使ってあげると認証できます
ヘッダーの確認はChrome developerツールのNetworkタブでみれます
ログイン制御を試す
「authenticate_user!」でログイン制御が行えます
ログインしてないとアクセスできないパスを簡単に作りました
# app/controller/auth_tests_controller.rb
class AuthTestsController < ApplicationController
before_action :authenticate_user!, only: :index
def index
render json: current_user
end
end
# config/routes.rb
# 追記
get '/auth_test', to: 'auth_tests#index'
で、/auth_test にGETでリクエスト送ります。ヘッダーには↑↑↑でとれたaccess-token、uid、clientをセットしてあげます。
こんな感じのレスポンスがきてればちゃんと認証されています。
{
"id": 1,
"provider": "twitter",
"uid": "00000000000000000000",
"allow_password_change": false,
"name": "osufu",
"nickname": "osufu",
"image": "http://~~~~.png",
"email": null,
"is_admin": false,
"created_at": "0000-00-00T00:00:00.000+09:00",
"updated_at": "0000-00-00T00:00:00.000+09:00"
}
今度はトークンを間違えたりヘッダーにセットしないでリクエスト送ってみます。
{
"errors": [
"You need to sign in or sign up before continuing."
]
}
エラーが出てくれました。
管理者の制御
次はログインできていてなおかつ管理者権限のある人だけがアクセスできるようにします。
# app/controller/application_controller.rb
class ApplicationController < ActionController::API
include DeviseTokenAuth::Concerns::SetUserByToken
protected
def admin_user!
unless user_signed_in? && current_user.is_admin
render json: { message: 'not found' }, status: 404
return
end
end
end
# app/controller/auth_tests_controller.rb
class AuthTestsController < ApplicationController
before_action :authenticate_user!, only: :index
before_action :admin_user!, only: :admin_only
def index
render json: current_user
end
def admin_only
render json: { message: 'You are admin!' }
end
end
# config/routes.rb
# 追記
get '/auth_test_admin', to: 'auth_tests#admin_only'
「admin_user!」で管理者制御するようにしました。
まださっき作ったユーザーに管理者権限を持たせていないので、正常なトークンをいれて/auth_test_adminにアクセスしても以下のようなエラーが返されるようになります
{
"message": "not found"
}
一応、管理者が使うAPIと推測されたくないため401じゃなくて404エラーにしています。
管理者権限を持たせてみます。DB直接いじるかrails cでUser.find(1).update(is_admin: true)をうつでもよいです。
で、アクセスすると
{
"message": "You are admin!"
}
管理者としてみなされました。
結構バグ?らしきものがあって、かつ参考記事も少なかったので調査に時間がかかりました。自前でトークン認証実装するより断然楽なので良かったですが、予想より面倒でした。
それでは👋😉
この記事が気に入ったらサポートをしてみませんか?