[Rails 6] Implementation of new registration function by SNS authentication (Facebook, Google)

Introduction

I am currently creating an application using Rais. This time, we will additionally implement the login function that has already been implemented so that you can log in with your SNS account. I will write it for memorandum and review.

environment

Ruby on Rails '6.0.0' Ruby '2.6.5'

Premise

--User login function has been implemented using devise (gem). -External API has been set from facebook for developers and Google Cloud Platform.

(1) Introduction of Gem and setting of environment variables

omniauth -A gem that allows you to implement user registration and login using SNS accounts such as Google, Facebook, and Twitter. This time we will install Facebook and Google omniauth.

Gemfile


gem 'omniauth-google-oauth2'
gem 'omniauth-facebook'

Terminal


% bundle install

Also, set the ID and secret key obtained when setting the external API in the environment variables. I think there are several ways to set environment variables, but in my case, I have already set a ".env file" with "gem'dotenv-rails'", so I wrote it there.

.env


FACEBOOK_CLIENT_ID='App ID'
FACEBOOK_CLIENT_SECRET='app secret'
GOOGLE_CLIENT_ID='Client ID'
GOOGLE_CLIENT_SECRET='Client secret'

Next, write the description to read the environment variables on the application side.

config/initializers/devise.rb


(abridgement)
  config.omniauth :facebook,ENV['FACEBOOK_CLIENT_ID'],ENV['FACEBOOK_CLIENT_SECRET']
  config.omniauth :google_oauth2,ENV['GOOGLE_CLIENT_ID'],ENV['GOOGLE_CLIENT_SECRET']

② Model settings

When you send a request to the SNS Web API, it authenticates on the API. After that, the information registered on SNS is returned to the application side. Save the "uid" and "provider" in the response from the API in the application database along with the user information (name, etc.). However, since the specifications are "the timing of SNS authentication and user registration are different", it is not possible to create a record in the users table during SNS authentication. Therefore, I created another table (SnsCredential model) to save the information at the time of SNS authentication.

Terminal


% rails g model sns_credential

db/migrate/XXXXXXXXXXXXXXXX_create_sns_credentials.rb


class CreateSnsCredentials < ActiveRecord::Migration[6.0]
 def change
   create_table :sns_credentials do |t|
     t.string :provider
     t.string :uid
     t.references :user,  foreign_key: true

     t.timestamps
   end
 end
end

Since it is associated with the user model, it has user_id as a foreign key. As soon as the editing is finished, it will be reflected in the database with "rails db: migrate".

Set the association.

app/models/user.rb


(abridgement)
has_many :sns_credentials, dependent: :destroy

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:facebook, :google_oauth2]

You can use OumniAuth from Facebook and Google by writing omniauth_providers: [: facebook,: google_oauth2].

app/models/sns_credential.rb


class SnsCredential < ApplicationRecord
  belongs_to :user
end

③ Reset devise

I am creating a controller to reconfigure the devise controller.

Terminal


% rails g devise:controllers users

Modify the devise routing to use the generated controller.

config/routes


Rails.application.routes.draw do
  devise_for :users, controllers: {
    omniauth_callbacks: 'users/omniauth_callbacks',
    registrations: 'users/registrations',
    sessions: 'users/sessions',
  }
···(abridgement)···
end

④ Controller settings

app/controllers/users/omniauth_callbacks_controller.rb


