0

I use Omniauth and Omniauth-facebook gems and I want to click on button, if user is found, then sign him in. If not, redirect him to register page.

So far I use just classic login/register:

user.rb:

def self.from_omniauth(auth, role)
  user = User.where(:provider => auth.provider, :uid => auth.uid).first

  if user
    return user
  else
    registered_user = User.where(:email => auth.info.email).first

    if registered_user
      registered_user.provider = auth.provider
      return registered_user
    else
      where(provider: auth.provider, uid: auth.uid).first_or_create do |user|

        if (role == "1")
          user.add_role :sportsman
        elsif (role == "2")
          user.add_role :donor
        end

        user.provider = auth.provider
        user.uid = auth.uid
        user.email = auth.info.email
        user.password = Devise.friendly_token[0,20]
      end
    end
  end
end

omniauth_callbacks_controller.rb:

def facebook
  role = cookies[:role]
  # signin = cookies[:signin]
  user = User.from_omniauth(request.env["omniauth.auth"], role)

  if user.persisted?
    flash.notice = "Signed by Facebooku"
    sign_in_and_redirect user
  else
    flash.notice = "Error, try again."
    session["devise.user_attributes"] = user.attributes
    redirect_to new_user_registration_url
  end
end

This code works, but if user is not registered, it will register him. But I sign roles when user register himself.

Thanks for help.

Gerry
  • 10,337
  • 3
  • 31
  • 40
Boomerange
  • 616
  • 1
  • 10
  • 18

2 Answers2

0

Because you are already saving the user attributes into the session with session["devise.user_attributes"] = user.attributes

Then the following should already work:

def self.from_omniauth(auth, role)
  user = User.where(:provider => auth.provider, :uid => auth.uid).first

  if user
    return user
  else
    # NOTE: because you're searching "email" and not both "email" + "provider", then I assume
    # that you want users to have only one shared account between all providers (i.e. if they also sign-in in Google with the exact email as his email in Facebook)
    registered_user = User.where(:email => auth.info.email).first
    if registered_user
      # NOTE: this line doesn't do anything because registered_user is not being "saved"
      registered_user.provider = auth.provider
      return registered_user
    else
      # build a new User object (don't save yet!)
      return User.new.tap do |u|
        u.provider = auth.provider
        u.email = auth.info.email
        u.uid = uid: auth.uid
        u.password = Devise.friendly_token[0,20]

        # because I assume you're using "rolify" gem, don't add the roles yet here, because I don't think you can store the "roles" into the session
        # add the roles logic in whatever controller you have for user registration
      end
    end
  end
end

Then override new_with_session in your user model. new_with_session is automatically called by Devise in registrations#new. We need to set the user attributes that we previously stored in session inside omniauth_callbacks#facebook

class User < ApplicationRecord
  def self.new_with_session(params, session)
    super.tap do |user|
      if user_attributes = session['devise.user_attributes']
        user.assign(user_attributes)
      end
    end
  end
