Skip to content

Instantly share code, notes, and snippets.

@secretpray
Last active June 13, 2021 08:55
Show Gist options
  • Save secretpray/4eb63d367dfb0553aa96da4b3f34c8a4 to your computer and use it in GitHub Desktop.
Save secretpray/4eb63d367dfb0553aa96da4b3f34c8a4 to your computer and use it in GitHub Desktop.
Omniauth (Facebook, Github, Google) with confirmation real email with Devise
=================
1. Add to Gemfile
=================
gem 'omniauth'
gem 'omniauth-facebook'
gem 'omniauth-github'
gem 'omniauth-google-oauth2'
gem 'activerecord-session_store'
gem 'omniauth-rails_csrf_protection'
group :development, :test do
gem 'letter_opener'
gem 'capybara-email'
end
======================
2. Create credentials
======================
EDITOR="atom --wait" rails credentials:edit
like this
development:
facebook:
facebook_client_id: 123232323232323
facebook_client_secret: acc22323232323
github:
github_client_id: 1722232323232
github_client_secret: 40343434343434343434343434
google:
google_client_id: 727232323232323.apps.googleusercontent.com
google_client_secret: tk343434343434
test:
facebook:
facebook_client_id: 123
facebook_client_secret: 456
github:
github_client_id: 123
github_client_secret: 456
google:
google_client_id: 123
google_client_secret: 456
production:
facebook:
facebook_client_id:
facebook_client_secret:
github:
github_client_id:
github_client_secret:
google:
google_client_id:
google_client_secret:
# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: 0b278118887887878......
save&quit
=======
3.EDIT
=======
config/routes.rb
----------------
devise_for :users, controllers: { omniauth_callbacks: 'oauth_callbacks', confirmations: 'confirmations' }
=================
4. Add migrations
=================
bin/rails g migration CreateAuthorizations
---------------------------------------------------
class CreateAuthorizations < ActiveRecord::Migration[6.0]
def change
create_table :authorizations do |t|
t.references :user, null: false, foreign_key: true
t.string :provider
t.string :uid
t.timestamps
end
add_index :authorizations, %i[provider uid], unique: true
end
end
bin/rails g migration AddConfirmableToDevise
-------------------------------------------------------
class AddConfirmableToDevise < ActiveRecord::Migration[6.0]
def up
add_column :users, :confirmation_token, :string
add_column :users, :confirmed_at, :datetime
add_column :users, :confirmation_sent_at, :datetime
add_index :users, :confirmation_token, unique: true
end
def down
remove_columns :users, :confirmation_token, :confirmed_at, :confirmation_sent_at
end
end
bin/rails g migration CreateSessions
--------------------------------------------
class CreateSessions < ActiveRecord::Migration[6.1]
def change
create_table :sessions do |t|
t.string :session_id, null: false
t.text :data
t.timestamps
end
add_index :sessions, :session_id, unique: true
add_index :sessions, :updated_at
end
end
==============
6. Controller
==============
oauth_callbacks_controller.rb
-----------------------------
class OauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
authorization('Google')
end
def facebook
authorization('Facebook')
end
def github
authorization('GitHub')
end
def authorization(provider)
# render json: request.env['omniauth.auth']
@user = User.find_for_oauth(request.env['omniauth.auth'])
if @user&.persisted? && [email protected]?('uid_')
sign_in_and_redirect @user, event: :authentication
set_flash_message(:notice, :success, kind: provider) if is_navigational_format?
elsif @user&.persisted? && @user.email.include?('uid_')
render 'confirmations/email', locals: { resource: @user }
else
flash[:alert] = 'There was a problem signing you in through Facebook. Please register or try signing in later.'
# redirect_to new_user_registration_url
redirect_to root_path
end
end
def failure
flash[:alert] = 'There was a problem signing you in. Please register or try signing in later.'
redirect_to new_user_registration_url
end
end
confirmations_controller.rb
---------------------------
class ConfirmationsController < Devise::ConfirmationsController
before_action :find_user, only: [:create]
def create
if @user.update(user_params)
@user.send_confirmation_instructions
redirect_to root_path, alert: 'Please confirm your email'
else
going_wrong
end
end
private
def after_confirmation_path_for(resource_name, resource)
if signed_in?(@user)
signed_in_root_path(@user)
else
new_session_path(@user)
end
end
def going_wrong
if params[:user][:email].blank?
flash.now[:alert] = 'Email can not be blank!'
render 'confirmations/email', locals: { resource: @user }
else
multiple_accounts
end
end
def multiple_accounts
@old_user = User.find_by(email: params[:user][:email])
@authorization = Authorization.find_by(user_id: @user).update(user_id: @old_user.id)
@user.delete
@old_user.update(confirmed_at: nil)
@old_user.send_confirmation_instructions
redirect_to new_session_path(@old_user), notice: "Welcome back, #{@old_user.email}!"
end
def find_user
@user = User.find(params[:user_id])
end
def user_params
params.require(:user).permit(:email)
end
end
=========
7. Model
=========
authorization.rb
----------------
class Authorization < ApplicationRecord
belongs_to :user
validates :provider, :uid, presence: true
validates :uid, uniqueness: { scope: :provider }
end
user.rb
-------
class User < ApplicationRecord
devise :database_authenticatable,
:registerable, :confirmable,
:recoverable, :rememberable,
:validatable, :omniauthable,
omniauth_providers: %i[facebook github google_oauth2]
has_many :authorizations, dependent: :destroy
...
def self.find_for_oauth(auth)
FindForOauthService.new(auth).call
end
...
============
8. Servises
============
Create folder 'app/services'
Create file 'app/services/find_for_oauth_service.rb'
----------------------------------------------------
class FindForOauthService
attr_reader :auth
def initialize(auth)
@auth = auth
end
def call
authorization = Authorization.where(provider: auth.provider, uid: auth.uid).first
# authorization = Authorization.find_by(provider: auth.provider, uid: auth.uid)
return authorization.user if authorization
email_auth
user = User.where(email: email_auth).first
# user = User.find_by(email: email_auth)
if user
user.authorizations.create!(provider: auth.provider, uid: auth.uid)
user
else
create_user!(auth)
end
end
def email_auth
if auth.info[:email].blank?
"uid_#{auth.uid}@#{auth.provider.downcase}.com"
else
auth.info[:email]
end
end
private
def create_user!(auth)
password = Devise.friendly_token[0, 20]
if email_auth.include?('uid_')
user = User.new(email: email_auth, password: password, password_confirmation: password)
user.skip_confirmation_notification!
user.save
else
user = User.create!(email: email_auth, password: password, password_confirmation: password)
end
Authorization.transaction do
user.authorizations.create!(provider: auth.provider, uid: auth.uid)
end
user
end
end
================
9. Add and edit
================
config/initializers/devise.rb
-----------------------------
28...#(optional)
# config.mailer_sender = Rails.application.credentials[:email][:sender]
config.mailer_sender = '[email protected]'
162...near
config.reconfirmable = false
273...near
# ==> OmniAuth
# Add a new OmniAuth provider. Check the wiki for more information on setting
# up on your models and hooks.
# config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
config.omniauth :facebook,
Rails.application.credentials.dig(Rails.env.to_sym, :facebook, :facebook_client_id),
Rails.application.credentials.dig(Rails.env.to_sym, :facebook, :facebook_client_secret),
scope: 'email, public_profile', info_fields: 'first_name, last_name'
config.omniauth :github,
Rails.application.credentials.dig(Rails.env.to_sym, :github, :github_client_id),
Rails.application.credentials.dig(Rails.env.to_sym, :github, :github_client_secret),
scope: 'user:email, read:user'
config.omniauth :google_oauth2,
Rails.application.credentials.dig(Rails.env.to_sym, :google, :google_client_id),
Rails.application.credentials.dig(Rails.env.to_sym, :google, :google_client_secret),
scope: 'userinfo.email, userinfo.profile'config.reconfirmable = false
...
config/initializers/session_store.rb
------------------------------------
Rails.application.config.session_store :active_record_store, key: '_devise-omniauth_session'
config/application.rb
---------------------
config.autoload_paths += [config.root.join('app')] # Not needed for Rails 6+ (optional)
db/seeds.rb
-----------
User.update_all confirmed_at: DateTime.now
lib/tasks/updated_confirmed_at_users.rake
------------------------------------------
# rake updated_confirmed_at_users
task :updated_confirmed_at_users => :environment do
User.all.each do |user|
user.update(confirmed_at: DateTime.now)
end
end
PS: Update old User -> in Terminal:
rake updated_confirmed_at_users
==========
10. Views
=========
Button sample slim template [for info only]
------------------------------------
hr/
h5 Login in with:
.text-center
= link_to user_google_oauth2_omniauth_authorize_path, method: :post, class: "btn btn-light btn-block bg-light text-dark" do
svg.native.svg-icon.iconGoogle aria-hidden="true" height="24" viewbox=("0 0 18 18") width="24"
path d=("M16.51 8H8.98v3h4.3c-.18 1-.74 1.48-1.6 2.04v2.01h2.6a7.8 7.8 0 002.38-5.88c0-.57-.05-.66-.15-1.18z") fill="#4285F4"
path d=("M8.98 17c2.16 0 3.97-.72 5.3-1.94l-2.6-2a4.8 4.8 0 01-7.18-2.54H1.83v2.07A8 8 0 008.98 17z") fill="#34A853"
path d=("M4.5 10.52a4.8 4.8 0 010-3.04V5.41H1.83a8 8 0 000 7.18l2.67-2.07z") fill="#FBBC05"
path d=("M8.98 4.18c1.17 0 2.23.4 3.06 1.2l2.3-2.3A8 8 0 001.83 5.4L4.5 7.49a4.77 4.77 0 014.48-3.3z") fill="#EA4335"
span.ms-2 Google
= link_to user_facebook_omniauth_authorize_path, method: :post, :data => {turbo: "false"}, class: "btn btn-light btn-block bg-light text-dark" do
svg.svg-icon.iconFacebook aria-hidden="true" height="24" viewbox=("0 0 18 18") width="24"
path d=("M3 1a2 2 0 00-2 2v12c0 1.1.9 2 2 2h12a2 2 0 002-2V3a2 2 0 00-2-2H3zm6.55 16v-6.2H7.46V8.4h2.09V6.61c0-2.07 1.26-3.2 3.1-3.2.88 0 1.64.07 1.87.1v2.16h-1.29c-1 0-1.19.48-1.19 1.18V8.4h2.39l-.31 2.42h-2.08V17h-2.5z") fill="#4167B2"
span.ms-2 Facebook
= link_to user_github_omniauth_authorize_path, method: :post, class: "btn btn-light btn-block bg-light text-dark" do
svg.svg-icon.iconGitHub aria-hidden="true" height="24" viewbox=("0 0 18 18") width="24"
path d=("M9 1a8 8 0 00-2.53 15.59c.4.07.55-.17.55-.38l-.01-1.49c-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.42 7.42 0 014 0c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48l-.01 2.2c0 .21.15.46.55.38A8.01 8.01 0 009 1z") fill="#010101"
span.ms-2 Github
hr/
Or in app/views/devise/shared/_links.html.erb
---------------------------------------------
<%- if devise_mapping.omniauthable? %>
<%#- resource_class.omniauth_providers.each do |provider| %>
<%#= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %><br />
<%# end %>
<hr>
<h5>Login in with:</h5>
<div class='text-center'>
<%= link_to user_google_oauth2_omniauth_authorize_path, method: :post, class: "btn btn-light btn-block bg-light text-dark" do %>
<svg aria-hidden="true" class="native svg-icon iconGoogle" width="24" height="24" viewBox="0 0 18 18">
<path d="M16.51 8H8.98v3h4.3c-.18 1-.74 1.48-1.6 2.04v2.01h2.6a7.8 7.8 0 002.38-5.88c0-.57-.05-.66-.15-1.18z" fill="#4285F4"></path>
<path d="M8.98 17c2.16 0 3.97-.72 5.3-1.94l-2.6-2a4.8 4.8 0 01-7.18-2.54H1.83v2.07A8 8 0 008.98 17z" fill="#34A853"></path>
<path d="M4.5 10.52a4.8 4.8 0 010-3.04V5.41H1.83a8 8 0 000 7.18l2.67-2.07z" fill="#FBBC05"></path>
<path d="M8.98 4.18c1.17 0 2.23.4 3.06 1.2l2.3-2.3A8 8 0 001.83 5.4L4.5 7.49a4.77 4.77 0 014.48-3.3z" fill="#EA4335"></path>
</svg>
<span class='ms-2'> Google</span>
<% end %>
<%= link_to user_facebook_omniauth_authorize_path, method: :post, :data => {turbo: "false"}, class: "btn btn-light btn-block bg-light text-dark" do %>
<svg aria-hidden="true" class="svg-icon iconFacebook" width="24" height="24" viewBox="0 0 18 18">
<path d="M3 1a2 2 0 00-2 2v12c0 1.1.9 2 2 2h12a2 2 0 002-2V3a2 2 0 00-2-2H3zm6.55 16v-6.2H7.46V8.4h2.09V6.61c0-2.07 1.26-3.2 3.1-3.2.88 0 1.64.07 1.87.1v2.16h-1.29c-1 0-1.19.48-1.19 1.18V8.4h2.39l-.31 2.42h-2.08V17h-2.5z" fill="#4167B2"></path>
</svg>
<span class='ms-2'> Facebook</span>
<% end %>
<%= link_to user_github_omniauth_authorize_path, method: :post, class: "btn btn-light btn-block bg-light text-dark" do %>
<svg aria-hidden="true" class="svg-icon iconGitHub" width="24" height="24" viewBox="0 0 18 18">
<path d="M9 1a8 8 0 00-2.53 15.59c.4.07.55-.17.55-.38l-.01-1.49c-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.42 7.42 0 014 0c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48l-.01 2.2c0 .21.15.46.55.38A8.01 8.01 0 009 1z" fill="#010101"></path>
</svg>
<span class='ms-2'> Github</span>
<% end %>
</div>
<hr>
<% end %>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment