LoginSignup
21
23

More than 3 years have passed since last update.

【Ruby on Rails】devise_token_authでTwitterログイン機能の実装

Last updated at Posted at 2020-01-08

前回の記事

DockerでRuby on Rails + Reactを別々にアプリ作成する環境構築手順

やったこと

Rails(APIモード)でdevise_token_authを用いたTwitterログイン機能の実装

参考記事

この記事を大変参考にさせていただきました。
Rails 5 API + Vue.js + devise_token_authでTwitterと連携するSPAを作る(①RAILS編)

実装手順

Gemfileの追記

モジュールインストールのためにGemfileに追記

Gemfile
###auth/Twitterログイン
gem 'omniauth'
gem 'devise'
gem 'devise_token_auth'
gem 'rack-cors'
gem 'omniauth-twitter'
gem 'dotenv-rails'
$ docker-compose run api bundle install
$ docker-compose run api bundle update

devise_token_authのインストール

$ docker-compose run api rails g devise_token_auth:install User auth

エラー吐きました。

Starting XXX_db_1 ... done
Traceback (most recent call last):
    22: from /usr/local/bundle/gems/spring-2.1.0/bin/spring:49:in `<main>'
    21: from /usr/local/bundle/gems/spring-2.1.0/lib/spring/client.rb:30:in `run'
    20: from /usr/local/bundle/gems/spring-2.1.0/lib/spring/client/command.rb:7:in `call'
    19: from /usr/local/bundle/gems/spring-2.1.0/lib/spring/client/server.rb:9:in `call'
    18: fro
