I am trying (again) to set up authentications with Rails 4, devise and omniauth.
I tried to follow the example in this post: Rails 4, Devise, Omniauth (with multiple providers)
I have these gems installed:
gem 'devise'
gem 'omniauth-google-oauth2'
gem 'omniauth-facebook'
gem 'omniauth-twitter'
gem 'omniauth-linkedin-oauth2'
gem 'oauth2'
I have a user model, and an authentications model.
I have:
User.rb:
has_many :authentications
def disapprove
self.approved = false
end
def approve
self.approved = true
end
SOCIALS = { facebook: 'Facebook', google_oauth2: 'Google', linkedin: 'Linkedin' }
def self.from_omniauth(auth, current_user)
authentication = Authentication.where(:provider => auth.provider,
:uid => auth.uid.to_s,
:token => auth.credentials.token,
:secret => auth.credentials.secret).first_or_initialize
authentication.profile_page = auth.info.urls.first.last unless authentication.persisted?
if authentication.user.blank?
user = current_user.nil? ? User.where('email = ?', auth['info']['email']).first : current_user
if user.blank?
user = User.new
user.skip_confirmation!
user.password = Devise.friendly_token[0, 20]
user.fetch_details(auth)
user.save
end
authentication.user = user
authentication.save
end
authentication.user
end
def fetch_details(auth)
self.first_name = auth.info.first_name
self.last_name = auth.info.last_name
self.email = auth.info.email
self.image = URI.parse(auth.info.image)
end
Authentication.rb
belongs_to :user
Routes.rb
devise_for :users,
:controllers => {
:registrations => "users/registrations",
:omniauth_callbacks => 'users/omniauth_callbacks',
}
User/registrations_controller
class Users::RegistrationsController < Devise::RegistrationsController
#before_filter :check_permissions , :only => [ :new, :create, :cancel ]
#skip_before_filter :require_no_authentication
def check_permissions
authorize! :create, resource
end
def index
if params[:approved] == "false"
@users = User.find_all_by_approved(false)
else
@users = User.all
end
end
def create
@user = User.new(user_params) #(params[:user])
respond_to do |format|
if resource.save
# Tell the UserMailer to send a welcome email after save
# {@user.send_admin_mail
# @user.send_user_welcome_mail}
format.html { redirect_to(profile_path(@user.profile))}
#, notice: 'We have received your registration. We will be in touch shortly.') }
#format.json { render json: root_path, status: :created, location: @user }
else
#format.html { redirect_to(root_path, alert: 'Sorry! There was a problem with your registration. Please contact us to sort it out.') }
format.html { render action: 'new' }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password )
end
# protected
# def after_sign_up_path_for(resource)
# 'subscribers/new'
# end
end
User/Omniauth_callbacks controller
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
# try again following https://stackoverflow.com/questions/21249749/rails-4-devise-omniauth-with-multiple-providers
def all
user = User.from_omniauth(env['omniauth.auth'], current_user)
if user.persisted?
sign_in user
flash[:notice] = t('devise.omniauth_callbacks.success', :kind => User::SOCIALS[params[:action].to_sym])
if user.sign_in_count == 1
redirect_to profile_path(@user.profile)
else
redirect_to root_path
end
else
session['devise.user_attributes'] = user.attributes
redirect_to root_path
end
end
User::SOCIALS.each do |k, _|
alias_method k, :all
end
end
The devise/new registrations view says:
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<div class="row">
<div class="col-md-3 col-md-offset-3">
<div class="row">
<div class="col-md-12">
<%- if devise_mapping.omniauthable? %>
<div class="facebookauth">
<%= link_to "Join with Facebook", user_omniauth_authorize_path(:facebook) %>
</div>
<% end -%>
</div>
</div>
<div class="row">
<div class="col-md-12">
<%- if devise_mapping.omniauthable? %>
<div class="googleauth">
<%= link_to "Join with Google", user_omniauth_authorize_path(:google_oauth2) %>
</div>
<% end -%>
</div>
</div>
<div class="row">
<div class="col-md-12">
<%- if devise_mapping.omniauthable? %>
<div class="linkedinauth">
<%= link_to "Join with LinkedIn", user_omniauth_authorize_path(:linkedin) %>
</div>
<% end -%>
</div>
</div>
<div class="row">
<div class="col-md-12">
<%- if devise_mapping.omniauthable? %>
<div class="twitterauth">
<%= link_to "Join with Twitter", user_omniauth_authorize_path(:twitter) %>
</div>
<% end -%>
</div>
</div>
</div>
<div class="col-md-5">
<div class="emailform">
<div class="form-inputs", style="margin-left: 7%">
<%= devise_error_messages! %>
<%= f.input :first_name, :label_html => {:class => 'deviselabels'}, autofocus: true, required: false, :input_html => {:maxlength => 15, :size => 40, class: 'lineitemdevise'} %>
<%= f.input :last_name, :label_html => {:class => 'deviselabels'}, required: false, :input_html => {:maxlength => 15, :size => 40, class: 'lineitemdevise'} %>
<%= f.input :email, :label_html => {:class => 'deviselabels'}, required: false, autofocus: false, placeholder: "Please use your work or university address", :input_html => {:maxlength => 55, :size => 40, class: 'lineitemdevise'} %>
<%= f.input :password, :label_html => {:class => 'deviselabels'}, required: false, placeholder: "Minimum 8 characters", :input_html => {:maxlength => 15, :size => 40, class: 'lineitemdevise'} %>
</div>
<div class="form-actions">
<%= f.button :submit, "Join by email", :class => "dcpb" %>
</div>
<% end %>
</div>
</div>
I have another model called profile.rb.
profile belongs_to user
Problems:
The heroku logs error message says:
(facebook) Authentication failure! invalid_credentials: OAuth2::Error, :
2015-11-03T07:05:48.237549+00:00 app[web.1]: {"error":{"message":"Error validating verification code. Please make sure your redirect_uri is identical to the one you used in the OAuth dialog request","type":"OAuthException","code":100,"fbtrace_id":"HD3mnzmSTEw"}}
Also, when I click on the user name, I get an error which says that profile does not exist. The heroku logs say:
(Couldn't find Profile with 'id'=3)
MY NEXT ATTEMPT:
I've changed all of the above and tried again, following the approach in the Railscasts videos.
I now use a user model and an authentications model.
In the user.rb, I have:
has_many :authentications, :dependent => :delete_all
def apply_omniauth(omniauth)
authentications.build(:provider => omniauth['provider'], :uid => omniauth['uid'], :token => auth['credentials']['token'])
end
authentication.rb
belongs_to :user
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_create do | user |
authentication.provider = auth.provider
authentication.uid = auth.uid
authentication.user.first_name = auth.first_name
authentication.user.last_name = auth.last_name
authentication.user.image = auth.info.image
end
end
Authentications_controller:
class AuthenticationsController < ApplicationController
before_action :set_authentication, only: [:destroy]
def index
@authentications = current_user.authentications if current_user
end
def create
omniauth = request.env["omniauth.auth"]
authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
if authentication
sign_in_and_redirect_user(:user, authentication.user.profile)
elsif current_user
current_user.authentications.create!(:provider => omniauth['provider'], :uid => omniauth['uid'])
redirect_to user.profile_url
else
user = User.new
user.omniauth(omniauth)
if user.save!
sign_in_and_redirect_user(:user, user.profile)
else
session[:omniauth] = omniauth.except('extra')
redirect_to new_user_registration_url
end
end
end
def destroy
@authentication.destroy
respond_to do |format|
format.html { redirect_to authentications_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_authentication
@authentication = current_user.authentications.find(params[:id])
end
end
In the routes.rb, I have:
devise_for :users,
:controllers => {
:registrations => "users/registrations",
}
patch '/auth/:provider/callback' => 'authentications#create'
Omniauth.rb
require 'omniauth-facebook' require 'omniauth-google-oauth2'
OmniAuth.config.logger = Rails.logger
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, ENV['FACEBOOK_ID'], ENV['FACEBOOK_KEY'],
:scope => 'public_profile', info_fields: 'id,first_name,last_name,link,email',
:display => 'popup',
:client_options => {:ssl => {:ca_file => '/usr/lib/ssl/certs/ca-certificates.crt'}}
Then when I try this, I get this error:
(facebook) Authentication failure! invalid_credentials: OAuth2::Error, :
2015-11-05T06:4839+00:00 app[web.1]: {"error":{"message":"Error validating verification code. Please make sure your redirect_uri is identical to the one you used in the OAuth dialog request","type":"OAuthException","code":100,"fbtrace_id":"CrvXN22Z"}}
I find the next part of the error message odd because it refers to the callbacks controller which I no longer use (the whole thing is commented out and there is no route for it).
Authentication failure! invalid_credentials: OAuth2::Error, :
2015-11-05T08:24:16.010951+00:00 app[web.1]: Processing by Devise::OmniauthCallbacksController#failure as HTML
2015-11-05T08:24:16.012648+00:00 app[web.1]: Redirected to http://www.dder.com/users/sign_in
A FURTHER ATTEMPT
I have been trying to set up devise with omniauth for more than 1.5 years now. This is my latest attempt (following the Sitepoint tutorial at sitepoint.com/rails-authentication-oauth-2-0-omniauth). I've tried to use this tutorial before and not had any success, so I've made some tweaks to try and adapt it to some aspects of other tutorials on this topic.
I now have:
user.rb
has_many :authentications, :dependent => :delete_all
def apply_omniauth(omniauth)
authentications.build(:provider => omniauth['provider'], :uid => omniauth['uid'], :token => auth['credentials']['token'])
end
authentication.rb
belongs_to :user
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_create do | user |
authentication.provider = auth.provider
authentication.uid = auth.uid
authentication.user.first_name = auth.first_name
authentication.user.last_name = auth.last_name
authentication.user.image = auth.info.image
end
end
authentications controller
class AuthenticationsController < Devise::OmniauthCallbacksController
before_action :set_authentication, only: [:destroy]
def index
@authentications = current_user.authentications if current_user
end
def create
omniauth = request.env["omniauth.auth"]
authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
if authentication
sign_in_and_redirect_user(:user, authentication.user.profile)
elsif current_user
current_user.authentications.create!(:provider => omniauth['provider'], :uid => omniauth['uid'])
redirect_to user.profile_url
else
user = User.new
user.omniauth(omniauth)
if user.save!
sign_in_and_redirect_user(:user, user.profile)
else
session[:omniauth] = omniauth.except('extra')
redirect_to new_user_registration_url
end
end
end
def destroy
@authentication.destroy
respond_to do |format|
format.html { redirect_to authentications_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_authentication
@authentication = current_user.authentications.find(params[:id])
end
end
registrations controller
class Users::RegistrationsController < Devise::RegistrationsController
#before_filter :check_permissions , :only => [ :new, :create, :cancel ]
#skip_before_filter :require_no_authentication
# before_action :configure_permitted_parameters, if: :devise_controller? # Suggestion from Sitepoint tutorial - not currently implemented because not sure about the difference between this and set params.
def check_permissions
authorize! :create, resource
end
def index
if params[:approved] == "false"
@users = User.find_all_by_approved(false)
else
@users = User.all
end
end
def create
super
session[:omniauth] = nil unless @user.new_record?
end
# THIS IS A SUGGESTION FROM SITEPOINT TUTORIAL
# protected
# def configure_permitted_parameters
# devise_parameter_sanitizer.for(:sign_up) << [:first_name, :last_name]
# end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password )
end
def build_resource(*args)
super
if session[:omniauth]
@user.apply_omniauth(session[:omniauth])
@user.valid?
end
end
end
routes
devise_for :users,
:controllers => {
:registrations => "users/registrations",
:omniauth_callbacks => "authentications"
# :omniauth_callbacks => 'users/omniauth_callbacks',
}
get '/auth/:provider/callback' => 'authentications#create'
I can check these routes with:
rake routes | grep auth
user_omniauth_authorize GET|POST /users/auth/:provider(.:format) authentications#passthru {:provider=>/facebook|linkedin|twitter|google_oauth2/}
user_omniauth_callback GET|POST /users/auth/:action/callback(.:format) authentications#:action
GET /auth/:provider/callback(.:format) authentications#create
new registration partial in the view
<%- if devise_mapping.omniauthable? %>
<div class="twitterauth">
<%= link_to "Join with Twitter", user_omniauth_authorize_path(:twitter) %>
</div>
<% end -%>
I'm really not sure where this path is coming from. Not sure why it's named as it is.
new session partial in the view
<%- if devise_mapping.omniauthable? %>
<div class="twitterauth">
<%= link_to "Login with Twitter", user_omniauth_authorize_path(:twitter) %>
</div>
<% end -%>
Current error:
AbstractController::ActionNotFound at /users/auth/twitter/callback
The action 'twitter' could not be found for AuthenticationsController
Devise is the cornerstone gem for Ruby on Rails authentication. With Devise, creating a User that can log in and out of your application is so simple because Devise takes care of all the controllers necessary for user creation ( users_controller ) and for user sessions ( users_sessions_controller ).
Our out-of-the box Devise setup is now working with Rails 7. Once again, if you'd like to refer to any of the code for this setup, or use the template wholesale for a new app, the code is available on GitHub, and you may also use it as a template repo to kick off your own Rails 7 devise projects.
Devise offers authentication helper functions for Rails controllers and views. They include: authenticate_user! : This helper function denies controller access to unauthenticated users. user_signed_in? : This returns a boolean that indicates whether or not a user is signed in.
Devise comes with an out of the box solution for integration with omniuth. You can checkout these urls: 1. https://www.digitalocean.com/community/tutorials/how-to-configure-devise-and-omniauth-for-your-rails-application This one shows integration with DigitalOcean but can be extended to others. 2. https://github.com/plataformatec/devise/wiki/OmniAuth%3A-Overview This one is from devise wiki
Hope it helps
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With