【Ruby on Rails】DeviseなしのUserモデルにOauth認証を追加する方法

#Oauth認証とは
Open Authorization 認証の略です。読み方は「オーオース」らしい。
ざっくりいうとTwitterやFacebookのアカウント情報でログインできるようになります。
アイコン画像も取ってこれるので、ユーザーとしては登録の手間がかなり省けて便利です。

#まずはAPIを取得する必要がある。
[こちら](https://blog.nanahako.net/programming/twitter-developer)を参考にDeveloperとして登録します。しかし、**Describe in your own words what you are building**の欄のみリンクと違い、4つの質問に一つ一つ答えていく形式に変わっています。(2019年4月10日現在)

![developer3.png](https://qiita-image-store.s3.amazonaws.com/0/300925/77eaba12-f847-1432-a7d8-dc09fef50c3e.png)

大まかに日本語に訳すと、以下の事柄について聞かれています。
すべて合わせて、300文字以上書く必要があります。

1.どのような目的やケースでツイッターのAPIを使うのか
2.ツイートやユーザーなどを分析するつもりかどうか。もしするなら、その方法や技術と詳細を記述。
3.ツイート、リツイート、リンクを含んだ使い方をするかどうか。もしするなら、ユーザーやコンテンツをやりとりする方法を記述。
4.どのようにツイッターの情報が表示されるのか。もしツイッターからコンテンツを表示するなら、アプリのどこにどのように表示されるのかを記述（それぞれのツイートやツイッターのコンテンツが表示されるのか、それともまとめた情報が表示されるのか）

Developerの登録が終わったら、[こちら](https://apps.twitter.com)にいき、右上の「Create an app」からAPIを使うアプリを登録します。
「App name」や「Website URL」は特に問題ないですが、Callback URLsに関してはURLが2つ以上登録されていないと正しく動作しません。
ローカル環境の場合は以下の2つのURLを登録します。

http://127.0.0.1:3000/auth/twitter/callback
http://localhost:3000/auth/twitter/callback

![twittercallback.png](https://qiita-image-store.s3.amazonaws.com/0/300925/b90944f1-51e3-2ddd-e57f-8cef57158a67.png)


また本番環境のときには、localhost:3000や127.0.0.1:3000の部分をドメイン名に書き換えたURLを一つ追加すればOKです。

次にKeys and tokensからConsumer API keysをを取得します。

![twitterconsumer.png](https://qiita-image-store.s3.amazonaws.com/0/300925/3fa0f573-1535-7004-0b77-cb3960e864a4.png)

このAPI KeyとAPI secret keyを後で使用します。
Access token & access token secretについては認証やツイートを取得するだけであれば必要ありません。

#gemのインストール
各種gemをインストールしていきます。

twitter認証用のgem

```bash:Gemfile
gem 'omniauth'
gem 'omniauth-twitter'
```

先ほどのConsumer API keysはGitHubに上げたくないので、環境変数に入れます。
今回は.bash_profileなどには入れず、gemで管理したいと思います。

```bash:Gemfile
gem 'dotenv-rails'
```

`bundle`します。

#設定
まずは環境変数の設定をします。
直下の`.env`（なければ作成）に以下を追記します。

```bash:.env
TWITTER_CONSUMER_KEY = (先ほど取得したAPI Key)
TWITTER_CONSUMER_SECRET = (先ほど取得したAPI secret key)
```
`.env`を`.gitignore`に追加するのを忘れないようにしてください。

`route.rb`に以下を追記します

```ruby:route.rb
get 'auth/:provider/callback', to: 'sessions#create'
```

次に`config/initializers/omniauth.rb`を作成し、以下を設定

```bash:config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
    provider :twitter, ENV['TWITTER_CONSUMER_KEY'], ENV['TWITTER_CONSUMER_SECRET'],
end
```

#実装

次にuserモデルにuid, provider、もしなければimageのためのカラムも追加します。
providerで何のサービスかを判断し、uidでどのユーザーかを識別しています。

```ruby:add_columns_to_users.rb
class AddColumnsToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :uid, :string
    add_column :users, :provider, :string
    add_column :users, :image_url, :string
  end
end
```

`rails db:migrate`後、Userモデルには以下を追記します。

```ruby:user.rb
  def self.find_or_create_from_auth(auth)
    provider = auth[:provider]
    uid = auth[:uid]
    name = auth[:info][:name]
    image = auth[:info][:image]
    
    self.find_or_create_by(provider: provider, uid: uid) do |user|
      user.name = name
      user.image_url = image
    end
  end
```

` self.find_or_create_by(provider: provider, uid: uid) do |user|`
でcreateが行われていた場合に検証やコールバックが行われるので適時修正する必要があります。

自分の場合は

```ruby:user.rb
  before_save { self.email.downcase! }
  validates :name, presence: true, length: { maximum: 50 }
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i },
                    uniqueness: { case_sensitive: false }
  has_secure_password
```

を

```ruby:user.rb
  before_save :email_downcase, unless: :uid?
  validates :name, presence: true, unless: :uid?, length: { maximum: 50 }
  validates :email, presence: true, unless: :uid?, length: { maximum: 255 },
                    format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i },
                    uniqueness: { case_sensitive: false }
  has_secure_password validations: false

  private

  def email_downcase
    self.email.downcase!
  end
```

のように修正することで上手くいきました。`unless: :uid`を使用するときには処理内容をメソッドとして切り分けないとsyntax errorになります。

次にsessions_controllersを以下のように修正します。

```ruby:sessions_controller.rb
  def create
    auth = request.env['omniauth.auth']
    if auth.present?
      user = User.find_or_create_from_auth(request.env['omniauth.auth'])
      session[:user_id] = user.id
      redirect_to user
    else
      (従来のログイン処理)
    end
  end
```

次に画像の表示の仕方です。
自分の場合はメールアドレスに対応してgravatarのアイコンが表示される実装になっていました。
（プログラミングスクールやRailsチュートリアルではよくある実装だと思います）
そこで、画像を表示するヘルパーを以下に修正

```ruby:users_helper.rb
module UsersHelper
  def icon_url(user, options = { size: 80 })
    if user.email.present?
      gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
      size = options[:size]
      return "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
    else
      return user.image_url
    end
  end
end
```

最後にビューの実装です。

```erb
<%= link_to "Twitterアカウントでログイン", "/auth/twitter" %>
```

#まとめ

はまるポイントとしてはCallback URLsが複数必要なことと`create`で検証やコールバックでつまづくところでしょうか。同じ要領で他のSNSのOauth認証もやっていきたいと思います。