[RUBY] [Rails] Implementation of SNS authentication (Twitter, Facebook, Google) function

Introduction

This time, I will explain how to implement the new registration / login function in SNS authentication by devise in Rails application.

Prerequisites

-Implemented user management function by devise ・ Registered external API for SNS authentication

The following article is easy to understand for the registration procedure of the external API.

[Rails] SNS authentication registration procedure (Twitter, Facebook, google)

Functional specifications

・ Press Twitter/Facebook/Google registration to start SNS authentication, and user registration will start with your nickname and email address entered.

・ A password is automatically generated for new registration with SNS authentication, and new registration is possible.

procedure

1) API settings

Please refer to the above article as I wrote at the beginning.

2) Implement SNS authentication in Rails app

2-1) Install Gem

Gemfile


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

#omniauth authentication is installed as a countermeasure because CSRF vulnerability has been pointed out.
gem 'omniauth-rails_csrf_protection'
#Installed to manage environment variables(vim ~/.Can also be defined in zshrc)
gem 'dotenv-rails'

Don't forget to write it in Gemrile and do bundle install.

Please refer to the following for dotenv-rails.

How to apply Rails environment variables to Docker container (aws :: Sigv4 :: Errors solution)

Terminal


% bundle install

2-2) Setting environment variables

Terminal


% vim ~/.zshrc

#Press i to enter insert mode
export TWITTER_API_KEY = 'ID you wrote down'
export TWITTER_API_SECRET_KEY = 'Noted SECRET'
export FACEBOOK_API_KEY = 'ID you wrote down'
export FACEBOOK_API_SECRET_KEY = 'Noted SECRET'
export GOOGLE_API_KEY='ID you wrote down'
export GOOGLE_API_SECRET_KEY='Noted SECRET'

#Once defined, esc →:Save as wq

After saving, execute the following command to reflect the settings.

Terminal


% source ~/.zshrc

If you have gem dotenv-rails installed, create a .env file in the app directory and write it in that file.

.env


TWITTER_API_KEY = 'ID you wrote down'
TWITTER_API_SECRET_KEY = 'Noted SECRET'
FACEBOOK_API_KEY = 'ID you wrote down'
FACEBOOK_API_SECRET_KEY = 'Noted SECRET'
GOOGLE_API_KEY = 'ID you wrote down'
GOOGLE_API_SECRET_KEY = 'Noted SECRET'

When you're done, add .env to the gitignore file to avoid pushing.

gitignore


/.env

2-3) Read environment variables on the application side

Edit the config/initializers/devise.rb file.

config/initializers/devise.rb


Devise.setup do |config|
  #abridgement
  config.omniauth :twitter,ENV['TWITTER_API_KEY'],ENV['TWITTER_API_SECRET_KEY']
  config.omniauth :facebook,ENV['FACEBOOK_API_KEY'],ENV['FACEBOOK_API_SECRET_KEY']
  config.omniauth :google_oauth2,ENV['GOOGLE_API_KEY'],ENV['GOOGLE_API_SECRET_KEY']
end

That's all for setting environment variables.

3) Server-side implementation of SNS authentication function

3-1) Creating a model for SNS authentication

At the time of SNS authentication, a request is sent to the API to authenticate. Therefore, it is necessary to ** create a table for SNS authentication separately from the users table **.

Terminal


% rails g model sns_credential 

db/migrate/XXXXXXXXXXX_crate_sns_credentials.rb


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

     t.timestamps
   end
 end
end

After editing, run rails db: migrate.

3-2) Editing User model and SnsCredential model

We will edit it so that OmniAuth can be used with devise.

app/models/user.rb


class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:twitter, :facebook, :google_oauth2]

has_many :sns_credentials

app/models/sns_credential.rb


class SnsCredential < ApplicationRecord
 belongs_to :user
end

3-3) devise controller settings

Execute the following command in the terminal to create a devise controller.

Terminal


% rails g devise:controlers users

After creating the controller, configure devise's routing.

config/routes.rb


Rails.application.routes.draw do
 devise_for :users, controllers: {
   omniauth_callbacks: 'users/omniauth_callbacks',
   registrations: 'users/registrations'
 }
  root to: 'users#index'
end

At this point, preparations for realizing SNS authentication are complete. Let's do our best.

4) Implementation of method for SNS authentication

4-1) Method implementation

OmniAuth GitHub

Google-auth2 GitHub

As you can see in the above document, we will define the method in the devise controller.

app/controllers/users/omniauth_callbacks_controller.rb


class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

 def twitter
  authorization
 end

 def facebook
  authorization
 end

 def google_oauth2
  authorization
 end

 private

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

Invoke the next defined action in the view.

app/views/users/new.html.erb


<%= link_to 'Register on Twitter', user_twitter_omniauth_authorize_path, method: :post%>
<%= link_to 'Register on Facebook', user_facebook_omniauth_authorize_path, method: :post%>
<%= link_to 'Register with Google', user_google_oauth2_omniauth_authorize_path, method: :post%>

Then create a method in the User model.

app/models/usr.rb


class User < ApplicationRecord
 # Include default devise modules. Others available are:
 # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
 devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:facebook, :google_oauth2]

 has_many :sns_credentials
#Define a class method
 def self.from_omniauth(auth)
  #If you can define it, "binding".Let's write "pry" and check if information can be obtained from SNS
 end
end

If you register with the authentication button, the process will stop, so enter auth in the terminal and check if you can get the information.

After confirming, we will describe the contents of the method.

app/models/user.rb


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

For processing, the first_or_create method is used to determine whether to save to the DB.

Next, add a description to search the DB when SNS authentication has not been performed (in the case of new registration).

