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.
Ruby on Rails '6.0.0' Ruby '2.6.5'
--User login function has been implemented using devise (gem). -External API has been set from facebook for developers and Google Cloud Platform.
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']
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
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
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.
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>
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
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.
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! !!
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