Rails, Devise & Omniauth - problems with setup - ruby-on-rails

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:
None of this works. When I click on each of the social media login links, the page just jumps to the sign up by email form.
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"}}
When I complete the sign up by email form with an email and password, the user name is recognised (in that the navbar says Hello , but when I go into the rails console, the user is not listed.
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)
Is there another step required to make the social media registrations work to create a new user?
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 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

There is a gem called dom that was made exclusively to manage Devise with multiple providers. It makes things deadly simple!
Also, I think you should read these articles. I'm sure you can solve all your questions with them:
Devise OmniAuth: Overview
OmniAuth Managing multiple provaders

Related

Rails 4 - Devise Omniauth and allowing a single user to authenticate with multiple social media strategies

I am trying to make an app with Rails 4.
I have been trying (for 3+ years) to figure out how to get Devise and Omniauth to works, so that users can add multiple social media accounts to their user profile.
I've read all of the devise and omniauth documentation. The best I can get to with those docs is to add 1 single social media account. That's not what I want.
I've tried this site point tutorial
sitepoint.com/rails-authentication-oauth-2-0-omniauth
I've tried this willschenck tutorial
http://willschenk.com/setting-up-devise-with-twitter-and-facebook-and-other-omniauth-schemes-without-email-addresses/
I've tried this jorge.caballeromurillo tutorial: http://jorge.caballeromurillo.com/multiple-omniauth-providers-for-same-user-on-ruby-on-rails/
I've also tried this sourcey tutorial: http://sourcey.com/rails-4-omniauth-using-devise-with-twitter-facebook-and-linkedin/
I've pledged thousands of points in bounties on SO in trying to find help with this problem - but not yet figured it out. I've been to every rails meetup in my area for the last 3 years and wasted $$$ on codementor in trying to find help. Enough time has passed since the most recent frustrating attempt to be ready to give it another go. Please help.
Here's what I have so far:
User.rb
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable,
:confirmable, :lockable,
# :zxcvbnable,
:omniauthable, :omniauth_providers => [:facebook, :linkedin, :twitter, :google_oauth2 ]
has_many :identities, dependent: :destroy
def self.find_for_oauth(auth, signed_in_resource = nil)
# Get the identity and user if they exist
identity = Identity.find_for_oauth(auth)
# If a signed_in_resource is provided it always overrides the existing user
# to prevent the identity being locked with accidentally created accounts.
# Note that this may leave zombie accounts (with no associated identity) which
# can be cleaned up at a later date.
user = signed_in_resource ? signed_in_resource : identity.user
# p '11111'
# Create the user if needed
if user.nil?
# p 22222
# Get the existing user by email if the provider gives us a verified email.
# If no verified email was provided we assign a temporary email and ask the
# user to verify it on the next step via UsersController.finish_signup
email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email)
email = auth.info.email if email_is_verified # take out this if stmt for chin yi's solution
user = User.where(:email => email).first if email
# Create the user if it's a new registration
if user.nil?
# p 33333
user = User.new(
# at least one problem with this is that each provider uses different terms to desribe first name/last name/email. See notes on linkedin above
first_name: auth.info.first_name,
last_name: auth.info.last_name,
email: email ? email : "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
#username: auth.info.nickname || auth.uid,
password: Devise.friendly_token[0,20])
# fallback for name fields - add nickname to user table
# debugger
# if email_is_verified
user.skip_confirmation!
# end
# user.skip_confirmation!
user.save!
end
end
# Associate the identity with the user if needed
if identity.user != user
identity.user = user
identity.save!
end
user
end
def email_verified?
self.email && TEMP_EMAIL_REGEX !~ self.email
end
Identity.rb
belongs_to :user
validates_presence_of :uid, :provider
validates_uniqueness_of :uid, :scope => :provider
def self.find_for_oauth(auth)
find_or_create_by(uid: auth.uid, provider: auth.provider)
end
Users controller:
class UsersController < ApplicationController
before_action :set_user, only: [ :show, :edit, :update, :finish_signup, :destroy]
def index
# if params[:approved] == "false"
# #users = User.find_all_by_approved(false)
# else
#users = User.all
authorize #users
# end
end
# GET /users/:id.:format
def show
# authorize! :read, #user
end
# GET /users/:id/edit
def edit
# authorize! :update, #user
authorize #user
end
# PATCH/PUT /users/:id.:format
def update
# authorize! :update, #user
respond_to do |format|
authorize #user
if #user.update(user_params)
sign_in(#user == current_user ? #user : current_user, :bypass => true)
# I'm trying to get the user matched to an organisation after the email address (in finish sign up) updates the user model.
UserOrganisationMapperService.call(#user)
format.html { redirect_to #user }#, notice: 'Your profile was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
# GET/PATCH /users/:id/finish_signup
def finish_signup
# authorize! :update, #user
if request.patch? && params[:user] #&& params[:user][:email]
if #user.update(user_params)
#user.skip_reconfirmation!
# #user.confirm!
sign_in(#user, :bypass => true)
redirect_to root_path#, notice: 'Your profile was successfully updated.'
# redirect_to [#user, #user.profile || #user.build_profile]
# sign_in_and_redirect(#user, :bypass => true)
else
#show_errors = true
end
end
end
# DELETE /users/:id.:format
def destroy
# authorize! :delete, #user
#user.destroy
authorize #user
respond_to do |format|
format.html { redirect_to root_url }
format.json { head :no_content }
end
end
private
def set_user
#user = User.find(params[:id])
authorize #user
end
def user_params
# params.require(:user).permit(policy(#user).permitted_attributes)
accessible = [ :first_name, :last_name, :email, :avatar, {role_ids: []} ] # extend with your own params
accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
# accessible << [:approved] if user.admin
params.require(:user).permit(accessible)
end
end
Identities controller
class IdentitiesController < ApplicationController
before_action :set_identity, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
# GET /identities
# GET /identities.json
def index
#identities = Identity.all
end
# GET /identities/1
# GET /identities/1.json
def show
end
# GET /identities/new
def new
#identity = Identity.new
end
# GET /identities/1/edit
def edit
end
# POST /identities
# POST /identities.json
def create
#identity = Identity.new(identity_params)
respond_to do |format|
if #identity.save
format.html { redirect_to #identity, notice: 'Identity was successfully created.' }
format.json { render :show, status: :created, location: #identity }
else
format.html { render :new }
format.json { render json: #identity.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /identities/1
# PATCH/PUT /identities/1.json
create alternative that I have also tried
def create
auth = request.env['omniauth.auth']
# Find an identity here
#identity = Identity.find_with_omniauth(auth)
if #identity.nil?
# If no identity was found, create a brand new one here
#identity = Identity.create_with_omniauth(auth)
end
if signed_in?
if #identity.user == current_user
# User is signed in so they are trying to link an identity with their
# account. But we found the identity and the user associated with it
# is the current user. So the identity is already associated with
# this user. So let's display an error message.
redirect_to root_url, notice: "Already linked that account!"
else
# The identity is not associated with the current_user so lets
# associate the identity
#identity.user = current_user
#identity.save
redirect_to root_url, notice: "Successfully linked that account!"
end
else
if #identity.user.present?
# The identity we found had a user associated with it so let's
# just log them in here
self.current_user = #identity.user
redirect_to root_url, notice: "Signed in!"
else
# No user associated with the identity so we need to create a new one
redirect_to new_registration_path, notice: "Please finish registering"
end
end
end
def update
respond_to do |format|
if #identity.update(identity_params)
format.html { redirect_to #identity, notice: 'Identity was successfully updated.' }
format.json { render :show, status: :ok, location: #identity }
else
format.html { render :edit }
format.json { render json: #identity.errors, status: :unprocessable_entity }
end
end
end
# DELETE /identities/1
# DELETE /identities/1.json
def destroy
#identity.destroy
respond_to do |format|
format.html { redirect_to identities_url, notice: 'Identity was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_identity
#identity = Identity.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def identity_params
params[:identity]
end
end
registrations controller
class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_permitted_parameters, if: :devise_controller?
def create
super do |resource|
UserOrganisationMapperService.call(resource)
end
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:email, :password, :first_name, :last_name) }
end
private
end
omniauth callbacks controller
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
#sourcey tutorial ------------------
def self.provides_callback_for(provider)
class_eval %Q{
def #{provider}
#user = User.find_for_oauth(env["omniauth.auth"], current_user)
if #user.persisted?
sign_in_and_redirect #user, event: :authentication
else
session["devise.#{provider}_data"] = env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
}
end
[:twitter, :facebook, :linkedin, :google_oauth2].each do |provider|
provides_callback_for provider
end
end
users/finish sign up view
<div class="container-fluid">
<div class="row">
<div class="col-xs-8 col-xs-offset-2">
<h1 class="formheader">Complete your registration</h1>
<%= form_for(current_user, :as => 'user', :url => finish_signup_path(current_user), :html => { role: 'form'}) do |f| %>
<% if #show_errors && current_user.errors.any? %>
<div id="error_explanation">
<% current_user.errors.full_messages.each do |msg| %>
<%= msg %><br>
<% end %>
</div>
<% end %>
<div class="form-group">
<!-- f.label :false -->
<div class="controls">
<% if current_user.first_name.blank? %>
<%= f.text_field :first_name, :value => '', class: 'form-control input-lg', placeholder: 'First name' %>
<p class="help-block">Hi there, what is your first name?.</p>
<% end %>
<% if current_user.last_name.blank? %>
<%= f.text_field :last_name, :value => '', class: 'form-control input-lg', placeholder: 'Last name (surname)' %>
<p class="help-block">Add your last name</p>
<% end %>
<% if !current_user.email_verified? %>
<%= f.text_field :email, :value => '', class: 'form-control input-lg', placeholder: 'Example: email#me.com -- use your primary work or university address' %>
<p class="help-block">Please confirm your email address. No spam.</p>
<% end %>
</div>
</div>
<div class="actions">
<%= f.submit 'Continue', :class => 'btn btn-primary' %>
</div>
<% end %>
</div>
</div>
</div>
users/authentications view
<div class="container-fluid">
<div class="row">
<div class="col-xs-8 col-xs-offset-2">
<div class="table-responsive" style="margin-left:30px; margin-top:15px">
<table class="table table-bordered">
<tr>
<td><i class="fa fa-facebook"></i></td>
<td>
<% if #user.identities.map(&:provider).include?('facebook') %>
<span class="glyphicon glyphicon-ok"</span>
<% else %>
<%= link_to icon('Connect Facebook', id: 'facebookauth'), user_omniauth_authorize_path(:facebook) %>
<% end %>
</td>
</tr>
<tr>
<td><i class="fa fa-google"></i></td>
<td>
<% if #user.identities.map(&:provider).include?('googleauth') %>
<span class="glyphicon glyphicon-ok"</span>
<% else %>
<%= link_to icon('Connect Google', id: 'googleauth'), user_omniauth_authorize_path(:google_oauth2) %>
<% end %>
</td>
</tr>
<tr>
<td><i class="fa fa-linkedin"></i></td>
<td>
<% if #user.identities.map(&:provider).include?('linkedin') %>
<span class="glyphicon glyphicon-ok"</span>
<% else %>
<%= link_to icon('Connect Linkedin', id: 'linkedinauth'), user_omniauth_authorize_path(:linkedin) %>
<% end %>
</td>
</tr>
<tr>
<td><i class="fa fa-twitter"></i></td>
<td>
<% if #user.identities.map(&:provider).include?('twitter') %>
å <span class="glyphicon glyphicon-ok"</span>
<% else %>
<%= link_to icon('Connect Twitter', id: 'twitterauth'), user_omniauth_authorize_path(:twitter) %>
<% end %>
</td>
</tr>
<tr>
<td>Password</td>
<td>
<% if #user.encrypted_password.present? %>
<span class="glyphicon glyphicon-ok"</span>
<% else %>
<%= form_for(current_user, :as => 'user', :html => { role: 'form'}) do |f| %>
<% if #show_errors && current_user.errors.any? %>
<div id="error_explanation">
<% current_user.errors.full_messages.each do |msg| %>
<%= msg %><br>
<% end %>
</div>
<div class="form-group">
<div class="controls">
<%= f.input :password, hint: ("#{#minimum_password_length} characters minimum" if #validatable), :input_html => { class: 'estimate-password'} %>
</div>
</div>
<% end %>
<div class="actions">
<%= f.submit 'Continue', :class => 'btn btn-primary' %>
</div>
<% end %>
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
routes
devise_for :users, #class_name: 'FormUser',
:controllers => {
:registrations => "users/registrations",
# :omniauth_callbacks => "users/authentications"
:omniauth_callbacks => 'users/omniauth_callbacks'
}
# PER SOURCEY TUTORIAL ----------
match '/users/:id/finish_signup' => 'users#finish_signup', via: [:get, :patch], :as => :finish_signup
None of this works. I don't know how to plug it in. I'm not sure whether I'm supposed to include the attributes stored in my identities table in the permitted params in the controller??
The attributes are:
t.integer "user_id"
t.string "provider"
t.string "accesstoken"
t.string "refreshtoken"
t.string "uid"
t.string "name"
t.string "email"
t.string "nickname"
t.string "image"
t.string "phone"
t.string "urls"
I have got this working so a user can authenticate with one method only. I don't know how to get this working. I've tried every resource I can find to figure this out but I'm stuck.
I have this all working with each individual social plugin and email, but what I don't have is the ability to add identities to an existing user (in a current session) so that the next time they login they can use any acceptable identity.
Can anyone help?
Without being able to see all of your code, I just created a shell app that runs with multiple providers. I just followed the steps from the tutorial you mentioned at sourcey. Here is the link to my repo.
You should be able to just clone it and run it by entering your app's keys and secret tokens from facebook, twitter, and linkedin in the devise.rb initializer. To get this to work locally you will need to make sure that the callback url on twitter is set to http://127.0.0.1:3000/.
If you wanted to give a user the option to add their own omniauth account (identity) instead of having it done automatically through app authorization you could just make a form for the user to enter the numeric uid and create the identity your self in the controller or back end like this:
new_identity = Identity.new
new_identity.user_id = "current user's id"
new_identity.provider = "facebook"
new_identity.uid = "0123456789"
new_identity.save!
The user would have to get their numeric uid from the site and enter it themselves.

Omniauth Twitter - Redirect to form for extra Information

I'm following this tutorial but fail on how to redirect the User to a page to fill out more information when he connects with omniauth.
In my User Model I have:
user = User.new(
name: auth.extra.raw_info.name,
user_name: auth.info.nickname,
about_me: auth.info.description,
email: email ? email : "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
password: Devise.friendly_token[0,20]
)
user.skip_confirmation!
user.save!
In my Omniauth callbacks controller I have:
def after_sign_in_path_for(resource)
if resource.profile_valid?
super resource
else
finish_signup_path(resource
end
end
profile_valid checks for:
def profile_valid?
self.email && self.email !~ TEMP_EMAIL_REGEX
end
Twitter auth doesn't give you an email, which I require for a Registration, so i'm passing a dummy email with a regex (TEMP_EMAIL_REGEX).
So when a valid email isn't present it should redirect to the finish_signup_page which contains:
<div id="add-email" class="container">
<h1>Add Email</h1>
<%= form_for(current_user, :as => 'user', :url => finish_signup_path(current_user), :html => { role: 'form'}) do |f| %>
<% if #show_errors && current_user.errors.any? %>
<div id="error_explanation">
<% current_user.errors.full_messages.each do |msg| %>
<%= msg %><br>
<% end %>
</div>
<% end %>
<!-- User Name -->
<div class="form-group">
<%= f.label :user_name %>
<div class="controls">
<%= f.text_field :user_name, :autofocus => true, :value => '', class: 'form-control', placeholder: 'Username' %>
<p class="help-block">Please enter your username</p>
</div>
</div>
<div class="form-group">
<%= f.label :email %>
<div class="controls">
<%= f.text_field :email, :autofocus => true, :value => '', class: 'form-control', placeholder: 'Example: email#me.com' %>
<p class="help-block">Please confirm your email address. No spam.</p>
</div>
</div>
<div class="actions">
<%= f.submit 'Continue', :class => 'btn btn-primary' %>
</div>
<% end %>
</div>
Here is my problem, when I enter something it doesn't save the User and it would be better if in the actual fields the current values would be present?
But I'm lost on how to do that, or maybe I'm trying this now for too long and have a tunnel-vision.
What am I Missing?
First go through the following classes. You need to store the correct data for the user, a generated email is not a good solution. So we basically store the provider's data in a separate model Social Provider.
Registration Flow:
sp = Social Provider is created
The user is initialized by the data received from the provider (not saved).
We store the sp id in the session, to isolate it from the user reach.
we render device registration/new view (adding all fields we need).
On successful registeration, we link the sp to the created user.
1.The User Class, has update_from_oauth method, which updates the main attributes in the user without saving it.
##########################
# User Class
##########################
class User < ActiveRecord::Base
has_many :social_providers, dependent: :destroy
# update from OAuth
def update_from_oauth(auth, provider_type)
self.email = auth[:info][:email] if self.email.blank?
case provider_type
when :twitter
name = auth[:info][:name].split(' ')
self.first_name ||= name[0]
self.last_name ||= name[1]
self.remote_avatar_url = auth[:extra][:raw_info][:profile_image_url]
when :facebook
...
when :google
...
end
end
end
2. SocialProvider class or (Identity)
class SocialProvider < ActiveRecord::Base
#Relations
belongs_to :user
def self.find_for_oauth(auth, provider_type)
unless social_provider = self.find_by(pid: auth[:uid].to_s, provider_type: provider_type)
user = User.find_by_email(auth[:info][:email])
social_provider = user.social_providers.where(provider_type: provider_type).first if user.present?
social_provider ||= SocialProvider.new
end
social_provider.update_from_oauth(auth, provider_type)
social_provider
end
def update_from_oauth(auth, provider_type)
self.email= auth[:info][:email]
self.pid= auth[:uid]
self.provider_type= provider_type
credentials = auth[:credentials]
case provider_type
when :twitter
self.token = credentials[:token]
self.secret = credentials[:secret]
self.url = auth[:info][:urls][:Twitter]
when :facebook
...
when :google
...
end
end
end
##########################
# SocialProvider Migration
##########################
class CreateSocialProviders < ActiveRecord::Migration
def change
create_table "social_providers" do |t|
t.string "pid" # user provider id
t.string "token"
t.string "refresh_token"
t.string "secret"
t.datetime "expires_at"
t.string "provider_type"
t.integer "user_id"
t.string "url"
t.string "email"
t.timestamps
end
end
end
3. Omniauth Callbacks Controller
###############################
# OmniAuth Callbacks Controller
###############################
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
before_filter :prepare_auth
def facebook
connect(:facebook)
end
def twitter
connect(:twitter)
end
def google_oauth2
connect(:google)
end
private
def prepare_auth
#auth = request.env["omniauth.auth"]
end
def connect(provider_type)
social_provider = SocialProvider.find_for_oauth(#auth, provider_type)
if user_signed_in?
if social_provider and social_provider.user_id == current_user.id
flash[:notice] = "Your #{provider_type} account is already attached"
redirect_to current_user and return
elsif social_provider.user_id.nil?
current_user.update_from_oauth(#auth, provider_type)
current_user.social_providers << social_provider if current_user.save
flash[:notice] = "Successfully attached #{provider_type} account"
redirect_to current_user and return
else
flash[:notice] = "#{provider_type} is already connected to another account"
redirect_to current_user and return
end
else
#user = social_provider.user || User.find_by_email(#auth[:info][:email]) || User.new
#user.update_from_oauth(#auth, provider_type)
social_provider.save
if #user.persisted? # If user already has account and not logged in
#user.social_providers << social_provider if #user.save
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => provider_type.capitalize
sign_in_and_redirect #user, :event => :authentication
else # If user has no account
session[:sp_id] = social_provider.id
render 'registrations/new'
end
end
end
end
4. Overriding Devise registration controller
#################################
# Devise::RegistrationsController
#################################
class RegistrationsController < Devise::RegistrationsController
def create
super
if user_signed_in? and session[:sp_id].present?
SocialProvider.find_by(id: session[:sp_id],user_id: nil).update(user_id: current_user.id)
end
end

Rails: Routing Error: uninitialized constant SessionController

I have Rails 4.0.10. I'm following Michael Hartl's Ruby on Rails tutorial, and I'm trying to build a login function, but I'm getting the following error when I press the login button:
Routing Error
uninitialized constant SessionController
I followed the instructions exactly, so I'm confused about why I'm getting an error. What did I do wrong?
My Sessions Controller:
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(id: params[session][:id])
if user && user.authenticate(params[:session][:password])
log_in user
redirect_to root_path
else
flash.now[:danger] = 'Invalid'
render 'new'
end
end
def destroy
end
end
Routes:
Website::Application.routes.draw do
get 'login' => 'sessions#new'
post 'login' => 'session#create'
delete 'logout' => 'sessions#destroy'
get "users/new"
root 'home_page#home'
end
Sessions/new View:
<div id= "admin-sign-in">
<%= form_for(:session, url: login_path) do |f| %>
<%= f.label :id %>
<%= f.text_field :id %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.submit "Log in", class: "btn btn-primary" %>
<% end %>
</div>
Sessions Helper:
module SessionsHelper
def log_in(user)
session[:user_id] = user.id
end
end
User Model:
class User < ActiveRecord::Base
has_secure_password
end
Users Controller:
class UsersController < ApplicationController
def new
#user = User.new
#users = User.all
end
def create
#user = User.new(user_params)
if #user.save
render :action => "crop"
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:password, :password_confirmation)
end
end
ApplicationController:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
include SessionsHelper
end
Instead of:
post 'login' => 'session#create'
Use the plural:
post 'login' => 'sessions#create'

Enable email lazy registration actionMailer Rails 4

I am new to Rails and trying to get a example working to register with a confirmation email with Rails 4 and devise. I am using this example:
https://github.com/mwlang/lazy_registration_demos
I created the following files:
initialisers/setup_mail.rb
ActionMailer::Base.delivery_method = :smtp
ActionMailer::Base.smtp_settings = {
:address => "smtp.gmail.com",
:port => 587,
:domain => "gmail.com",
:user_name => "account#gmail.com",
:password => "passwork",
:authentication => "plain",
:enable_starttls_auto => true
}
/app/mailers/welcome_email.rb
class WelcomeMailer < ActionMailer::Base
def registration_confirmation(user)
mail :to => user, :from => "email#domain.com", :subject => "Subject line"
end
end
/devise/mailer/confirmations_instructions.html.erb
<p>
Welcome #{#email}!
</p>
<p>You can confirm your account email through the link below:</p>
<p>
<%= link_to 'Confirm my account', confirmation_url(#resource, :confirmation_token => #resource.confirmation_token) %>
</p>
confirmations_controller.rb
class ConfirmationsController < Devise::ConfirmationsController
# Remove the first skip_before_filter (:require_no_authentication) if you
# don't want to enable logged users to access the confirmation page.
skip_before_filter :require_no_authentication
skip_before_filter :authenticate_user!, except: [:confirm_user]
def update
with_unconfirmed_confirmable do
if confirmable_user.blank_password?
confirmable_user.update_password(params[:user])
if confirmable_user.valid?
do_confirm
else
do_show
confirmable_user.errors.clear # prevent rendering :new
end
else
self.class.add_error_on(self, :email, :password_allready_set)
end
end
render 'devise/confirmations/new' unless confirmable_user.errors.empty?
end
def confirm_user
if confirmation_token && (#confirmable_user = User.find_by(:confirmation_token => confirmation_token))
do_show
else
flash[:error] = "Invalid confirmation token"
redirect_to :unconfirmed
end
end
def show
with_unconfirmed_confirmable do
confirmable_user.blank_password? ? do_show : do_confirm
end
unless confirmable_user.errors.empty?
self.resource = confirmable_user
render 'devise/confirmations/new'
end
end
protected
def confirmation_token
#confirmation_token ||= params["user"] && params["user"]["confirmation_token"] || params["confirmation_token"]
end
def confirmable_user
#confirmable_user ||= User.find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
end
def with_unconfirmed_confirmable
unless confirmable_user.new_record?
confirmable_user.only_if_unconfirmed {yield}
end
end
def do_show
self.resource = confirmable_user
render 'devise/confirmations/show'
end
def do_confirm
confirmable_user.confirm!
set_flash_message :notice, :confirmed
sign_in_and_redirect(resource_name, confirmable_user)
end
end
/devise/registrations/new.html.haml
%h2 Sign up
= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f|
= devise_error_messages!
%div
= f.label :email
= f.email_field :email, :autofocus => true
%div{style: "margin-top: 25px"}= f.submit "Sign up", class: "btn btn-primary btn-large"
%hr
= render "devise/shared/links"
To trigger the email to be send I need to add
WelcomeMailer.registration_confirmation(#user).deliver
But I have no clue where I need to add this trigger. Do I need to do this in the controller? Or in the view?
Thanks a lot
Managed to fix this issue myself using mailcatcher
Steps to fix:
clone github project lazy_registrations
gem install mailcatcher
add lines to /environments/development.rb
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = { :address => "localhost", :port => 1025 }
run mailcatcher
check email using 127.0.0.1:1080
ignore all other code above, just needed a night of sleep :)

Devise Send Reset Password Instruction "undefined method `slice' for nil:NilClass"

I am using devise (2.2.3) in a rails (3.2.12) application.
I have 2 devise model, devise Admin and User
On devise model Admin authentication_keys using email and User authentication_keys using username (but still having email), admin model is fully working but user model on forgot password function has decided stop working. When I enter an email address and click the 'send me reset instructions', I got the error message looks like :
NoMethodError in PasswordusersController#create
undefined method `slice' for nil:NilClass
There is no significant differences between passwordadmins_controller.rb with passwordusers_controller.rb , the only difference being named
What I do I have to do to fix this?
This passwordusers_controller.rb
class PasswordusersController < Devise::PasswordsController
prepend_before_filter :require_no_authentication
append_before_filter :assert_reset_token_passed, :only => :edit
def new
super
end
def create
self.resource = resource_class.send_reset_password_instructions(resource_params)
if successfully_sent?(resource)
redirect_to new_session_user_path, :notice => "Instruction Send To Your Email"
else
respond_with(resource)
end
end
def edit
super
end
def update
self.resource = resource_class.reset_password_by_token(resource_params)
if resource.errors.empty?
resource.unlock_access! if unlockable?(resource)
flash_message = resource.active_for_authentication? ? :updated : :updated_not_active
set_flash_message(:notice, flash_message) if is_navigational_format?
sign_in(resource_name, resource)
redirect_to home_subdomain_path, :notice => "Password changed"
else
respond_with resource
end
end
protected
def after_sending_reset_password_instructions_path_for(resource_name)
new_session_user_path
end
def assert_reset_token_passed
if params[:reset_password_token].blank?
set_flash_message(:error, :no_token)
redirect_to new_session_user_path
end
end
def unlockable?(resource)
resource.respond_to?(:unlock_access!) &&
resource.respond_to?(:unlock_strategy_enabled?) &&
resource.unlock_strategy_enabled?(:email)
end
end
UPDATE
<%= form_for("User", :url => passwordusers_create_path(resource_name), :html => { :method => :post }) do |f| %>
<%= render 'layouts/flash_messages' %>
<div class="row-fluid">
<div class="input-prepend">
<span class="add-on"><i class="icon-envelope"></i></span>
<%= f.email_field :email, 'style' => 'width:240px;', :placeholder => 'Email' %>
</div>
<div class="dr"><span></span></div>
<%= f.submit "Send", :class => 'btn btn-block btn-primary' %>
<div class="dr"><span></span></div>
</div>
<% end %>
Thank you for help
Resource devise default using lowercase, try this,
form_for("User" .....
change to
form_for("user"

Resources