Railsアプリに簡単なTwitterログインを実装する


簡単なTwitterログインを実装したので、方法をまとめていきます。
ログイン方法はTwitter連携だけなので、devise等は利用していません。

仕様は以下の通りです。

  • ログイン方法はTwitterのみ(メールアドレスでのログインは出来ない)
  • ログインボタンをクリックすると、Twitterの認証画面に遷移する
  • 初めてログインする場合にはusersテーブルにレコードを作成、2回目以降の場合には既存のデータを読み込む
  • ログイン後は、sessionが開始する

アプリはすでにあって、トップページ等の設定は出来ている前提で話を進めていきます。

API Key、API Secret Keyを取得

Twitterと連携するためには、TwitterのAPIキーとAPIシークレットキーが必要です。以下の手順で取得しましょう。

アカウント取得

まずは以下の記事等を参考に、開発者アカウントを取得しましょう。
Twitter API 登録 (アカウント申請方法) から承認されるまでの手順まとめ ※2019年8月時点の情報
2019年12月29日に申請を出したところ、即時承認されました。待ち時間0秒でした。

アプリ作成

アカウント取得、ログイン後
https://developer.twitter.com/en/apps/create
よりアプリを作成します。

Callback URLsには
http:localhost:3000/auth/twitter/callback
http:0.0.0.0:3000/auth/twitter/callback
http:<本番環境のドメイン>/auth/twitter/callback
https:<本番環境のドメイン>/auth/twitter/callback
の4つを指定します。
最低2つ設定されていないと動かない仕様になっているようです。

他は基本的にはそれっぽいことを書いておけばOKです。
また設定は後から自由に変更できます。

キーを取得

アプリ一覧画面
https://developer.twitter.com/en/apps
から作成したアプリ右の「Details」ボタンをクリック→「Keys and tokens」タブからAPIキーとAPIシークレットキーが取得出来ます。

必要なgemのセットアップ

gemのインストールと、各種設定を行います。

dotenv-rails

APIキーとAPIシークレットキーを環境変数に保存するためにdotenv-railsを導入します。

Gemfile
gem 'dotenv-rails'
$ bundle install

プロジェクトルートに.envファイルを作成し、上記で取得した各種キーを書き込みます。

TWITTER_API_KEY = hogehoge
TWITTER_API_SECRET = foobarbaz

omniauth-twitter

Twitter連携のためのgemomniauth-twitterを導入します。

Gemfile
gem 'omniauth-twitter'
$ bundle install

config/initializers/omniauth.rbを作成し、APIキーとAPIシークレットキーを書き込みます。

config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, ENV['TWITTER_API_KEY'], ENV['TWITTER_API_SECRET']
end

モデル、データベースを作成

TwitterのID、ニックネーム、ユーザー名、画像パスを保存するためのデータベースと関連するモデルを作成します。
上記は全てomniauth-twitterで取得できる値です。その他の値についてはomniauth-twitterのREADMEをご覧ください。

$ rails g model User uid:string nickname:string name:string image:string
$ rails db:migrate

セッション管理用のヘルパーメソッドを作成

app/helpers/sessions_helper.rb
module SessionsHelper
  # 渡されたユーザーでログインする
  def log_in(user)
    session[:uid] = user.uid
  end

  # 現在ログイン中のユーザーを返す (いる場合)
  def current_user
    if session[:uid]
      @current_user ||= User.find_by(uid: session[:uid])
    end
  end

  # ユーザーがログインしていればtrue、その他ならfalseを返す
  def logged_in?
    !current_user.nil?
  end

  # 現在のユーザーをログアウトする
  def log_out
    session.delete(:uid)
    @current_user = nil
  end
end

ログイン、ログアウト処理を実装

$ rails g controller Sessions
app/controllers/sessions_controllers.rb
class SessionsController < ApplicationController
  def create
    unless request.env['omniauth.auth'][:uid]
      flash[:danger] = '連携に失敗しました'
      redirect_to root_url
    end
    user_data = request.env['omniauth.auth']
    user = User.find_by(uid: user_data[:uid])
    if user
      log_in user
      flash[:success] = 'ログインしました'
      redirect_to root_url
    else
      new_user = User.new(
        uid: user_data[:uid],
        nickname: user_data[:info][:nickname],
        name: user_data[:info][:name],
        image: user_data[:info][:image],
      )
      if new_user.save
        log_in new_user
        flash[:success] = 'ユーザー登録成功'
      else
        flash[:danger] = '予期せぬエラーが発生しました'
      end
      redirect_to root_url
    end
  end


  def destroy
    log_out if logged_in?
    flash[:success] = 'ログアウトしました'
    redirect_to root_url
  end
end

ログイン、ログアウト用のリンクを追加

app/views/layouts/_header.html.erb
<header class="header">
.
.
  <% if logged_in? %>
    <%= link_to 'ログアウト', logout_path %>
  <% else %>
    <a href="/auth/twitter">ログイン</a>
  <% end %>
.
.
</header>

ルーティングを設定

config/routes.rb
Rails.application.routes.draw do
.
.
  get '/auth/:provider/callback', to: 'sessions#create'
  get '/logout', to: 'sessions#destroy'
end

omniauth-twitterの仕様で、/auth/twitter/callback/auth/:provider/callbackに変換されます。

ログイン状態の振る舞いのテスト

今回、session[:uid]にユーザーidを代入形でログイン処理を実装しました。
RSpecでログイン状態のユーザーを作る時にもsession[:uid] = user.uidと書きたいところですが、
RSpecのfeature spec及びsystem specではデフォルトでsessionメソッドが使えません。
そこでrack_session_accessをインストールして使えるようにします。

Gemfile
group :test do
# 省略
  gem 'rack_session_access'
end
$ bundle install
config/environments/test.rb
Rails.application.configure do
# 省略
  config.middleware.use RackSessionAccess::Middleware
spec/spec_helper.rb
RSpec.configure do |config|
# 省略
  require 'rack_session_access/capybara'
end
spec/systems/sample_spec.rb
it 'ログイン' do
  visit root_path
  expect(page).to_not have_content 'マイページ' # ログイン前はマイページという表示が無い
  page.set_rack_session(uid: user.uid) # ログイン
  visit root_path
  expect(page).to have_content 'マイページ' # マイページと表示されている
end

参考