[Rails]devise、Omniauthを利用したGoogle、facebook認証を実装した

# はじめに
久しぶりの更新になります。某スクールを先日卒業したChihaと申します。
某スクールにてチームで某フリマアプリのコピーを作成したため、実装物のコードについて記録を残しつつ、開発チームで共有する目的で当記事を書いています。

今回は[OmniAuth](https://github.com/omniauth/omniauth)を利用したGoogle、facebookユーザー認証機能について、私が行った作業を全部まとめて書いていこうと思います。
極力この記事のみで全て実装できるように丁寧な記事にしようと思います。
~~丁寧な記事が書けるとはとは言ってない~~

# 対象読者
- SNS認証をアプリケーションに組み込みたい方
- あれこれ色々な記事を確認しながら実装するのに疲れた方
- 某スクールの受講生

~~後輩受講生が見る可能性があるのでこういう記事書いていいのかな？とも思いますがその辺は考えない~~

# そもそもの話、[OmniAuth](https://github.com/omniauth/omniauth)とは？
Google、Facebook、twitter等のSNSアカウントを用いてユーザー登録やログインを行ってくれるgem。

```ruby:Gemfile
gem "omniauth"
gem "omniauth-twitter"
gem "omniauth-facebook"
```
のように、使いたいSNSによって"omniauth-snsによって決まった名称"のgemを導入する必要があります。

>OmniAuthは、複数の外部サービスのアカウント情報を使ってユーザー登録やログインを提供します。OmniAuthはサービスごとにストラテジー（Strategies）を管理する、いわば元締めのgemです。OmniAuthのストラテジーとは、外部サービスごとにOAuth認証に必要な処理が記述されており、Rackミドルウェアとして提供されます。

>出典：[RailsでSNS認証機能を実装しよう～定番gem「OmniAuth」活用法](https://codezine.jp/article/detail/10970)

ということで、やっていきましょう。

# 開発環境
- Ruby on Rails 5.2.2
- Ruby 2.5.1
- haml
- gem devise
- gem omniauth

# 事前準備
使いたいSNSのAPI取得が必要です。
今回はgoogleとfacebookですね。
また、注意事項が数点あります。

## 導入にあたっての注意事項
~~この辺でハマったから記事書いたまである~~

### google認証の注意事項
OAuthクライアントID（Railsに設定するAPIキーのようなもの）の取得に際して、承認済みのリダイレクトURIを登録する必要がありますが、きちんとしたドメインを設定したURLでないと登録ができません。
※ローカル環境のURLは普通に登録できます。できないのはdevelopment環境での〇〇.〇〇.〇〇のような、数値のみのURLとなるドメイン等になります。
そのため、今回はローカル環境のみの話になります。本番実装するならドメインを取得して登録するなどの対応が必要です。

### facebook認証の注意事項
こいつも曲者です。
OAuthクライアントID（Railsに設定するAPIキーのようなもの）の取得に際して、承認済みのリダイレクトURIを登録する必要がありますが、SSL通信を行うURLのみ登録することができます。
つまり、httpsから始まるURLでの登録が必要であり、httpで始まるURLでは登録ができません。
※Railsを普通にセッティングして開発してた場合、ローカルサーバーを普通に`$rails s`すると、httpから始まるパスになります。
そのため、ローカルでもSSL通信となるような設定の変更が必要になります。

### 注意点まとめ
- google認証では、本番環境ではきちんとしたドメインの取得が必要な場合がある
- facebook認証では、httpsから始まるパスによるSSL通信をするURLを登録する必要がある

今回の記事ではローカルでの開発に絞った話をするため、ローカルでのSSL通信化のみ必要となります。

## 開発環境でのSSL通信化
[Rails5 + pumaのローカル環境でSSL/HTTPSを有効にする](https://qiita.com/zaru/items/ccf328e87c768168134d)を参考にしました。

#### SSL証明書の作成
今回はローカルで動くようにするだけなので適当な証明書をアプリのディレクトリ内に適当に作ります。

```:ターミナル
ssl証明書を置くディレクトリ $ openssl genrsa 2048 > server.key
ssl証明書を置くディレクトリ $ openssl req -new -key server.key > server.csr #色々入力を求められますが、全部適当で大丈夫です。
ssl証明書を置くディレクトリ $ openssl x509 -days 3650 -req -signkey server.key < server.csr > server.crt
```
作れたらpumaの設定を弄ります。

#### pumaの設定変更
以下のコードを書き足します。

```ruby:puma.rb
~~中略~~
if ENV.fetch('RAILS_ENV') { 'development' } == 'development'
  ssl_bind 'localhost', '9292', {
    key: 'tmp/server.key',
    cert: 'tmp/server.crt'
  }
end
```

`ssl_bind`の後にはURLにしたい番号やらを書くといいです。
今回の場合は生成されるのは`https://localhost:9292`となります。

#### 起動時の注意
SSL通信が可能なサーバーを起動する場合、`$ rails s`ではなく、

```:ターミナル
$ bundle exec puma -C config/puma.rb
```
で起動します。
以後はこの起動コマンド及びURLにて作業することになります。

続いて認証のためのAPI取得などなど

## google認証

まずはクライアントID及びクライアントシークレットを取得します。

### クライアントIDの取得

[Google Developer Console](https://console.developers.google.com/)
にログインし、プロジェクトの選択 > 新しいプロジェクト
<img width="909" alt="スクリーンショット 2019-10-18 18.24.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/eed247da-eecd-23c0-4422-0b73041ac205.png">
<img width="1092" alt="スクリーンショット 2019-10-18 18.26.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/e6d07b19-bdb3-11a5-d70d-61dc7ae8c22d.png">
導入するアプリに沿ってプロジェクト名を入力し、作成します(今回は記事用のスクリーンショットなのでデフォルト名そのまま)
<img width="764" alt="スクリーンショット 2019-10-18 18.29.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/22fca241-fb67-9cc2-eb2a-7d5f233b1dbc.png">
作成したら以下の画面に遷移するので、左上の三本線（ハンバーガーアイコン）より、ナビゲーションメニューを表示し、APIとサービス > OAuth同意画面　へと遷移し、アプリ名だけ入れて保存を押します。
<img width="1261" alt="スクリーンショット 2019-10-18 18.38.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/dfbadae9-72ab-6550-4320-37626f680f06.png">
<img width="925" alt="スクリーンショット 2019-10-18 19.15.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/497a5b48-9e7a-9f9b-314f-37f260bf192b.png">
<img width="1102" alt="スクリーンショット 2019-10-18 19.19.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/fdfe982f-0b00-04df-20ba-8170ff16d870.png">

保存ができたら、認証情報 > 認証情報を作成 > OAuth クライアント IDから、ID取得画面へ移動します。

<img width="858" alt="スクリーンショット 2019-10-18 18.45.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/52839bc7-c803-112b-cf70-2c83a67b2fdd.png">
<img width="780" alt="スクリーンショット 2019-10-18 18.46.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/9eaaa163-11c6-abd3-5015-c100ee7825d5.png">
<img width="1090" alt="スクリーンショット 2019-10-18 18.49.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/7b5cd9a9-c1ad-cb68-6ce8-db7a1ecfc205.png">
<img width="700" alt="スクリーンショット 2019-10-18 19.11.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/22891284-12f3-478d-8039-aedf3e92ae73.png">

今回は「ウェブ アプリケーション」を選択してください。
<img width="686" alt="スクリーンショット 2019-10-18 19.25.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/f361e4f7-acf8-f661-65a5-e23d014ec000.png">
選ぶと色々入力項目が出ますが、承認済みのリダイレクトURIに

```
https://localhost:9292/users/auth/google_oauth2/callback
```
を入れて保存してあげましょう。
保存すると、「クライアントID、クライアントシークレット」の２つが表示されます。
この２つをRailsで使用するので控えておきます。


取得できたら、認証を利用するためのAPIを有効にしましょう。

### Google+ APIの有効化
左側ナビゲーションメニューからAPIとサービス >　ライブラリ　へ移動
<img width="783" alt="スクリーンショット 2019-10-18 20.31.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/14d5b088-f67a-597d-523c-c18c9797d2cd.png">

google+ で検索し、検索結果に出てくるgoogle+ APIを有効にします。
<img width="1035" alt="スクリーンショット 2019-10-18 20.34.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/9d879c07-b29f-6ea7-e79c-a77a86056fc1.png">
<img width="1111" alt="スクリーンショット 2019-10-18 20.35.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/8622c107-2e81-fff1-345d-03ef7214d661.png">
<img width="949" alt="スクリーンショット 2019-10-18 20.37.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/662b4caa-02db-a8f4-0315-80df5b588ded.png">

これでGoogleは完了です。

## facebook認証
[facebook for developers](https://developers.facebook.com/apps)へとアクセスします。
新しいアプリの追加を押してアプリ名とメールアドレスを入力し、アプリIDを作成する。
<img width="554" alt="スクリーンショット 2019-10-18 20.43.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/58adddd7-debd-e2b1-ad41-aea25c880f8a.png">
<img width="1125" alt="スクリーンショット 2019-10-18 20.45.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/02c314c4-305d-834d-24ff-6d8819fb45b5.png">
作成したら、ベーシックへと移動し、「アプリID」「app secret」を控えておきます。
<img width="694" alt="スクリーンショット 2019-10-18 20.56.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/7638d968-0af4-0e30-4b27-f761fac27b13.png">
<img width="1346" alt="スクリーンショット 2019-10-21 14.31.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/ea045b66-a626-ea55-46b2-83a7eccc9577.png">
控えたら左側メニューのプロダクトの横にある「+」ボタンからプロダクト追加画面へ。
<img width="692" alt="スクリーンショット 2019-10-21 15.05.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/9862e33a-fc61-8671-8ffa-4576c8dbbac3.png">

一番最初に出てくる「Facebookログイン」製品の設定を押すと、左のメニューにFacebookログインの項目が追加されます。
<img width="964" alt="スクリーンショット 2019-10-21 15.07.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/1faba5d6-101f-fbdd-bbe9-25eb08e52023.png">
表示されたら、左メニュー「設定」から、OAuthリダイレクトURIを設定します。

```
https://localhost:9292/users/auth/facebook/callback
```
今回はpumaの設定に沿って以下のURIを登録します。問題なければ「変更を保存」して完了です。

<img width="1064" alt="スクリーンショット 2019-10-21 15.35.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/ddd678be-50fe-da1e-2cf8-ec49be88b8f9.png">

何も問題なければ、これでいけると思います。

これでSNS側での設定は完了しました。続いてRails側のコード。

#機能実装
今回はdeviseのomniauth_callbackを利用します。
## テーブル
devise経由で作成したusersテーブルにuid、providerの２項目を追加します。（ログイン時に認証するために保存が必要になります）

```:ターミナル
$ rails g migrate 適当な名前
```
して適当なmigrateファイルを作成し、uidとproviderカラムを追加する記述をします。
uidは数字だけではないのでstring型にしましょう。integer型だと保存できません。

```ruby:migrateファイル
class AddOmniauthToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :provider, :string
    add_column :users, :uid, :string
  end
end
```
書いたら

```:ターミナル
$ rails db:migrate
```

して、テーブルの準備は完了です。
最終的なテーブル構成はこんな感じに。
####テーブル構成
|Column|Type|Options|
|------|----|-------|
|nickname|string|null: false|
|email|string|null: false,unique: true|
|encrypted_password|string|null: false|
|uid|string||
|provider|string||

アソシエーションや他テーブルは今回の実装に関係ないので割愛します。

##ルーティング
deviseで生成されるomniauth_callbacks_controller.rbを使用するため、コントローラを明示する記述を追加します。

```ruby:routes.rb
Rails.application.routes.draw do
  devise_for :users,controllers: {omniauth_callbacks: "users/omniauth_callbacks"}
end
```

## Controller

```ruby:controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  #facebookとgoogle_oauth2の２つを定義
  def facebook
    callback_from :facebook
  end

  def google_oauth2
    callback_from :google
  end

  private

  def callback_from(provider)
    provider = provider.to_s #プロバイダを定義
    @user = User.from_omniauth(request.env['omniauth.auth']) #モデルでSNSにリクエストするメソッド（from_omniauth）を使用し、レスポンスを@userに代入
    if @user.persisted? #@userがすでに存在したらログイン処理、存在しなかったら残りの登録処理へ移行
      sign_in @user
      redirect_to root_path
    else
      #今回は複数ページに渡る登録項目があるため、情報をsessionに保存し、他のページにも持ち越せるように
      #この辺りの値は用途に合わせてアレンジしてください。
      session[:password] = @user.password
      session[:password_confirmation] = @user.password
      session[:provider] = @user.provider
      session[:uid] = @user.uid
      redirect_to registration_signup_index_path
    end
  end

end
```
## Model
実際の認証を行う処理部分をモデルに書いています。

```ruby:user.rb
class User < ApplicationRecord
  # :omniauthableの記述を追加するのを忘れないように
  devise :database_authenticatable, :registerable,
          :recoverable, :rememberable, :validatable, :omniauthable
  ~~中略~~
  # sns認証後、ユーザーの有無に応じて挙動を変更する
  def self.from_omniauth(auth)
    # uidとproviderでユーザーを検索
    user = User.find_by(uid: auth.uid, provider: auth.provider)
    if user
      #SNSを使って登録したユーザーがいたらそのユーザーを返す
      return user
    else
      #いなかった場合はnewします。
      new_user = User.new(
        email: auth.info.email,
        nickname: auth.info.name,
        uid: auth.uid,
        provider: auth.provider,
        #パスワードにnull制約があるためFakerで適当に作ったものを突っ込んでいます
        password: Faker::Internet.password(min_length: 8,max_length: 128)
      )
      return new_user
    end
  end
end
```

## View
認証を行いたいページの適当な箇所にcallbackのリンクを仕込むだけです。

```haml:new.html.haml
#見やすさのためにclassや他の記述は省いています。
= link_to user_facebook_omniauth_authorize_path do
  = 'Facebookで登録する'
= link_to user_google_oauth2_omniauth_authorize_path do
  = 'Googleで登録する'
```

## クライアントIDの設定
secret.ymlに取得したクライアントID・クライアントシークレットを記載します。

```yml:secret.yml
development:
  google_client_id: <%= ENV["GOOGLE_CLIENT_ID"] %>
  google_client_secret: <%= ENV["GOOGLE_CLIENT_SECRET"] %>

  facebook_client_id: <%= ENV["FACEBOOK_CLIENT_ID"] %>
  facebook_client_secret: <%= ENV["FACEBOOK_CLIENT_SECRET"] %>
```

devise.rbに、設定したクライアントID及びシークレットを読み込む記述を追加します。適当にファイル末尾に以下を記載。

```ruby:devise.rb
config.omniauth :facebook,Rails.application.secrets.facebook_client_id,Rails.application.secrets.facebook_client_secret
config.omniauth :google_oauth2,Rails.application.secrets.google_client_id,Rails.application.secrets.google_client_secret
```

以上になります。
今回は保存処理などに関しては書いていません。認証処理と認証完了後のデータの取得を中心に記事を書きました。

# 最後に

スクール卒業から身の回りに積もったあれこれを消化していたら久しぶりの記事更新になりました。まだまだ書きたい項目はあるのでじゃんじゃん更新していこうと思います。
就活も頑張ります。

まだまだ粗末な点も多いと思いますが、より良いコード、間違った点などがあればご教授頂けると幸いです。