Failure to Redirect on user Sign-in - ruby-on-rails

I have a rails app . I have created a sessionscontroller and want to redirect to users page '/users' once the user signs in. But the redirect doesnt seem to be happening.
class SessionsController < ApplicationController
def create
user = User.find_or_create_by_fbid(params[:user][:fbid]) #...Success
user.update_attributes(params[:user]) #....Sucess
sign_in(user) # ....This occurs successfully
redirect_to users_path # .... Redirect doesnt occur on the browser side
end
end
The sign_in method is defined inside the Application Controller
class ApplicationController < ActionController::Base
def sign_in(user)
session[:fbid] = user.fbid
#current_user = user
end
end
Server Logs below . The redirect actually seems to be happening on the server side . But I do not see any change on the client side. The browser doesnt change page.
The UsersController
class UsersController < ApplicationController
def index
#users = User.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #users }
end
end
end
Original Ajax Post -
$.post("/sessions",{user:{name:profile.name, email:profile.email,fbid:profile.id}});
The redirect occurs successfully if I use a javascript redirect statement inside the $post() as a callback function .
$.post("/sessions",{user:{name:profile.name, email:profile.email,fbid:profile.id}},function( data ) {
window.location="/users";
}
);

You need to handle the response's redirection in an ajax query. Normally the browser handles, but it won't with ajax.
From this SO question (edited slightly for your case)
var params = {user:{name:profile.name, email:profile.email,fbid:profile.id}};
$.ajax({
type: "POST",
url: "/sessions",
data: params,
dataType: "json",
success: function(data, textStatus) {
if (data.redirect) {
// data.redirect contains the string URL to redirect to
window.location.href = data.redirect;
}
else {
// data.form contains the HTML for the replacement form
$("#login_form").replaceWith(data.form);
}
}
});

I'm using a real basic authentication based of Ryan Bates' screencast. If you can't figure it out from my code, I recommend watching Authentication from Scratch
applications_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
sessions_controller.eb
class SessionsController < ApplicationController
def new
end
def create
user = User.authenticate(params[:email], params[:password])
if user
session[:user_id] = user.id
redirect_to root_url, :notice => "Logged in!"
else
flash.now.alert = "Invalid email or password"
render "new"
end
end
def destroy
session[:user_id] = nil
redirect_to root_url, :notice => "Logged out!"
end
end
Ajax Post -
$.post("/sessions",{user:{name:profile.name, email:profile.email,fbid:profile.id}});

Related

How can I set auth header with JWT in Rails?