class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    authorization
  end

  def google_oauth2
    authorization
  end

  private

  def authorization
    @user = User.from_omniauth(request.env["omniauth.auth"]
  end
end

Define the actions "facebook" and "google_oauth2" that call "authorization" described in the private method.

⑤ Edit View file

ruby:app/views/users/registrations/new.html.erb


(abridgement)
<div class="d-flex justify-content-between mt-4">
  <%= link_to user_facebook_omniauth_authorize_path, class:"btn btn-outline-primary sns-btn", method: :post do %>
    <i class="fab fa-facebook fa-2x"></i>
  <% end %>
  <%= link_to user_google_oauth2_omniauth_authorize_path, class:"btn btn-outline-danger sns-btn", method: :post do %>
    <i class="fab fa-google fa-2x"></i>
  <% end %>
</div>

⑥ Edit model

I created a class method in the User model for the data that goes into the User model. Class methods seem to be used for processing that uses common information in the class. The description method is defined by connecting self with. (Dot) before the method name.

app/models/user.rb


(abridgement)
 def self.from_omniauth(auth)
   sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
 end

You can now also call User.from_omniauth described in omniauth_callbacks_controller.rb above. Also, the first_or_create method is used by substituting "provider" and "uid" in the acquired information for sns. (First_or_create method: A method that searches if the record to be saved exists in the database, returns an instance of that record if there is a record with the searched condition, and saves a new instance if not.)

Next, write the code after confirming whether SNS authentication has been performed.

app/models/user.rb


(abridgement)
 def self.from_omniauth(auth)
   sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
   user = User.where(email: auth.info.email).first_or_initialize(
      first_name: auth.info.last_name,
      last_name: auth.info.first_name,
      email: auth.info.email
    )
 end

The code added above will be searched by email address if SNS authentication has not been performed. Use first_or_initialize to return the name and email address. (First_or_initialize: By using with the where method, if there is a record of the condition searched by where, it returns an instance of that record, otherwise it creates a new instance.)

first_or_create: Save new record in database first_or_initialize: Do not save new records in database

⑦ Edit controller

Describe the processing after returning from the User model.

app/controllers/users/omniauth_callbacks_controller.rb


class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    authorization
  end

  def google_oauth2
    authorization
  end

  private

  def authorization
    @user = User.from_omniauth(request.env['omniauth.auth'])

    if @user.persisted? #Since the user information has already been registered, login processing is performed instead of new registration.
      sign_in_and_redirect @user, event: :authentication
    else #Since user information has not been registered, the screen will change to the new registration screen.
      render template: 'devise/registrations/new'
    end
  end
end

Assign the value returned from the User model to @user. This is to display the "name" and "email address" obtained in view.

Next, write the login time.

⑧ Edit User model

app/models/user.rb


(abridgement)
 def self.from_omniauth(auth)
   sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
   user = User.where(email: auth.info.email).first_or_initialize(
      first_name: auth.info.last_name,
      last_name: auth.info.first_name,
      email: auth.info.email
    )

  #Determine if user is registered
   if user.persisted?
     sns.user = user
     sns.save
   end
   user
 end

It is judged as a registered user by persisted ?, and the inside of the "if statement" is called. At the time of new registration, the SnsCredential model and the User model are not linked because the user_id was not fixed at the timing when the SnsCredential model was saved. At the time of login, sns.user will be updated and linked.

Finally, edit the view file on the login screen and you're done! !!

At the end

If you try to implement it by referring to this article and it doesn't work, please let me know. .. Please forgive me for the first implementation. ..

Recommended Posts

[Rails 6] Implementation of new registration function by SNS authentication (Facebook, Google)
[Rails] Implementation of SNS authentication (Twitter, Facebook, Google) function
[Rails 6] Implementation of SNS (Twitter) sharing function
[Rails] Implementation of new registration function in wizard format using devise
[Rails] Implementation of retweet function in SNS application
SNS authentication using Rails google
[Rails 6] Implementation of search function
[Rails] Implementation of category function
[Rails] Implementation of tutorial function
[Rails] Implementation of like function
[Rails] Implementation of CSV import function
[Rails] Asynchronous implementation of like function
[Rails] Implementation of image preview function
[Rails] About implementation of like function
[Rails] Implementation of user withdrawal function
[Rails] Implementation of CSV export function
Implementation of user authentication function using devise (1)
Rails [For beginners] Implementation of comment function
Implementation of user authentication function using devise (3)
[Vue.js] Implementation of menu function Implementation version rails6
[Ruby on rails] Implementation of like function
[Vue.js] Implementation of menu function Vue.js introduction rails6
[Rails] Implementation of search function using gem's ransack
Implementation of Ruby on Rails login function (Session)
[Rails 6] Implementation of inquiry function using Action Mailer
[Rails 6] Change redirect destination at the time of new registration / login by devise
Implementation of Google Sign-In using Google OAuth 2.0 authentication (server edition)
Ruby on Rails <2021> Implementation of simple login function (form_with)
[Rails] Implementation of drag and drop function (with effect)
Implementation of search function
[Ruby on Rails] Implementation of tagging function/tag filtering function
[Rails] Implementation of multi-layer category function using ancestry "seed"
Rails search function implementation
Implementation of pagination function
[Rails] Google, Twitter, Facebook authentication using Devise and Omniauth
Rails CRUD function implementation ① (new addition, deletion this time)
[Rails] Implementation of multi-layer category function using ancestry "Editing form"
[Rails] Implementation of multi-layer category function using ancestry "Creation form"
Rails sorting function implementation (displayed in order of number of like)
[Rails] Implementation of tagging function using intermediate table (without Gem)
Rails implementation of ajax removal
Rails fuzzy search function implementation
Implementation of sequential search function
Implementation of like function (Ajax)
Implementation of image preview function
Implementation of category pull-down function
Login function implementation with rails
[Rails 6] Pagination function implementation (kaminari)
[Implementation procedure] Create a user authentication function using sorcery in Rails
[Rails] Implementation of coupon function (with automatic deletion function using batch processing)
I implemented Rails API with TDD by RSpec. part3-Action implementation with authentication-
[Rails] Implementation of tag function using acts-as-taggable-on and tag input completion function using tag-it
[Rails] Implementation procedure of the function to tag posts without gem + the function to narrow down and display posts by tags
[Rails] Implementation of user logic deletion
[Rails] Introduction of Rubocop by beginners
[Rails] Comment function (registration / display / deletion)
[Rails] Implementation of many-to-many category functions
[Rails] gem ancestry category function implementation
[Ruby on Rails] Comment function implementation
[Rails 6] Like function (synchronous → asynchronous) implementation
[Rails] Comment function implementation procedure memo