#前回の記事
DockerでRuby on Rails + Reactを別々にアプリ作成する環境構築手順
#やったこと
Rails(APIモード)でdevise_token_authを用いたTwitterログイン機能の実装
#参考記事
この記事を大変参考にさせていただきました。
Rails 5 API + Vue.js + devise_token_authでTwitterと連携するSPAを作る(①RAILS編)
#実装手順
###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ファイルの編集
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
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を"作成する"
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の編集
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の編集
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を用いて環境変数を管理します。
TWITTER_KEY="API Key"
TWITTER_SECRET="API secret key"
###モデルの編集
models/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の編集
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は使わないので、コメントアウトしました。
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
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)を使って、いろいろする予定です。
###データベースを確認してみる
$ docker-compose run api rails dbconsole
XXX_development=# select * from users;
Twitterアカウントの情報がusersテーブルに保存されているはず。
#メールアドレスを用いたサインアップ(登録)、サインイン(ログイン)の実装
ReactでAPIで使うために、Unpermitted Parameters問題を解決しないといけない。
ActiveSupport.on_load(:action_controller) do
# wrap_parameters format: [:json]
wrap_parameters format: []
end
こっちは不要かも。とりあえずやってみた。
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