大ちゃんの駆け出し技術ブログ

RUNTEQ受講生のかわいいといわれるアウトプットブログ

【Rails】Sorceryを使用したGitHubログイン

はじめに

こんにちは!大ちゃんの駆け出し技術ブログです。

sorceryでGitHubログインを実装しましたので本記事で紹介します。GitHubログインをSorceryで実装している記事はほとんどなかったので誰かの役に立てば幸いです。

公式の流れに基本的には沿っていますがGitHubの例ではないです。

GitHubにアプリ登録

外部認証を使用するためにGitHub側で必要な設定を行います。

① 右上のアイコン → 「Settings」をクリック

https://i.gyazo.com/619fe057684c5f24adb85b767fdec29e.png

② 左ペインから「Developer settings」をクリック

https://i.gyazo.com/727b89bd859cfb92c8bf3c428907ad27.png

③ 左ペインから「OAuth Apps」を選択し、「New GitHub App」をクリックします。

https://i.gyazo.com/3a8f21f2318f28109a49e3a96caf6efd.gif

④ 以下のように各項目を入力し、「Register application」をクリック

※ 開発環境ではlocalhost:3000ですが、デプロイ をした後はドメイン名に変更します。

https://i.gyazo.com/62901d0c6b338c33811a0b426d42beaf.png

⑤ ④の後にアプリの設定画面に遷移します。「You need a client secret to authenticate as the application to the API」(アプリケーション側でAPIを認証するためにはクライアントシークレットが必要)とあるように、client secretがないとGitHubログインが実装できません。「Generate a new client secret」をクリックします。

https://i.gyazo.com/2091128228675fef7f08107ab2cb87aa.png

⑥ Client IDとClient Secretを控える(後ほどアプリ内で記述します。)

https://i.gyazo.com/13ad5aa0ce9d69892585275d6704858d.png

補足

SNSログイン等の外部認証機能はこのように基本的に「新規アプリ作成」というフローが必要です。そこで使われる用語の意味はほとんど共通ですので下記に概要を書いておきます。

  • Authorization callback URL・・・認証後にアクセスするURLのこと。例えば、GitHub認証画面で認証を許可した後のURLに該当する。sorceryの場合は「ドメイン/oauth/callback?provider=(facebook or github or etc...)」で指定します。
  • Client ID & Client Secret・・・外部サイトのアプリ固有の鍵みたいなもの。APIを使用するためにアプリ内で環境変数として指定し、APIとの連携を許可する役割を持っています。

Sorcery側の実装

エディタでGitHub認証するための設定をしていきます。前提として、Sorceryでユーザの通常ログインは実装済みで話を進めていきます。

① authenticationsテーブルを作成

下記コマンドを実行することで外部認証用のテーブルを作成します。

$ rails g sorcery:install external --only-submodules
        gsub  config/initializers/sorcery.rb
      insert  app/models/user.rb
      create  db/migrate/2021xxxxxxxxxx_sorcery_external.rb

下記ファイルが作成されます。

class SorceryExternal < ActiveRecord::Migration[6.1]
  def change
    create_table :authentications do |t|
      t.integer :user_id, null: false
      t.string :provider, :uid, null: false

      t.timestamps              null: false
    end

    add_index :authentications, [:provider, :uid]
  end
end

そのままマイグレーション

$ rails db:migrate

② Authenticationモデルの作成

①で実行したコマンドでテーブルは作成されるのですが、それに対応したモデルは作成されていません。そのため手動で作成する必要があります。

$ rails g model Authentication --migration=false

マイグレーションは実行済みなのでオプションで--migration=falseとしておきます。①でモデルも一緒に作成しておけばオプションの考慮はいらないはずなのですが、、、

AuthenticationモデルとUserモデルにアソシエーションを追加します。

Userモデル

class User < ApplicationRecord
  authenticates_with_sorcery!
  has_many :authentications, dependent: :destroy
  accepts_nested_attributes_for :authentications
end

Authenticationモデル

class Authentication < ApplicationRecord
  belongs_to :user
end

ちなみにaccepts_nested_attributes_forですが、これはそれに属するインスタンスが作成された時に、子モデルも同時に作成する設定です。Userモデルが作成されたら、自動でAuthenticationモデルも作成してくれるということになります。

※ ちなみにaccepts_nested_attributes_forは非推奨となっており、Railsの開発者たちから嫌われています。


③ credentials.yml.encを編集

先ほど控えたClient IDとClient Secretを環境変数としてcredentials.yml.encに記述しアプリ内に登録します。credentials.yml.encは暗号化された文字列が表示されているファイルです。

