I've set up Devise and Omniauth for users to sign in via email, twitter, and facebook. I'm not trying to allow users to tweet a message from inside the app.
I’ve got it currently working with the following code but it’s only posting from MY twitter account. I’m assuming this has to do with not setting up the Oauth_token correctly. No matter what account logins into the app, it still comes from my account.
In my User model, I have the following code (I’ve changed my key and tokens)…
def self.find_for_twitter_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
if user
return user
else
registered_user = User.where(:email => auth.uid + "#twitter.com").first
if registered_user
return registered_user
else
user = User.create(full_name:auth.extra.raw_info.name,
provider:auth.provider,
uid:auth.uid,
email:auth.uid+"#twitter.com",
oauth_token:auth.credentials.token,
oauth_secret:auth.credentials.secret,
password:Devise.friendly_token[0,20],
)
end
end
end
def tweet(tweet)
client = Twitter::REST::Client.new do |config|
config.consumer_key = "XXXXXXXX"
config.consumer_secret = "XXXXXXX"
config.access_token = "XXXXXXX-XXXXX"
config.access_token_secret = "XXXXXXX"
end
client.update(tweet)
end
In my config/initializer/devise.rb I have the following:
# Add Twitter OmniAuth
require 'omniauth-twitter'
config.omniauth :twitter, ENV['TWITTER_CONSUMER_KEY'], ENV['TWITTER_CONSUMER_SECRET']
# Add Facebook OmniAuth
require 'omniauth-facebook'
config.omniauth :facebook, ENV['FACEBOOK_APP_ID'], ENV['FACEBOOK_APP_SECRET'], :scope => 'basic_info, email, publish_stream'
In my view, I'm using a form for them to fill out and submit the tweet.
<p>
<%= form_for :tweet, url: tweets_path, method: :post do |f| %>
<%= f.text_field :message %>
<%= f.submit "Send Tweet" %>
<% end %>
</p>
You should authorize each twitter user with your app (as I remember with consumer key and consumer secret only).
I did this with simple way:
consumer = OAuth::Consumer.new($TWITTER_CONSUMER_KEY, $TWITTER_CONSUMER_SECRET, :site => "https://api.twitter.com")
request_token = consumer.get_request_token(:oauth_callback => "http://localhost/twitter/auth_callback")
return request_token.authorize_url
by URL I have
access_token = request_token.get_access_token(:oauth_verifier => params[:oauth_verifier] )
token = access_token.token
secret = access_token.secret
Now if you set these toekn and secret in to your REST client you can post tweets from your user account.
Related
Very simple question.
I have run into the "Email can't be blank" issue with my rails app/omniauth/facebook login. There are many questions on this, and among the answers is that some users give their telephone number and not their email addresses. However in my case, I have a facebook account open, with an email address that works fine with omniauth in development, but when I use it production it comes back email can't be blank. My credentials are correct.
Can anyone explain how you could have this combination of events?
edit: here is my controller code (keep in mind my devise model is "Member" and there is an associated profile model "User"
def provider
auth = request.env["omniauth.auth"]
member_email_check = Member.find_by_email(auth.info.email)
if member_email_check.present? && User.find_by_member_id(member_email_check.id)
sign_in_and_redirect member_email_check
return false
else
member = Member.where(provider: auth.provider, uid: auth.uid).first_or_create do |member|
member.provider = auth.provider
member.uid = auth.uid
member.email = auth.info.email
end
end
if auth.provider == "facebook"
if member.save
unless User.find_by_member_id(member.id)
User.create(:member_id => member.id,
:full_name => auth.info.first_name + " " + auth.info.last_name,
:first_name => auth.info.first_name,
:last_name => auth.info.last_name,
:email => auth.info.email,
:picture => auth.info.image)
end
sign_in_and_redirect member
else
session["devise.member_attributes"] = member.attributes
redirect_to new_member_registration_url
end
end
end
alias_method :facebook, :provider
end
SOLVED!
config.omniauth :facebook, "<YOUR API KEY>", "<YOUR SECRET KEY>", scope: 'email', info_fields: 'email,name,first_name,last_name,gender'
just added the email scope and info_fields.
I have spent days watching the RailsCasts on devise and omniauth and then going through related tutorials for setting up an authentication system that uses these gems. I think the RailsCasts are out of date and trying to patch the gaps with other tutorials is creating all kinds of issues.
Please can anyone suggest a current tutorial that I can use as a basis for implementing this system. I have separate user and authentications models (with users having many authentications).
I'd really like to use devise and omniauth (with CanCan for abilities) on rails 4 but am tearing my hair out in trying to find a basic setup (using psql as a database).
To use Devise with multiple auth providers you need 1 more model - Authorization
#authorization.rb
# == Schema Information
#
# Table name: authorizations
#
# id :integer not null, primary key
# user_id :integer
# provider :string(255)
# uid :string(255)
# token :string(255)
# secret :string(255)
# created_at :datetime
# updated_at :datetime
# profile_page :string(255)
#
class Authorization < ActiveRecord::Base
belongs_to :user
end
There is code for User model
#user.rb
SOCIALS = {
facebook: 'Facebook',
google_oauth2: 'Google',
linkedin: 'Linkedin'
}
has_many :authorizations
def self.from_omniauth(auth, current_user)
authorization = Authorization.where(:provider => auth.provider, :uid => auth.uid.to_s,
:token => auth.credentials.token,
:secret => auth.credentials.secret).first_or_initialize
authorization.profile_page = auth.info.urls.first.last unless authorization.persisted?
if authorization.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
authorization.user = user
authorization.save
end
authorization.user
end
def fetch_details(auth)
self.name = auth.info.name
self.email = auth.info.email
self.photo = URI.parse(auth.info.image)
end
And at the end you need to override methods for Devise controller
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
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 first_login_path
else
redirect_to cabinet_path
end
else
session['devise.user_attributes'] = user.attributes
redirect_to new_user_registration_url
end
end
User::SOCIALS.each do |k, _|
alias_method k, :all
end
end
If you need some for providers, e.g. Twitter you need to override twitter method, because it doesn't provide user email and you should save it's credentails in some other way.
Also you should make some changes in routes
devise_for :users,
:controllers => {
:omniauth_callbacks => 'users/omniauth_callbacks',
}
in you initializers try doing something like this
#config/initializers/devise.rb
#provider1
config.omniauth :facebook, ENV['FACEBOOK_APP_ID'], ENV['FACEBOOK_SECRET'],
:site => 'https://graph.facebook.com/',
:authorize_path => '/oauth/authorize',
:access_token_path => '/oauth/access_token',
:scope => 'email, user_birthday, read_stream, read_friendlists, read_insights, read_mailbox, read_requests, xmpp_login, user_online_presence, friends_online_presence, ads_management, create_event, manage_friendlists, manage_notifications, publish_actions, publish_stream, rsvp_event, user_about_me, user_activities, user_birthday, user_checkins, user_education_history, user_events, user_groups, user_hometown, user_interests, user_likes, user_location, user_notes, user_photos, user_questions, user_relationships, user_relationship_details, user_religion_politics, user_status, user_subscriptions, user_videos, user_website, user_work_history'
#provider2
config.omniauth :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET'],
:scope => {
:secure_image_url => 'true',
:image_size => 'original',
:authorize_params => {
:force_login => 'true'
}
}
#provider3
config.omniauth :google_oauth2, ENV["GOOGLE_KEY"], ENV["GOOGLE_SECRET"]
You should give this gem a try.
https://github.com/AlexanderZaytsev/domp
Quick and Easy to integrate. I had a working setup in about 30m.
I'm using:
Ruby on Rails 4
devise 3.0.3
omniauth (1.1.4)
omniauth-facebook (1.4.1)
omniauth-twitter (1.0.0)
I recently set up my omniauth-facebook and everything works fine. Now i want to add omniauth-twitter but somehow i mess things up, pretty bad.
1.) To set up my Omniauth-Facebook i did this (in a nutshell):
gem 'omniauth'
gem 'omniauth-facebook'
2.) Added the columns "provider" and "uid" to my User model.
3.) Next, i declared the provider in my config/initializers/devise.rb:
require "omniauth-facebook"
config.omniauth :facebook, "App_ID", "App_Secret",
{:scope => 'email,offline_access',
:client_options => {:ssl => {:ca_file => 'lib/assets/cacert.pem'}},
:strategy_class => OmniAuth::Strategies::Facebook}
4.) I edited my Model User.rb
# Facebook Settings
def self.find_for_facebook_oauth(auth, signed_in_resource = nil)
user = User.where(provider: auth.provider, uid: auth.uid).first
if user.present?
user
else
user = User.create(first_name:auth.extra.raw_info.first_name,
last_name:auth.extra.raw_info.last_name,
facebook_link:auth.extra.raw_info.link,
user_name:auth.extra.raw_info.name,
provider:auth.provider,
uid:auth.uid,
email:auth.info.email,
password:Devise.friendly_token[0,20])
end
end
and added the attributes to devise:
:omniauth_providers => [:facebook]
5.) I edited the routes:
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
THE END
Although this worked perfectly for Facebook, i tried for hours now to get this working for Twitter, and i just cant figure it out.
If someone, who has experience in this, or just knows the solution could help me set this up, i would be very thankful :)
Thank you guys, and sorry for the long Post.
In Addition
Twitter does not provide an :email Attribute so i have to Split up my User Registration Process i guess ?
My Twitter action in my User Model
# Twitter Settings
def self.find_for_twitter_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth[:provider], :uid => auth[:uid]).first
unless user
user = User.create(:first_name => auth[:name],
:user_name => auth[:screen_name],
:provider => auth[:provider], :uid => auth[:uid],
:password => Devise.friendly_token[0,20]
)
end
user
end
# build auth cookie hash for twitter
def self.build_twitter_auth_cookie_hash data
{
:provider => data.provider, :uid => data.uid.to_i,
:access_token => data.credentials.token, :access_secret => data.credentials.secret,
:first_name => data.name, :user_name => data.screen_name,
}
end
I had to migrate a confirmable for Users -> How To: Add :confirmable to Users
My Form's Problem, (At Least im getting to this poing now :) )
To fix your problem with the email you could just set a dummy mail, or add a second step where the user adds his/her email.
Dummy mail:
class User < ActiveRecord::Base
before_create :set_dummy_mail, if self.provider == "twitter"
private
def set_dummy_mail
self.email = "#{self.name}_email#example.com"
end
end
Or the second step option:
Modify your controller to redirect to an add email step if the provider is twitter and the email is blank. Maybe you also have to modify your validations to allow email blank on create if the provider is twitter.
UPDATE: I did it like following:
Gemfile:
gem "devise"
gem "omniauth"
gem "omniauth-facebook"
gem "omniauth-twitter"
I used:
devise version 2.2.3
omniauth 1.1.4
omniauth-facebook 1.3.0
omniauth-twitter 0.0.17
If you are using different versions, you maybe must change a few things..
routes.rb:
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
devise_scope :user do
post "account/create" => "users/accounts#create"
end
app/models/user.rb
class User < ActiveRecord::Base
# allow email blank for first create
validates_format_of :email, :with => Devise.email_regexp, :allow_blank => true, :if => :email_changed?
# facebook find method
def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
unless user
user = User.create(:first_name => auth.extra.raw_info.first_name,
:last_name => auth.extra.raw_info.last_name,
:facebook_link => auth.extra.raw_info.link,
:user_name => auth.extra.raw_info.name
:provider => auth.provider,
:uid => auth.uid, :email => auth.info.email,
:password => Devise.friendly_token[0,20]
)
user.confirm!
end
user
end
# twitter find method
def self.find_for_twitter_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth[:provider], :uid => auth[:uid]).first
unless user
user = User.create(:first_name => auth[:first_name], :user_name => auth[:user_name],
:provider => auth[:provider], :uid => auth[:uid],
:password => Devise.friendly_token[0,20]
)
end
user
end
# build auth cookie hash for twitter
def self.build_twitter_auth_cookie_hash data
{
:provider => data.provider, :uid => data.uid.to_i,
:access_token => data.credentials.token, :access_secret => data.credentials.secret,
:first_name => data.screen_name, :user_name => data.name,
}
end
end
app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
# callback action for facebook auth
def facebook
#user = User.find_for_facebook_oauth(request.env["omniauth.auth"], current_user)
if #user.persisted?
sign_in_and_redirect #user, :event => :authentication
set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
# callback action for twitter auth
def twitter
data = session["devise.omniauth_data"] = User.build_twitter_auth_cookie_hash(request.env["omniauth.auth"])
user = User.find_for_twitter_oauth(data)
if user.confirmed? # already registered, login automatically
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Twitter"
sign_in_and_redirect user, :event => :authentication
elsif !user.email?
flash[:error] = "You must add an email to complete your registration."
#user = user
render :add_email
else
flash[:notice] = "Please confirm your email first to continue."
redirect_to new_user_confirmation_path
end
end
end
app/views/users/omniauth_callbacks/add_email.html.erb
<%= form_for(#user, :as => "user", :url => account_create_path, :html => {:class => "form-inline"}) do |f| %>
<%= f.email_field :email, :placeholder => User.human_attribute_name(:email), :class => "input-medium" %>
<%= f.submit "Finish registration", :class => "btn btn-small" %>
<% end %>
app/controllers/users/accounts_controller.rb
class Users::AccountsController < ApplicationController
def create
data = session["devise.omniauth_data"]
data[:email] = params[:user][:email]
user = User.find_for_twitter_oauth(data)
user.email = data[:email]
if user.save
flash[:notice] = I18n.t "devise.registrations.signed_up_but_unconfirmed"
redirect_to root_path
else
flash[:error] = I18n.t "devise.omniauth_callbacks.failure", :kind => data[:provider].titleize, :reason => user.errors.full_messages.first
render "users/omniauth_callbacks/add_email"
end
end
end
Maybe you have to modify the one or other part of my solution..you also could refactor the two methods in the user model (find_for_facebook_auth, find_for_twitter_auth) to work with one dynamic method. Try it out and let me know, if you still have problems. If you find any typo, please also let me know.. Also you should write tests to check everything within your system.
Add this in gem file
gem 'omniauth-twitter'
do bundle and restart the server
after this add your app_id and secret key in config/initializer/devise.rb
require "omniauth-twitter"
config.omniauth :twitter, "app_id", "secret_key"
edit your user model
:omniauth_providers => [:facebook,:twitter]
def self.find_for_twitter_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
unless user
user = User.create(name:auth.extra.raw_info.name,
provider:auth.provider,
uid:auth.uid,
email:auth.info.email,
password:Devise.friendly_token[0,20]
)
end
user
end
Add new controller file in
app/controller/user/omniauth_callbacks_controller.rb
def twitter
#user = User.find_for_twitter_oauth(request.env["omniauth.auth"], current_user)
if #user.persisted?
sign_in_and_redirect #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "Twitter") if is_navigational_format?
else
session["devise.twitter_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
Add this link in your view file
<%= link_to 'sign in with twitter', user_omniauth_authorize_path(:twitter) %>
Changes required in user model as twitter do not return user's email id:
Create a migration file to allow null values in user's email column:
class ChangeEmailToNullInUser < ActiveRecord::Migration
def up
change_column :users, :email, :string, :null=>true
end
def down
end
end
after this you also need to override user model validations so add this in user.rb
def email_required?
false
end
Note: Before performing this you should create your app on twitter and give proper callback path. Its is important as after authentication from twitter the controller would come back to the path which you specify in your app on twitter.
let me know if you have any problem.
Mattherick has a good solution for the email but I couldn't get the before_create to work. A callback doesn't play nice with conditional if statements because anything after the comma is meant to be an options hash. Therefore:
before_save :set_dummy_email, if self.provider == "twitter"
Popped me errors.
This how I fixed this:
before_save :set_dummy_email
def set_dummy_email
self.email ||= "#{self.name}-CHANGEME#example.com"
end
This will only set an email if one is not given by the provider (ie: Twitter).
And then for a more 'universal' way of setting attributes (so you don't need a unique strategy:
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_initialize.tap do |user|
user.name = auth.info.name || auth.info.nickname
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email if auth.info.email
user.save
end
end
I am working on Facebook authentication for Devise and I stumbled upon one problem - when will try to sign up someone with Facebook email that is already in the system, I get an error (which is partially correct).
I would need to redirect this user back to the homepage and there to print out this issue, but at the moment I am having this message printed as an error (on localhost).
Here's how I am doing that (user model):
def self.find_for_facebook_oauth(access_token, signed_in_resource=nil)
data = access_token.extra.raw_info
if user = User.where(:provider => 'facebook', :uid => data.id).first
user
else # Create a user with a stub password.
user = User.create!(:first_name => data.first_name,
:last_name => data.last_name,
:email => data.email,
:password => Devise.friendly_token[0,20],
:provider => 'facebook',
:uid => data.id,
:terms_of_use => true)
end
return user if user
end
How to redirect the user on a page where would be printed out the validation messages?
Thanks
Why don't you have something like this in your model and controller
Return just the user object from the method
def self.find_for_facebook_oauth(access_token, signed_in_resource=nil)
data = access_token.extra.raw_info
#Look for the first user with provider: :facebook & uid: data.id,
#If now user is there go ahead and create one with first_or_create.
user = User.where(:provider => 'facebook', :uid => data.id).first_or_create do |user|
user.first_name = data.first_name,
user.last_name = data.last_name,
user.email = data.email,
user.password = Devise.friendly_token[0,20],
user.provider = 'facebook',
user.uid = data.id,
user.terms_od_use = true
end
end
Controller
def sign_in_method #dummy method
user = User.find_for_facebook_oauth(access_token, signed_in_resource=nil)
if user.valid?
redirect success_url #any url where u want to redirect on success
else user.errors
redirect root_url, error: user.errors.full_messages.join(', ')
end
end
I'm usign omniauth to login user from facebook and I'm also using devise gem so in my devise.rb initializer file. I added
config.omniauth :facebook, app_id, app_secret,
{:scope => 'email, offline_access', :client_options =>
{:ssl =>
{:ca_file => '/usr/lib/ssl/certs/ca-certificates.crt'}
}
}
and Its working perfectly as I have user 'signed in' in my app but the problem is I'm not getting how to move on and get user profile info and its friends info and also Subscription and status updates.
Therer are graph Api methods available
#graph = Koala::Facebook::API.new(acess_token)
#profile = #graph.get_object("me")
#friends = #graph.get_connections("me", "friends")
But I'm not getting how to get "acess_token" to do all this stuff.
So summing up I need AccessToken kindly provide clear answer plzz
awaiting your response
yahooo got it :)
in the follwing method $access_token = auth.credentials.token statement is pointed to get access token.
def self.find_for_provider_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
$access_token = auth.credentials.token
unless user
user = User.create(name:auth.extra.raw_info.name,
provider:auth.provider,
uid:auth.uid,
email:auth.info.email,
password:Devise.friendly_token[0,20]
)
end
user
end
OR you can get it,by this
request.env["omniauth.auth"].credentials.token
Is there any other best way to get access_token plzz share I need optimization and good practice but for now its working.