This time, I will explain how to implement the new registration / login function in SNS authentication by devise in Rails application.
-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)
・ 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.
Please refer to the above article as I wrote at the beginning.
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
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
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.
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.
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
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.
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.
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.
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.
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.
[Rails] SNS authentication (Twitter, Facebook, google)
[Rails] SNS authentication registration procedure (Twitter, Facebook, google)
Recommended Posts