p2cImUehvb1o9HfMqvKxj0PImkV8nJO12hwcUwC9xuZg6WhKIfkt+yzKIMOs61DflHTnft5abRrDvVDHxwTEe5YQHG+/TdvWF3VlFVxaiG3AhwoiOZqpVmGBVeIk+Bk4cAdNzD6hKwCzoZLC3R80aMtmSDSgGplAp+M3gWvWKPRUUCWnDZD9K/+ayHeuVjf/jmQMwBvHH2aEzDeoFjbaEp5cZgIsVRUnA4HpmAiTHkBmQlN16FWVXB1BmKXfJkCFG01iFUkLMfnUG5sKGHTzb33XRQ0y05pa5RDUQCEZLIuwLL5mL6xLsIs28+P64qLsqqiMfAcVUfQdZMDBSlzH1a+R3cBowF2Zpe9b5gKOcIheM00Xv04imOCxfN6j5Z4waB1u1psOhU/ucUdaN2UrglnVAklHTRC9BjKS6//tfzUsJarpjGT4v7o30MFgkvj825Yjysb0mYQ7FcgcRdZTqDVecz6JQTCAif7+QTbXilPtD5ZbSB7G7Y4GzHP8XaEfXmWPWv15PxotjXVWL+2qI7/2Qy9lJK6WZZU5WfP/a9fX6C5wxy0ZCZlZENamIl6hIlwIcbS5FUurXDZB8WqEGh4v0TYCdzOT9Gskj2Kv+1hQFVsxNdtPig==--6dulaUT3AgkwOLTZ--DD7AmvexctmdANexHgISBw==

しかし、master.keyがconfig配下にある状態で下記コマンドを実行すると中身を編集できます。

$ EDITOR="vi" bin/rails credentials:edit

以下のように編集してください。

github:
  key: 控えたClient ID
  secret: 控えたClient Secret
  callback_url: 'http://localhost:3000/oauth/callback?provider=github

編集後保存します。上述した中身を編集できるコマンドを実行して保存されたか確認しておきましょう。

$ EDITOR="vi" bin/rails credentials:edit

④ sorcery.rbを編集

sorcery.rbをGitHub認証用に対応するように編集します。

Rails.application.config.sorcery.submodules = [:external] # コメントアウト
・
・
・
# Here you can configure each submodule's features.
Rails.application.config.sorcery.configure do |config|
・
・
・# 外部認証にgithubを指定
    config.external_providers = [:github]
・
・
・
    # ③で設定したcredentials.yml.encの値に合わせる
    config.github.key = Rails.application.credentials.dig(:github, :key)
  config.github.secret = Rails.application.credentials.dig(:github, :secret)
  config.github.callback_url = Rails.application.credentials.dig(:github, :callback_url)
  config.github.user_info_mapping = {email: "email", name: "login", remote_avatar_url: "avatar_url"}
  config.github.scope = "user:email"

        # --- user config ---
  config.user_config do |user|

        # -- external --
    # Class which holds the various external provider data for this user.
    # Default: `nil`
    #
    user.authentications_class = Authentication # コメントアウトしてAuthenticationモデルを指定

  end
end

⑤ OauthsControllerを作成して下記のように記述

class OauthsController < ApplicationController
  skip_before_action :require_login, raise: false

  def oauth
    return redirect_to root_path, info: t('defaults.info') if logged_in?
    login_at(params[:provider])
  end

  def callback
    provider = params[:provider]
    if @user = login_from(provider)
      redirect_to root_path, success: "#{provider.titleize}アカウントでログインしました。"
    else
      begin
        @user = create_from(provider)
        reset_session
        auto_login(@user)
        redirect_to root_path, success: "#{provider.titleize}アカウントでログインしました。"
      rescue
        redirect_to root_path, error: "#{provider.titleize}アカウントでのログインに失敗しました。"
      end
    end
  end
end

補足

  • login_from・・・既にユーザが外部認証済みでDBに登録されているか確認し、登録されていればログインをしtrueを返却するメソッド。登録されていなければfalseを返す。
  • create_from・・・login_fromfalseの場合にユーザ登録をするメソッド。

⑥ ルーティングを設定

sorceryではどの外部認証を使っても下記のルーティングを記載します。

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

⑦ Userテーブルにremote_avatar_urlカラムを追加

GitHub認証の返却値として、name, email, remote_avatar_urlの値が返却されます。sorceryのGitHub認証では、Userテーブルにこれらのカラムがあることを期待して動作するため、もしremote_avatar_urlがないとエラーを起こします。

undefined method 'remote_avatar_url' for < #class User ・・・>

既にsorceryでUserテーブルを作成していますが、そのカラムにremote_avatar_urlがないため、カラムを追加します。

class AddAvatarsToUsers < ActiveRecord::Migration[6.1]
  def change
    add_column :users, :remote_avatar_url, :string
  end
end
$ rails db:migrate

⑧ リンクをviewに追加

<%= link_to auth_at_provider_path(provider: :github), class: "btn btn-light" do %>
  <i class="fab fa-github mr-2"></i>
    <span class="has-text-weight-medium">Sign in with Github</span>
<% end %>

終わりに

Slackログインには1ヶ月もかかったのでGitHubもそれなりに時間がかかるのかなと思っていましたが、たったの2時間で実装できてしまいました、、、、全然難易度違う、、、、、。しかし、Slackログインを経験したからこそClient IDの流れやCallback URLの設定の理解があったので楽に実装できたんだと思います!

他の外部認証もどんどん試していきたいです!