app/models/user.rb


def self.from_omniauth(auth)
   sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
   #If you have sns authentication, get it by association
   #If not, search the user by email and get or build(Do not save)
   user = User.where(email: auth.info.email).first_or_initialize(
     nickname: auth.info.name,
       email: auth.info.email
   )
 end

By performing a search using the first_or_initialize method, you can process so that new records are not saved in the DB.

4-2) Describe the process from the User model

Following the flow of MVC, we will describe the processing of the model with the controller.

app/controllers/users/omniauth_callbacks_controller.rb



#abridgement
   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
#abridgement

Up to this point, the implementation of the new registration function has been completed.

5) Implementation of login function

5-1) Editing the User model

I will describe the process at login.

app/models/user.rb


def self.from_omniauth(auth)
   sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
   #If you have sns authentication, get it by association
   #If not, search the user by email and get or build(Do not save)
   user = User.where(email: auth.info.email).first_or_initialize(
     nickname: auth.info.name,
       email: auth.info.email
   )
#Add the following
   #Determine if user is registered
   if user.persisted?
     sns.user = user
     sns.save
   end
   { user: user, sns: sns }
 end

Then edit the view. OmniAuth ** has both new registration and login **, so the path is the same.

ruby:app/views/devise/sessions/new.html.erb


<%= link_to 'Login with Twitter', user_twitter_omniauth_authorize_path, method: :post%>
<%= link_to 'Login with Facebook', user_facebook_omniauth_authorize_path, method: :post%>
<%= link_to 'Login with google', user_google_oauth2_omniauth_authorize_path, method: :post%>

This completes the implementation of the login function.

Finally, we will implement it so that you do not have to enter the password at the time of SNS authentication.

5-2) Implementation of processing for password input

Add the option optional: true to the sns_credential model. With this option, you can save without a foreign key value.

app/models/sns_credential.rb


class SnsCredential < ApplicationRecord
 belongs_to :user, optional: true
end

Added the following description to the controller

app/controllers/users/omniauth_callbacks_controller.rb


Class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
 #Omission
 def authorization
   sns_info = User.from_omniauth(request.env["omniauth.auth"])
# @with user@sns_Add id
   @user = sns_info[:user]

   if @user.persisted?
     sign_in_and_redirect @user, event: :authentication
   else
     @sns_id = sns_info[:sns].id
     render template: 'devise/registrations/new'
   end
 end

end

In the password form of the view file, describe the conditional branch of whether SNS authentication is performed.

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



 <%if @sns_id.present? %>
   <%= hidden_field_tag :sns_auth, true %>
 <% else %>
   <div class="field">
     <%= f.label :password %>
     <% @minimum_password_length %>
     <em>(<%= @minimum_password_length %> characters minimum)</em>
     <br />
     <%= f.password_field :password, autocomplete: "new-password" %>
   </div>

   <div class="field">
     <%= f.label :password_confirmation %><br />
     <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
   </div>
 <% end %>

Finally, uncomment and write the following so that the create action of devise is activated.

app/controllers/users/registrations_controller.rb


class Users::RegistrationsController < Devise::RegistrationsController
 # before_action :configure_sign_up_params, only: [:create]
 # before_action :configure_account_update_params, only: [:update]


 # GET /resource/sign_up
 # def new
 #   super
 # end

 # POST /resource
 def create
   if params[:sns_auth] == 'true'
     pass = Devise.friendly_token
     params[:user][:password] = pass
     params[:user][:password_confirmation] = pass
   end
   super
 end
#abridgement

By writing like this, you can save the values ​​sent from params from the view file.

This completes the implementation of the SNS authentication function.

It was my first time to use it in my app, so I created it with the intention of recording it as a memorandum. If its helpful then im happy.

References

OmniAuth GitHub

Google-auth2 GitHub

[Rails] SNS authentication (Twitter, Facebook, google)

[Rails] SNS authentication registration procedure (Twitter, Facebook, google)

Recommended Posts

[Rails] Implementation of SNS authentication (Twitter, Facebook, Google) function
[Rails 6] Implementation of new registration function by SNS authentication (Facebook, Google)
[Rails 6] Implementation of SNS (Twitter) sharing function
[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] Google, Twitter, Facebook authentication using Devise and Omniauth
[Rails] Implementation of CSV import function
[Rails] Asynchronous implementation of like 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 (2)
Implementation of user authentication function using devise (1)
Rails [For beginners] Implementation of comment function
[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] Implementation of image enlargement function using lightbox2
Implementation of search function
Rails search function implementation
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 Ruby on Rails login function (devise edition)
[Ruby on Rails] Implementation of tagging function/tag filtering function
[Rails] Implementation of multi-layer category function using ancestry "Preparation"
Rails implementation of ajax removal
Implementation of sequential search function
Implementation of like function (Ajax)
Implementation of image preview function
Implementation of category pull-down function
[Rails 6] Pagination function implementation (kaminari)
[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 new registration function in wizard format using devise
[Rails] Implementation of user logic deletion
Kaminari --Added pagination function of Rails
[Implementation procedure] Create a user authentication function using sorcery in Rails
[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
[Rails] Implementation of tag function using acts-as-taggable-on and tag input completion function using tag-it
Implementation of like function in Java
[Rails] I will explain the implementation procedure of the follow function using form_with.
[Rails] Addition of Ruby On Rails comment function
Rails Addition of easy and easy login function
[Ruby on Rails] Follow function implementation: Bidirectional
Rails Basic CRUD function implementation procedure scaffold
[Rails] Implementation of validation that maintains uniqueness
Rails6 OmniAuth twitter Authentication Email conditional branch