I'm quite new to RoR programming and stuck while trying to migrate my authentication from Clearance to JWT. I created all of the required methods in ApplicationController and UsersController and even managed to sign up a user, save the user's password_digest to the database and then log in a user (in terms of POST params, I mean that no errors were thrown). However, I fail to keep the user logged in. I understand that there should be an auth_header attached to each request by my user, but how do I create one? I googled it multiple times, but failed to find how to handle it in terms of front-end. Everybody seems to use these fancy apps with buttons to create all the required headers and send raw json data ((
In other words, I have my JWT token encoded in the entrance method (as posted below) but I cannot understand how to pass it as a header in each and every request to the app further on?
users_controller.rb
class UsersController < ApplicationController
def create
#user = User.create(user_params)
if #user.valid?
token = encode_token({ user_id: #user.id })
redirect_to root_path
else
render json: { error: "Invalid email or password"}, status: :unprocessable_entity
end
end
def entrance
#user = User.find_by(email: user_params[:email])
if #user && #user.authenticate(user_params[:password])
token = encode_token({ user_id: #user.id })
redirect_to root_path
else
render json: { error: "Invalid email or password"}, status: :unprocessable_entity
end
end
def login
render 'login'
end
def signup
#user = User.new
render 'signup'
end
application_controller.rb
class ApplicationController < ActionController::Base
helper_method :current_user
def encode_token(payload)
JWT.encode(payload, 'secret')
end
def decode_token
auth_header = request.headers["Authorization"]
if auth_header
token = auth_header.split(' ')[1]
begin
JWT.decode(token, 'secret', true, algorithm: 'HS256')
rescue JWT::DecodeError
nil
end
end
end
def current_user
decoded_token = decode_token()
if decoded_token
user_id = decoded_token[0]['user_id']
#user = User.find(user_id)
end
end
def signed_in?
current_user.present?
end
def authorize
if signed_in?
redirect_to root_path
else
redirect_to "/log_in"
end
end
I truncated the code a bit, but all the relevant methods are included.

RoR - NoMethodError, undefined method

I have created an app with simple login authentication, it is actually a twitter clone. The user logs in and access the pages, etc.
But when the user posts something from there profile. It gives an error
NoMethodError in RibbitsController#create
undefined method `id=' for nil:NilClass
The error is around line 5:
class RibbitsController < ApplicationController
def create
#ribbit = Ribbit.create(user_ribbits)
#ribbit.userid = current_user.id
if #ribbit.save
redirect_to current_user
else
flash[:error] = "Problem!"
redirect_to current_user
end
end
private
def user_ribbits
params.require(:ribbit).permit(:content, :userid)
end
end
The request given to app:
Request
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"dwVmjDNO4GOowphGFgChMDBxBfvka+M/xSUHvJMECzwxtv4NF6OuWtiaX74NLz91OwQJ9T9+wm7yMiPQ0BLpGA==",
"ribbit"=>{"content"=>"hi. test.\r\n"},
"commit"=>"Ribbit!"}
The sessions controller:
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_username(params[:username])
if user && user.authenticate(params[:password])
session[:userid] = user.id
redirect_to rooturl, notice: "Logged in!"
else
flash[:error] = "Wrong Username or Password."
redirect_to root_url
end
end
def destroy
session[:userid] = nil
redirect_to root_url, notice: "Logged out."
end
end
The users controller:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.create(user_params)
if #user.save
session[:user_id] = #user.id
redirect_to #user, notice: "Thank you for signing up!"
else
render 'new'
end
end
def show
#user = User.find(params[:id])
#ribbit = Ribbit.new
end
private
def user_params
params.require(:user).permit(:name, :username, :email, :password, :password_confirmation, :avatar_url)
end
end
And the application controller
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
end
I would really appreciate it if you guys would help!
Thanks.
You're trying to assign current_user.idto #ribbit.userid without ensuring that current_user is set. 'current_user' would be set only if a user has been previously saved before.
Therefore, you need either to make sure that an authenticated user is trying to create a Ribbit, or if you consider the userid as a non mandatory field, you can simply change your line 5 by:
#ribbit.userid = current_user.id unless current_user.blank?
If you only want authenticated user to create Ribbits, then consider using a gem to handle authentication such as Devise. You could then use before_filter :authenticate_user! in your controller to make sure users are properly authenticated.

Using Clearance with API

I am currently using Clearance from Throughbot for my authentication. I am needing to add an API to my product and can't seem to find docs about using Clearance with an API. Is there a certain Header I can set that Clearance will check automatically and if not what can I use? I think I may be able to use this.
To get around this I ended up overriding the authenticate methods on the ApplicationController and the User model. It looks something like this:
class ApplicationController < ActionController::Base
include Clearance::Controller
include Clearance::Authentication
def authenticate(params)
if request.headers['AUTH-TOKEN']
return nil unless user = User.where(remember_token: request.headers['AUTH-TOKEN']).first
sign_in user
else
User.authenticate(params[:session][:email], params[:session][:password])
end
end
#rest of class omitted for bevity
end
Then I subclasses SessionsController to override the create method like so:
class SessionsController < Clearance::SessionsController
def create
#user = authenticate(params)
sign_in(#user) do |status|
respond_to do |format|
if status.success?
format.html { redirect_back_or url_after_create }
format.json { render json: #user, status: :ok }
else
format.html do
flash.now.notice = status.failure_message
render template: 'sessions/new', status: :unauthorized
end
format.json { render json: [errors: status.failure_message], status: :unauthorized }
end
end
end
end
#rest of class omitted for bevity
end
Then all you have to do to test or use is set the requests AUTH-TOKEN header to the users remember token and you're all set. I chose to use the remember token because it is updated whenever the user logs out. You may not want this to happen and could instead generate a auth_token field on your model and change the where to use the new field.

return redirect_to in private controller method

Preface: I'm using devise for authentication.
I'm trying to catch unauthorized users from being able to see, edit, or update another user's information. My biggest concern is a user modifying the form in the DOM to another user's ID, filling out the form, and clicking update. I've read specifically on SO that something like below should work, but it doesn't. A post on SO recommended moving the validate_current_user method into the public realm, but that didn't work either.
Is there something obvious I'm doing wrong? Or is there a better approach to what I'm trying to do, either using devise or something else?
My UsersController looks like this:
class UsersController < ApplicationController
before_filter :authenticate_admin!, :only => [:new, :create, :destroy]
before_filter :redirect_guests
def index
redirect_to current_user unless current_user.try(:admin?)
if params[:approved] == "false"
#users = User.find_all_by_approved(false)
else
#users = User.all
end
end
def show
#user = User.find(params[:id])
validate_current_user
#user
end
def new
#user = User.new
end
def edit
#user = User.find(params[:id])
validate_current_user
#user
end
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to #user, :notice => 'User was successfully created.' }
else
format.html { render :action => "new" }
end
end
end
def update
#user = User.find(params[:id])
validate_current_user
respond_to do |format|
if #user.update_attributes(params[:user])
format.html { redirect_to #user, :notice => 'User was successfully updated.' }
else
format.html { render :action => "edit" }
end
end
end
private
def redirect_guests
redirect_to new_user_session_path if current_user.nil?
end
def validate_current_user
if current_user && current_user != #user && !current_user.try(:admin?)
return redirect_to(current_user)
end
end
end
The authenticate_admin! method looks like this:
def authenticate_admin!
return redirect_to new_user_session_path if current_user.nil?
unless current_user.try(:admin?)
flash[:error] = "Unauthorized access!"
redirect_to root_path
end
end
EDIT -- What do you mean "it doesn't work?"
To help clarify, I get this error when I try to "hack" another user's account:
Render and/or redirect were called multiple times in this action.
Please note that you may only call render OR redirect, and at most
once per action. Also note that neither redirect nor render terminate
execution of the action, so if you want to exit an action after
redirecting, you need to do something like "redirect_to(...) and
return".
If I put the method code inline in the individual controller actions, they do work. But, I don't want to do that because it isn't DRY.
I should also specify I've tried:
def validate_current_user
if current_user && current_user != #user && !current_user.try(:admin?)
redirect_to(current_user) and return
end
end
If you think about it, return in the private method just exits the method and passes control back to the controller - it doesn't quit the action. If you want to quit the action you have to return again
For example, you could have something like this:
class PostsController < ApplicationController
def show
return if redirect_guest_posts(params[:guest], params[:id])
...
end
private
def redirect_guest_post(author_is_guest, post_id)
redirect_to special_guest_post_path(post_id) if author_is_guest
end
end
If params[:guest] is present and not false, the private method returns something truthy and the #show action quits. If the condition fails then it returns nil, and the action continues.
You are trying and you want to authorize users before every action. I would suggest you to use standard gems like CanCan or declarative_authorization.
Going ahead with this approach you might end up reinventing the wheel.
In case you decide on using cancan, all you have to do is add permissions in the ability.rb file(generated by rails cancan:install)
can [:read,:write,:destroy], :role => "admin"
And in the controller just add load_and_authorize_resource (cancan filter). It will check if the user has permissions for the current action. If the user doesnt have persmissions, then it will throw a 403 forbidden expection, which can be caught in the ApplicationController and handled appropriately.
Try,
before_filter :redirect_guests, :except => [:new, :create, :destroy]
should work.
This is because you are using redirect twice, in authenticate_admin! and redirect_guests for new, create and destroy actions.
"Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action."
That's the reason of the error. In show method, if you are neither the owner of this account nor the admin, you are facing two actions: redirect_to and render
My suggestion is to put all of the redirect logic into before_filter

How to properly validate a user before displaying page in Rails

In my application, I store the user's ID in session[]. At the beginning of every controller action, I'm calling a method defined in the ApplicationController called current_user:
def current_user
#current_user ||= session[:current_user_id] &&
User.find_by_id(session[:current_user_id])
end
At the beginning of my controllers' methods, I have the following:
#current_user = current_user
if #current_user == nil
redirect_to :home
return
end
This is obviously repetitive code and should be a method somewhere. I read the answer for this question, and tried putting my method into a parent class that my controller classes now descend from, however it seems like I can't redirect from that method now.
In my parent class, I have:
def verify_user
user = current_user
if user == nil
redirect_to "/"
return
end
return user
end
And now I've changed my controller methods to this:
#current_user = verify_user
This doesn't work, and I think I know why. For one, I can't simply call return in my verify_user method, as that obviously will just return to the controller. The redirect doesn't seem to have any affect, probably because format.html is being called after the redirect call, which was the reason for the return in the original code.
So, what am I doing wrong here, and what suggestion do you have to solve it? Is this the wrong approach? My main goal is to keep the entire "check if user is logged in otherwise redirect" to one line of code per controller method.
Take a look at the devise gem https://github.com/plataformatec/devise. It handles a lot of this basic user authentication logic for you. This specific problem can we solved by adding before_filter :authenticate_user! to the controllers or actions that need to be guarded.
Add the following logic to the ApplicationController class:
class ApplicationController < ActionController::Base
def current_user
...
end
def logged_in?
current_user.present?
end
def require_user
return true if logged_in?
render_error_message("You must be logged in to access this page",
new_user_session_url)
return false
end
def render_message message
respond_to do |format|
format.html {
if request.xhr?
render(:text => message, :status => :unprocessable_entity)
else
redirect_to(root_url, :notice => message)
end
}
format.json { render :json => message, :status => :unprocessable_entity }
format.xml { render :xml => message, :status => :unprocessable_entity }
end
end
end
Now add a before_filter to your controller:
class OrdersController < ApplicationController
before_filter :require_user
end

Resources