【Rails】SorceryでTwitter認証【2022年12月版】

# 概要
ポートフォリオの作成で[Sorcery](https://github.com/Sorcery/sorcery)を使ったTwitterログイン認証を実装しました。

rails側は基本的には[公式のwiki](https://github.com/Sorcery/sorcery/wiki/External)に沿って進めれば問題ないのですが、ポイントで変更が必要で、Twitter Develpersの申請周りが変化していたので、2022年12月時点で成功した方法をキャプチャーを交えて記録します。

## 実行環境
この記事は以下環境で動作を確認しました。
Ruby 3.1.2
Rails 6.1.7

## 完成版のイメージ

<img src="https://gyazo.com/e9609f8fea2dae4f0c13a8d98dc68f42.gif">

ログイン後にプロフィールを確認するとTwitterのメールアドレス(email)、名前(name)、自己紹介(description)が取得できている状態をゴールとしました。
<img src="https://gyazo.com/dd899c2cacdacb73692f0cb64128cb1a.png">

## GitHub

https://github.com/gentaroooo/kariteyomu

## Twitter developerでの設定
### APPの作成
[Twitter developerツール](https://developer.twitter.com/en/apps)にログインします。
ログイン後右上の「Create an App」を押します。
<img src="https://gyazo.com/1d94660913afb423645658d7d8d57171.png">
次画面の選択項目はキャプチャーの通りに選択しました。
<img src="https://gyazo.com/6db3f9ff65befeca949aa0a8390637e5.png">

App nameにはアプリ名を入力します。Get Keyボタンを押すとAPIキーとAPIシークレットキーが取得できます。**後ほどrails側に設定するので控えて次に進みます。**
<img src="https://gyazo.com/0bdc9418f56c71d91bc32f05e2f737b2.png">

### User authentication settingsの編集
[ダッシュボード](https://developer.twitter.com/en/portal/dashboard)画面からAPPの設定ボタンを押します。

<img src="https://gyazo.com/f540c67841134194fbf729245c608444.png">

**ページ下の方にあるUser authentication settingsのEditを押します。**

<img src="https://gyazo.com/7100f373bde99662c189cce19df552f5.png">

選択項目はキャプチャーの通りに選択しました。
<img src="https://gyazo.com/74299f6fac3accb20157d26fbee45336.png">
Callback URI/URLはローカルと開発環境でそれぞれ設定します。
**ローカル環境でのアクセスはlocalhost:3000ではなく127.0.0.1:3000でアクセスしないと401 Authorization Requiredとなります。**[Twitterログイン 401 Authorization Required](https://qiita.com/niwa1903/items/2d6a3e9c103652d88538)
<img src="https://gyazo.com/a15e7013672a7817b7d81cdcabac4221.png">

### Elevated accessの申請
Elevatedを申請しないと「エラーコード453」が発生したので申請をします。
TweepyライブラリにはTwitter API v1.1が含まれており、Elevated accessを申し込む必要があるようです。
下記にアクセスする
https://developer.twitter.com/en/portal/products/elevated

Do you need Elevated access for your Project?の「Apply」をクリックして
各項目を埋めてNextをクリック
- **What country are you based in?**
    - 居住国を選択します
- **What's your current coding skill level?**
    - 自分のスキルレベルを選択します
- **Want updates?**
    - アップデート情報が必要な場合はチェックしてください

### 再度各項目を埋めてNextをクリック（英語で）
DeepLで翻訳すると楽です

- **How will you use the Twitter API or Twitter Data?**
    - TwitterのAPIやデータをどのように使用するかを200文字で記入します
    - APIを使用して解消したい課題やユーザーストーリなど大目的を詳細に記入します
- **Are you planning to analyze Twitter data?**
    - 取得したデータを分析する場合はYESを選択します
    - 分析する内容と手法または技術を100文字で詳細に記入します
- **Will your App use Tweet, Retweet, Like, Follow, or Direct Message functionality?**
    - ツイート、リツイート、いいね,フォロー、DMを使用する場合はYESを選択します
    - 何にどのような操作を行うのかを100文字で詳細に記入します
- **Do you plan to display Tweets or aggregate data about Twitter content outside Twitter?**
    - 政府機関関係でデータを表示する場合はYESを選択します
    - 表示する予定の政府機関をすべて記入します

**入力した内容を確認して、「Next」をクリック**
**利用規約を読み、チェックを入れて、「Submit」をクリック**

**Twitter Developerの登録、設定が完了しました！おつかれさまです！👏**
**続いてrails側を実装します。**

## authenticationsテーブルを作成
[Sorceryのwiki](https://github.com/Sorcery/sorcery/wiki/External)にチュートリアルがあるので、基本的にはそれに従っていきます。

```
$ rails g sorcery:install external --only-submodules
        gsub  config/initializers/sorcery.rb
      insert  app/models/user.rb
      create  db/migrate/20221104011735_sorcery_external.rb
```
コマンドを打つと、外部認証に必要なauthenticationsテーブルを作成するためのマイグレーションファイルが作られます。

user_idにindexを貼る記述を追加してマイグレーションを実行します。

```ruby:db/migrate/20221104011735_sorcery_external.rb
class SorceryExternal < ActiveRecord::Migration
  def change
    create_table :authentications do |t|
      t.integer :user_id, :null => false
      t.string :provider, :uid, :null => false

      t.timestamps
    end

    add_index :authentications, [:provider, :uid]
    add_index :authentications, :user_id # 追加
  end
end
```

```
rails db:migrate
```

## Authenticationモデルの設定
先ほど既にマイグレーションファイルは作成しているので、`--migration=false`オプションを付けています。
```
rails g model Authentication --migration=false
```

```ruby:app/models/authentication.rb
class Authentication < ActiveRecord::Base
  belongs_to :user
end
```

```ruby:app/model/user.rb
class User < ApplicationRecord
  authenticates_with_sorcery!
  has_many :authentications, dependent: :destroy
  accepts_nested_attributes_for :authentications # has_many :authenticationsより下に書く
end
```
## sorcery.rbの設定

```ruby:config/sorcery.rb
Rails.application.config.sorcery.submodules = [:external]
config.external_providers = [:twitter]
config.twitter.key = ENV['twitter_key']
config.twitter.secret = ENV['twitter_secret_key']
config.twitter.callback_url = Settings.sorcery[:callback_url]
config.twitter.user_info_path = "/1.1/account/verify_credentials.json?include_email=true"
config.twitter.user_info_mapping = {
email: 'email',
name: 'name',
introduction: 'description',
}
user.authentications_class = Authentication
```

- config.twitter.callback_url
環境ごとにurlが異なるので[gemのconfig](https://github.com/rubyconfig/config)を入れて環境毎に設定します。

```ruby:config/settings/development.yml
sorcery:
  callback_url: "http://127.0.0.1:3000/oauth/callback?
```
```ruby:config/settings/production.yml
sorcery:
  callback_url: "本番環境のURL"/oauth/callback?
```

- config.twitter.key & config.twitter.secret
 Twitter Developer ページのアプリ内「Keys and tokens」にある
「API key」と「API secret key」は[gemのdotenv-rails](https://github.com/bkeepers/dotenv)を導入して環境変数として設定しました。
```ruby:.env
twitter_key='Twitter Developer ページのAPI key'
twitter_secret_key='Twitter Developer ページのAPI secret key'
```
- config.twitter.user_info_mapping
取得したい値はメールアドレス、名前、自己紹介なので以下を設定しました。

```ruby
email: 'email',
name: 'name',
introduction: 'description'
```
## Oauth処理を行うコントローラを作成
```ruby:app/controllers/oauths_controller.rb
class OauthsController < ApplicationController
  skip_before_action :require_login
  def oauth
    login_at(auth_params[:provider])
  end

  def callback
    provider = auth_params[:provider]

    if auth_params[:denied].present?
      redirect_to root_path, success: ('Twitterアカウントでのアカウント作成に成功しました')
      return
    end

    begin
      create_user_from(provider) unless (@user = login_from(provider))
      redirect_to root_path, success: ('Twitterアカウントでのログインに成功しました')
 
    rescue StandardError
      redirect_to root_path, danger: ('Twitterアカウントでログインに失敗しました')
    end
  end

  private

  def auth_params
    params.permit(:code, :provider, :denied)
  end

  def create_user_from(provider)
    @user = create_from(provider)
    reset_session
    auto_login(@user)
  end
end
```
## ルーティングの追加

```ruby:routes.rb
post "oauth/callback", to: "oauths#callback"
get "oauth/callback", to: "oauths#callback"
get "oauth/:provider", to: "oauths#oauth", as: :auth_at_provider
```

## ログイン画面の追加

```ruby:app/views/user_sessions/new.html.erb
<%= link_to auth_at_provider_path(provider: :twitter), class: 'btn w-100 twitter' do %>
   <i class="bi bi-twitter"></i> Twitterでログイン
<% end %>
```

**以上でrails側の実装も終わりです！**