Last active
June 13, 2021 08:55
-
-
Save secretpray/4eb63d367dfb0553aa96da4b3f34c8a4 to your computer and use it in GitHub Desktop.
Omniauth (Facebook, Github, Google) with confirmation real email with Devise
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
================= | |
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