end
Jay-Ar Polidario
  • 6,463
  • 14
  • 28
  • @Jay_AePolidario Thank you for answer. But I think I dont understand. The code I provided works great. But it is only for registering the user. Because if he wants to register, he must choose his role. But I cant use this for login. Because if the user exists, he will login. That is fine. But if he is not signed up already, it will register him without role. – Boomerange Jun 10 '17 at 12:26
  • You said that when you clicked the "login with facebook", you do no want to "register" him yet right? but just redirect him to the registration page right? My code doesn't register him yet (see above it says "build a new User object"), which will not save the user into the database yet. However, we want to store some "user data" like the roles and the the auth.id into the session (which was what your code is already doing, it seems). Now that these data are in the session after clicking login with facebook, then retrieve these session data in your RegistrationsController, and then set the user – Jay-Ar Polidario Jun 10 '17 at 15:24
  • @Jay-ArPolidarion Yes, but I still need to have a button for registering user using Facebook. My code is from Omniauth pages etc. and works great for registering user, but I need to have a login button which sign in registered user, but if he is not registered, then redirect him to register page. And I dont know if I can make a separate action of omniauth or have to use method self.from_omniauth. – Boomerange Jun 17 '17 at 23:44
  • Both "Register through Facebook" and "Login with Facebook" buttons both call "omniauth_callbacks_controller#facebook", so basically you won't need to create another separate action for that. You'll just need to do the **redirect/creation of user/storing the temporary user into session** all inside the action `facebook` as what you already currently have. – Jay-Ar Polidario Jun 17 '17 at 23:52
  • Ok, I get it, but how can I create user from season? Do I have to override Devise create action? – Boomerange Jun 17 '17 at 23:57
  • You could treat both "Register through Facebook" and "Login with Facebook" buttons exactly the same thing, only except that your actual users do not know they are the same thing, and is just meant to guide them as they are more familiar between differentiating "Register" and "Login" words. – Jay-Ar Polidario Jun 17 '17 at 23:57
  • Can you please be more specific? – Boomerange Jun 22 '17 at 11:24
  • were you referring to my last comment? or my last change to my answer? – Jay-Ar Polidario Jun 22 '17 at 11:26
  • If referring to my last change to my answer, I believe it was already specific. Let me know what it is that you do not understand. – Jay-Ar Polidario Jun 22 '17 at 11:33
  • If referring to my last comment, to be specific, "Register through Facebook" and "Login with Facebook" both exactly has the same link url, that url redirects to facebook (which opens Facebook page), then after authenticating with Facebook, Facebook calls the exact same callback for both those two buttons -- the callback being `omniauth_callbacks#facebook`. Therefore everything is exactly the same, only except that the two buttons contain two different text – Jay-Ar Polidario Jun 22 '17 at 11:39
  • @Jay-ArPolidarion I think we dont understand each other. I know that the user cant see the difference between login and register through Facebook. That is completely fine. But for me it is the special case. Register is on register page and login on login page. When user wants to register, he has to fill form with his role. Until now everything is fine. But the login page is specific. If he has an account, then ok, he will login. But if he is not registered yet and clicks on Facebook Login, he doesnt have a role. That is why I think I need to: override register action or write separate function – Boomerange Jul 09 '17 at 22:44
  • You said "When user wants to register, he has to fill form with his role. Until now everything is fine", what currently happens if a User is on the Register page, and then clicked the Register Through Facebook button (even though that user is already registered on your site)? Reason I'm asking is that because "Login Through Facebook" and "Registration Through Facebook" is distinguishable which I think you already know. A possible solution to your case to make it "distinguishable" is to add `state` params. See here https://stackoverflow.com/questions/9890985/passing-parameters-through-omniauth – Jay-Ar Polidario Jul 17 '17 at 10:11
  • Then you pass in the `state: 'register'` when on the Register page, and `state: 'login'` when on the Login page, therefore after authentication, Facebook redirects back to your callback: `def facebook`, but then now you can check `params[:state]` and check if the value is `register` or `login`, and does differently now as you need. Haven't really tried this myself yet, but hopefully it helps. – Jay-Ar Polidario Jul 17 '17 at 10:16
  • Hi, I have implemented it two weeks ago and the solution is similar to your last comment. Check my answer. – Boomerange Jul 26 '17 at 12:07
0

So I used simple params to get where the user come from:

<script type="text/javascript">
    document.cookie = "login=0"
</script>

This code is in views/devise/registrations/new.html.erb (and in views/devise/sessions/new.html.erb) and it is a JavaScript. It tells me if user went from login page (sessions folder - login=1) or from registration page (registrations folder - login=0). Then I use this code to determinate if user is from login page AND if he is not registered yet. If both conditions are true, then he is redirected to register page. It is so simple that I am embarrassed...

def facebook
    hash  = request.env["omniauth.auth"]
    info  = hash.info
    email = info["email"]

    user = User.find_by_email(email)

    login = cookies[:login]

    if (user == nil && login == "1")
      redirect_to new_user_registration_path
    else
      role = cookies[:role]
      user = User.from_omniauth(request.env["omniauth.auth"], role)
      if user.persisted?
        flash.notice = "Logged in by Facebook"
        sign_in_and_redirect user
      else
        flash.notice = "Error, try again."
        session["devise.user_attributes"] = user.attributes
        redirect_to new_user_registration_url
      end
    end
  end
Boomerange
  • 616
  • 1
  • 10
  • 18