I have created an app with a facebook login using :
gem "koala"
gem 'omniauth'
gem 'omniauth-facebook', '1.4.0'
gem 'fb_graph2'
I want to get access to user friends who use the app too.
But I don't know how to do that and how to have the 'access token' because I don't understand what is the access token ...
Right now, here is the code that I have.
models/ user.rb
class User
include Mongoid::Document
include Mongoid::Timestamps
field :provider, type: String
field :uid, type: String
field :name, type: String
field :picture, type: String
field :auth_token, type: String
has_many :parties
has_many :invitations, :class_name => 'Invite', :foreign_key => 'recipient_id'
has_many :sent_invites, :class_name => 'Invite', :foreign_key => 'sender_id'
has_many :friends
# TODO: Ajouter les amitiƩs
# TODO: Ajouter les recherches d'amis (livereload)
def self.create_with_omniauth(auth)
create! do |user|
user.provider = auth['provider']
user.uid = auth['uid']
user.auth_token = auth['credentials']['token']
if auth['info']
user.name = auth['info']['name'] || ""
user.picture = auth['info']['image'] || ""
end
end
end
def large_image
return "http://graph.facebook.com/#{self.uid}/picture?type=large"
end
def normal_image
return "http://graph.facebook.com/#{self.uid}/picture?type=normal"
end
end
controllers / sessions_controller.rb
class SessionsController < ApplicationController
def create
auth = request.env["omniauth.auth"]
user = User.where(:provider => auth['provider'],
:uid => auth['uid']).first || User.create_with_omniauth(auth)
session[:user_id] = user.id
redirect_to root_url, :notice => "Signed in!"
end
def destroy
reset_session
redirect_to root_url, :notice => 'Signed out!'
end
def new
redirect_to '/auth/facebook'
end
def failure
redirect_to root_url, :alert => "Authentication error: #{params[:message].humanize}"
end
end
initializer / omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, '<public_key>', '<private_key>'
end
And my friends controller :
class FriendsController < ApplicationController
def index
if params[:code]
session[:access_token] = session[:oauth].get_access_token(params[:code])
end
# auth established, now do a graph call:
#api = Koala::Facebook::API.new(session[:access_token])
#user_profile = #api.get_object("me")
#friends = #api.get_connections(#user_profile['id'], "friends")
end
end
It don't work, I have this error :
type: OAuthException, code: 2500, message: An active access token must be used to query information about the current user., x-fb-trace-id: BeOG6OGemO9 [HTTP 400]
I imagine that it's because my code is not correct, so I hope someone should help me improve my code and get access to user friends !
The token that you saved in users.auth_token field is what you need. So when initializing a new Koala instance pass users auth_token - #api = Koala::Facebook::API.new(current_user.auth_token) and then try to get friends list, like you do currently.
Regarding what access token is you can read about it here (in Facebook context) https://developers.facebook.com/docs/facebook-login/access-tokens
I have tried so many guides and so many tutorials, but whenever I try to retrieve information that Omniauth is supposed to provide after the user accepts the permissions. Right now I just want a user to log in with Google and access his calendar.
routes.rb
devise_for :users, path_names: {sign_in: "login", sign_out: "logout"},
:controllers => { :omniauth_callbacks => "callbacks" }
devise_scope :user do
get "/auth/google_oauth2/callback" => 'callbacks#google_oauth2'
end
config/initializes/omniauth.rb
module OmniAuth
module Strategies
class GoogleAuth < OmniAuth::Strategies::GoogleOauth2
option :name, 'google_auth'
option :callback_path, '/callbacks/google'
end
end
end
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2, '<my client id in string>', '<my client secret in string>', {
access_type: 'offline',
scope: 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/calendar',
callback_path:'http://localhost:3000/auth/google_oauth2/callback'
prompt: 'select_account consent'
}
#provider :devloper unless Rails.env.production?
provider :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']
end
callbacks_controller.rb
class CallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
#user = User.from_omniauth(request.env["omniauth.auth"])
if user.persisted?
sign_in_and_redirect #user
else
session["devise.google_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
models/user.rb:
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
end
end
The error this that env["omniauth.auth"] and :provider. How do I change my files so that Omniauth correctly populates with the user's information and so that I can create a devise user for that session. The project is at https://github.com/SafaTinaztepe/teemvu.git.
on your user.rb, I think it should be
user.provider = auth['provider'],
user.uid = auth['uid'],
....
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 want to use seperate admin login for my application using idenity provider.
I have written this in config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :identity, :model => Credential, :on_failed_registration =>SessionsController.action(:register)
provider :identity, :model => Credential, :name => 'admin', :on_failed_registration => SessionsController.action(:login_admin)
provider :google_oauth2, '000000000.apps.googleusercontent.com', '00000000000'
end
In config/routes.rb
match '/auth/admin/callback', :to => 'sessions#authenticate_admin'
In app/controllers/sessions_controller.rb
def authenticate_admin
auth_hash = request.env['omniauth.auth']
session[:admin_user] = auth_hash['user_info']['email']
if admin?
redirect_to '/'
else
render :text => '401 Unauthorized', :status => 401
end
end
But when i try to access request.env['omniauth.auth'], it always gets nil. While it is accessible when using default callback for normal users at sessison#create action. I just want to know if there is anything that has been missed in this code. I am following this blog http://www.intridea.com/blog/2011/1/31/easy-rails-admin-login-with-google-apps-and-omniauth.
I got Devise setup and working for my app. When i add
gem omniauth-google-oauth2
im getting an error in controller sign_in_and_redirect method.
Error:
NoMethodError (undefined method `serialize_into_session' for String:Class):
app/controllers/users/omniauth_callbacks_controller.rb:9:in `google_oauth2'
Code:
omniauth_callbacks_controller.rb
def google_oauth2
#user = User.find_for_google_oauth2(request.env["omniauth.auth"], current_user)
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google"
sign_in_and_redirect sites_path, :event => :authentication
else
session["devise.google_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
user.rb
def self.find_for_google_oauth2(access_token, signed_in_resource=nil)
data = access_token.info
user = User.where(:email => data["email"]).first
unless user
user = User.create(email: data["email"],
password: Devise.friendly_token[0, 20]
)
end
user
end
devise.rb
require "omniauth-google-oauth2"
config.omniauth :google_oauth2, 'CLIENT_ID', 'CLIENT_SECRET', {access_type: "offline", approval_prompt: ""}
Please let me know if im missing something.
Your config is good, but I use for this situation instead omniauth-google-oauth2, this gem https://rubygems.org/gems/omniauth-openid.
Add to your /config/initializer/devise.rb the next:
require 'openid/store/filesystem'
config.omniauth :open_id, :store => OpenID::Store::Filesystem.new('/tmp'), :name => 'google', :identifier => 'https://www.google.com/accounts/o8/id', :require => 'omniauth-openid'
and after inside your omniauth_callbacks_controller.rb change the method User.find_for_google_oauth2... by User.find_for_open_id...
on your user model change the self.find_for_google_oauth2 by self.find_for_open_id.
Try with this gem please!