2

I have a controller PostsController, which allows user to create posts before logging in. But to save it, user has to log in using Omniauth.

In PostsController, I have:

class PostsController < ApplicationController
  before_filter :authenticate_user_by_service, :only => :create
  def create
    ...
  end

private
  def authenticate_user_by_service
    redirect_to user_omniauth_authorize_path(:facebook)
  end

Now, I have another controller to handle callback from facebook, which is called ServicesController

class ServicesController < ApplicationController
  def create
    auth = request.env["omniauth.auth"]
    ... authentication logic here ...
    sign_in(:user, service.user)
  end
  method_alias: :facebook, :create

Normally, for authentication, after sign in, I'll redirect to :back.

However, services#create here is used as a before_filter. In this case, what should I do to get it back to my Posts#create ?

Update: I got this warning saying the filter chain is interrupted right at the moment I refer to a different method

Started POST "/posts" for 127.0.0.1 at 2013-02-26 23:47:41 -0500
Processing by PostsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"H0as8=", "post"=>{"post"=>"bla bla"}, "commit"=>"Create Post"}
Redirected to http://localhost:3000/users/auth/facebook
Filter chain halted as :authenticate_user_by_service rendered or redirected

1 Answer 1

1

You're approaching this wrongly. You're handling login and the check wether someone is logged in in the same step.

Consider using a sessions_controller to handle all your signup/login/logout logic, for example:

class SessionsController < ApplicationController
  def new # this will be /login
    session[:return_to] = params[:returnto] unless params[:returnto].nil?
    redirect_to "/auth/facebook"
  end

  def create # this will be the callback after the user is authenticated
    auth_token = request.env["omniauth.auth"]["credentials"]["token"]
    # you'll need to write this based on your app's requirement. 
    # Find a user or create one if he doesn't exist yet.
    user = User.find_or_create_authenticated_user(auth_token) 

    if user.present?
      session[:user_id] = user.id # this stores the current user's id in your session and lets Rails remember him for you.
      redirect_to return_or(products_url) # see below for the usage of return_or
      return
    end

    redirect_to root_url, alert: 'User not found or invalid'
  end

  def destroy # /logout
    session[:user_id] = nil
    redirect_to root_url
  end
end


#routes.rb
match '/logout' => 'sessions#destroy', :as => :logout
match '/login' => 'sessions#new', :as => :login
match '/auth/facebook/callback' => 'sessions#create'

Then, in your ApplicationController you set up a couple of helper methods:

class ApplicationController < ActionController::Base

  protected  
  # Use this in your views and controllers to get 
  # a handle of the user that is currently logged in. 
  # it will return nil if there is no user logged in.
  def current_user
    @current_user ||= User.find(session[:user_id]) if session[:user_id]
  end
  helper_method :current_user

  # Use this to check wether a user is logged in. 
  # returns true if there is a user, false otherwise.
  def logged_in?
    !current_user.nil?
  end
  helper_method :logged_in?

  # Block access to controllers or actions based on 
  # wether there's a user or not.
  def require_login
    unless logged_in?
      # if we need the user to log in, we send him on his way
      # but send him back to the current page afterwards. 
      session[:return_to] = request.fullpath
      redirect_to root_url(subdomain: false), :alert => "Please login"
    end
  end

  # When a user is not logged in, but you send him to log in,
  # you can force him to return to the url he was in or if nothing
  # was set go to a standard path. 
  # See this being set up in SessionsController#new and in require_login and then
  # being used in SessionsController#create
  def return_or(path)
    session.delete(:return_to) || path
  end
  helper_method :return_or
end

These helper methods are available in all your controllers since they all inherit from ApplicationController. You could then tell your PostsController to send users who are not logged in to go and log in and after doing that they get returned to the PostsController.

Then to address your requirement of saving a post only after authentication: Either you do create the post, save it, but only update it to being public after the user is authenticated, or you store the contents of the post in the session and restore them after the user is authenticated:

class PostsController < ApplicationController
  def new
    @post = Post.new(session[:post_params] || {})
  end

  def create
    if logged_in?
      @post = Post.create(params[:post])
      # ... etc
    else
      session[:post_params] = params[:post]
      session[:return_to] = new_post_path
    end
  end
end

Note that this is a rather vulnerable approach. I would rather suggest to actually create the Post, mark it as not yet public and store only the post's id in the Session. After authentication you could then find that post_id, recreate the object from, set its status to public and associate it with the current_user.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a lot for a very detailed answer. Could you kindly explain what happens in Posts#create if the user does not login? Should I invoke require_login within this method?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.