[RUBY] [Rails 6] Register and log in with Devise + SNS authentication (multiple links allowed)

Introduction

Create authentication that can be linked with multiple SNS. Devise will proceed as implemented. We will not touch on the acquisition of each API key, so please check accordingly. This time I will implement it on facebook and twitter, but I think that it is basically the same for other SNS.

environment

environment


Windows10
ruby 2.6.6
Rails 6.0.3.1

Preparation

gem added

Gemfile


gem 'omniauth'
gem 'omniauth-facebook'
gem 'omniauth-twitter'

Please bundle install after saving.

Write the API key in credentials.yml

For Windows, please refer to Previous article.

credentials.yml



facebook:
  api_key: pk_test_~
  secret_key: sk_test_~
twitter:
  api_key: pk_test_~
  secret_key: sk_test_~
Devise settings

In config / initializer.

devise.rb


#Add the following
config.omniauth :facebook, Rails.application.credentials.facebook[:api_key], Rails.application.credentials.facebook[:secret_key], scope: 'email', info_fields: 'email,name'
config.omniauth :twitter, Rails.application.credentials.twitter[:api_key], Rails.application.credentials.twitter[:secret_key], scope: 'email', callback_url: 'https://localhost:3000/users/auth/twitter/callback'

It seems that twitter needs to specify the callback URL.

Create a Social model

This time, it is assumed that the User model has already been created in Devise. You can add columns to the User model, but this time we will create a new Social model linked to the User model. (The User model and the Social model have a one-to-many relationship.)

console


rails g model social

The Social model should have user_id, provider (which contains "facebook", "twitter", etc.), and uid (which contains the id to associate with each SNS account).

2020~create_socials.rb


class CreateSocials < ActiveRecord::Migration[6.0]
  def change
    create_table :socials do |t|
      t.references :user, null: false, foreign_key: true
      t.string :provider, null: false
      t.string :uid, null: false
      t.timestamps
    end
  end
end

After saving, rails db: migrate.

Association

user.rb


has_many :socials, dependent: :destroy#add to
devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, 
          :omniauthable#add to

social.rb


belongs_to :user
validates :uid, uniqueness: {scope: :provider}#Prevent multiple registrations within the same provider

Prevent the same SNS account from being linked to multiple users. I think it's okay if you don't have "{scope :: provider}", but just in case.

Implementation

Create a callback process

routes.rb


devise_for :users, controllers: {
  sessions: 'users/sessions',
  password: 'users/password',
  registrations: 'users/registrations',
  omniauth_callbacks: 'users/omniauth_callbacks'#add to
  }

Below in app / controllers / users / omniauth_callbacks_controller.rb. (Check with rails routes etc. and set the method in this to each redirect destination.)

omniauth_callbacks_controller.rb


class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

 def facebook
    if request.env['omniauth.auth'].info.email.blank?#Do you allow email on Facebook?
      redirect_to '/users/auth/facebook?auth_type=rerequest&scope=email'
    end
    callback_from :facebook
  end

  def twitter
    callback_from :twitter
  end


  private

  def callback_from(provider)
    provider = provider.to_s

    if user_signed_in?
      @user = current_user
      User.attach_social(request.env['omniauth.auth'], @user.id)#Attach later_make social. Pass the information from SNS and the id of the logged-in User.
    else
      @user = User.find_omniauth(request.env['omniauth.auth'])#Find later_Make omniauth. Pass information from SNS.
    end

    if @user.persisted?#Registered or if you can register
      flash[:notice] = I18n.t('devise.omniauth_callbacks.success', kind: provider.capitalize)
      sign_in_and_redirect @user, event: :authentication
    else
      session["devise.#{provider}_data"] = request.env['omniauth.auth']
      redirect_to new_user_registration_url
    end
  end
end

Pass the type of SNS of the redirect source to "callback_from" and isolate it. In "callback_from", "new user registration by SNS authentication or login of SNS authenticated user" or "SNS cooperation of logged-in user" was separated. The processing of "after registration", "after login", and "after cooperation" is not divided this time, but all are redirected to the user page.

Next, write each process in User.rb.

user.rb


 def self.find_omniauth(auth)#New registration or sns login with SNS authentication
    social = Social.where(uid: auth.uid, provider: auth.provider).first
    unless social.blank?#sns certified(Login)
      user = User.find(social.user.id)
    else#New registration with sns authentication
      temp_pass = Devise.friendly_token[0,20]#This time I will create a random password for the time being
      user = User.create!(
        username: auth.info.name,
        email: auth.info.email,
        password: temp_pass,
        password_confirmation: temp_pass,
      )
      social = Social.create!(
        user_id: user.id,
        provider: auth.provider,
        uid: auth.uid,
      )
    end

    return user
 end

 def self.attach_social(auth, user_id)#When adding sns linkage
    social = Social.create!(
      user_id: user_id,
      provider: auth.provider,
      uid: auth.uid,
    )
  end

At the time of "new user registration by SNS authentication or login of SNS authenticated user", it is further divided into "new registration" and "login". At the time of "new registration", both User and Social are created (at the same time, User and Social are linked), and User is returned. At the time of "login", Social is searched from the information (auth.uid and auth.provider) sent from SNS, and the associated User is returned. Only Social is created at the time of "SNS linkage of logged-in user". The user_id is received to associate with the logged-in User.

This is the end of the process. After that, if you create a link with "user_facebook_omniauth_authorize_path" etc., new registration, login, and cooperation will be performed according to the user's situation.

The end

Actually, I think it would be better to have a link to the linked SNS, a password reset mechanism at the time of new registration on SNS, Devise's email authentication, a button to cancel the link, etc., but for the time being the minimum configuration just made it. I'm studying Rails, so I think there are many places that aren't smart. If you do something strange, please let me know!

Recommended Posts

[Rails 6] Register and log in with Devise + SNS authentication (multiple links allowed)
[Rails] How to log in with a name by adding a devise name column
[Ruby on Rails] How to log in with only your name and password using the gem devise
Create authentication function in Rails application using devise
(Basic authentication) environment variables in rails and Docker
[Rails] Google, Twitter, Facebook authentication using Devise and Omniauth
Build a bulletin board API with authentication authorization in Rails # 12 Association of user and post
Log aggregation and analysis (working with AWS Athena in Java)
[Rails] Search from multiple columns + conditions with Gem and ransack
[rails] Problems that cannot be registered / logged in with devise
Implement login function simply with name and password in Rails (3)
Implement user registration function and corporate registration function separately in Rails devise
[Rails] Get access_token at the time of Twitter authentication with Sorcery and save it in DB
Build a bulletin board API with authentication authorization in Rails 6 # 11 User model test and validation added
Japaneseize using i18n with Rails
Implement LTI authentication in Rails
Use multiple databases with Rails 6.0
Use multiple checkboxes in Rails6!
Memorandum [Rails] User authentication Devise
When log data accumulates in Rails and the environment stops working