/usr/local/lib/ruby/site_ruby/2.5.0/bundler/spec_set.rb:91:in `block in materialize': Could not find i18n-1.7.1 in any of the sources (Bundler::GemNotFound)

モジュール(Gemfileに記載したやつ)がインストールできていなかったようです。
結構苦戦したけど、これで解決。
https://fuqda.hatenablog.com/entry/2019/03/21/204118

$ docker-compose build --no-cache

migrationファイルの編集

db/migrate/XXX_devise_token_auth_create_users.rb
class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[5.2]
  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
      t.boolean  :allow_password_change, :default => false

      ## 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.inet     :current_sign_in_ip
      t.inet     :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
$ docker-compose run api rails db:migrate
Starting XXX_db_1 ... done
rails aborted!
NoMethodError: undefined method `devise' for User (call 'User.connection' to establish a connection):Class
/usr/local/bundle/gems/activerecord-5.2.4.1/lib/active_record/dynamic_matchers.rb:22:in `method_missing'

エラーです。deviseがインストールされていないっぽい。
これで解決する。https://qiita.com/penguin_note/items/93b00c09c0da1f1a0eab
1, route.rbでdevise_auth_tokenの記述をコメントアウト。
2,

$ docker-compose run api rails d model user 
$ docker-compose run api rails g devise:install
$ docker-compose run api rails g devise_token_auth:install User auth
Starting XXX_db_1 ... done
Running via Spring preloader in process 28
   identical  config/initializers/devise_token_auth.rb
     skipped  Concern is already included in the application controller.
     skipped  Routes already exist for User at auth
     skipped  Migration 'devise_token_auth_create_users' already exists
      create  app/models/user.rb
$ docker-compose run api rails db:migrate 

上手くいった!

$ docker-compose run api rails g controller api/v1/auth/registrations

initializerの設定

config/initializers/devise_token_auth.rb

devise_token_auth.rb
config.change_headers_on_each_request = false

config.token_lifespan = 2.weeks

  config.headers_names = {:'access-token' => 'access-token',
                         :'client' => 'client',
                         :'expiry' => 'expiry',
                         :'uid' => 'uid',
                         :'token-type' => 'token-type' }

config/initializers/omniauth.rbを"作成する"

omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, ENV.fetch("TWITTER_KEY"), ENV.fetch("TWITTER_SECRET"), callback_url: "http://127.0.0.1:3000/omniauth/twitter/callback"
end

application_controller.rbの編集

application_controller.rb
class ApplicationController < ActionController::Base
  include DeviseTokenAuth::Concerns::SetUserByToken
  protect_from_forgery
  before_action :skip_session
  skip_before_action :verify_authenticity_token, if: :devise_controller? 

  protected
    def skip_session
      request.session_options[:skip] = true
    end
end

config/application.rbの編集

config/application.rb
require_relative 'boot'

require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "action_cable/engine"
# require "sprockets/railtie"
require "rails/test_unit/railtie"

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module Myapp
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.

    # Only loads a smaller set of middleware suitable for API only apps.
    # Middleware like session, flash, cookies can be added back manually.
    # Skip views, helpers and assets when generating a new resource.
    config.api_only = true
    config.session_store :cookie_store, key: '_interslice_session'
    config.middleware.use ActionDispatch::Cookies # Required for all session management
    config.middleware.use ActionDispatch::Session::CookieStore, config.session_options
    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

TwitterのAPP KEYとAPP SECRETを.envに記載

Twitter Developerに登録し、appを作成し、Consumer API Keyを取得します。
先程インストールしたdotenvを用いて環境変数を管理します。

.env
TWITTER_KEY="API Key"
TWITTER_SECRET="API secret key"

モデルの編集

models/user.rb

user.rb
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, omniauth_providers: [:twitter]
  include DeviseTokenAuth::Concerns::User
end

route.rbの編集

route.rb
Rails.application.routes.draw do
 namespace :api, defaults: { format: :json } do
    namespace :v1 do
      mount_devise_token_auth_for 'User', at: 'auth', controllers: {
          omniauth_callbacks: 'api/v1/auth/omniauth_callbacks'
      }
    end
 end
 root 'home#about'
end

omniauth_callbacks_controllerの作成

$ docker-compose run api rails g controller api/v1/auth/omniauth_callbacks

参考記事のコードをほぼそのまま使わせていただきました。credentialsは使わないので、コメントアウトしました。

omniauth_callbacks_controller.rb
module Api
  module V1
    module Auth
      class OmniauthCallbacksController < DeviseTokenAuth::OmniauthCallbacksController
        skip_before_action :skip_session

        def redirect_callbacks
          super
        end
        def omniauth_success
          super
          update_auth_header
        end
        def omniauth_failure
          super
        end

        protected
          def get_resource_from_auth_hash
            super
             # @resource.credentials = auth_hash["credentials"]
            clean_resource
          end

          def render_data_or_redirect(message, data, user_data = {})
            if Rails.env.production?
              if ['inAppBrowser', 'newWindow'].include?(omniauth_window_type)
                render_data(message, user_data.merge(data))
              elsif auth_origin_url
                redirect_to DeviseTokenAuth::Url.generate(auth_origin_url, data.merge(blank: true))
              else
                fallback_render data[:error] || 'An error occurred'
              end
            else
              # @resource.credentials = auth_hash["credentials"]

              # わかりやすい様に開発時はjsonとして結果を返す
              render json: @resource, status: :ok
            end
          end

          def clean_resource
            @resource.name = strip_emoji(@resource.name)
            @resource.nickname = strip_emoji(@resource.nickname)
          end
          def strip_emoji(str)
            str.encode('SJIS', 'UTF-8', invalid: :replace, undef: :replace, replace: '').encode('UTF-8')
          end
      end
    end
  end
end

実際にアクセスしてみる。

http://127.0.0.1:3000/api/v1/auth/twitter
スクリーンショット 2020-01-08 22.17.06.png

jsonが返ってくる。

{"id":1,"provider":"twitter","uid":"XXXXXXX","allow_password_change":false,"name":"締切は10年後","nickname":"dl10yr","image":"http://pbs.twimg.com/profile_images/XXXX.jpg","email":null,"created_at":"2020-01-08T13:12:51.296Z","updated_at":"2020-01-08T13:18:43.661Z"}

ヘッダーの情報(uid、client、access-token)を使って、いろいろする予定です。
スクリーンショット 2020-01-08 22.45.50.png

データベースを確認してみる

$ docker-compose run api rails dbconsole
  XXX_development=# select * from users;

Twitterアカウントの情報がusersテーブルに保存されているはず。

メールアドレスを用いたサインアップ(登録)、サインイン(ログイン)の実装

ReactでAPIで使うために、Unpermitted Parameters問題を解決しないといけない。

config/initializers/wrap_parameters.rb
ActiveSupport.on_load(:action_controller) do
  # wrap_parameters format: [:json]
  wrap_parameters format: []

end

こっちは不要かも。とりあえずやってみた。

application_controller.rb
class ApplicationController < ActionController::Base
  include DeviseTokenAuth::Concerns::SetUserByToken
  protect_from_forgery
  before_action :skip_session
  skip_before_action :verify_authenticity_token, if: :devise_controller?, raise: false
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected
    def skip_session
      request.session_options[:skip] = true
    end
    def configure_permitted_parameters
      devise_parameter_sanitizer.permit(:sign_up, keys: [:email, :password])
      devise_parameter_sanitizer.permit(:sign_in, keys: [:email, :password])
    end
end
21
23
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
23