I'm using restful_authentication plugin for Ruby on Rails. All seems fine except that it seems the user session is not getting created at all. I have the create method below. It appears that the self.current_user is being set but that the actual session is never created. When and how is the current_user_session supposed to be defined. I have the method in my application controller but this is where it always fails.
def create
logout_keeping_session!
user = User.authenticate(params[:login], params[:password])
if user
# Protects against session fixation attacks, causes request forgery
# protection if user resubmits an earlier form using back
# button. Uncomment if you understand the tradeoffs.
# reset_session
self.current_user = user
new_cookie_flag = (params[:remember_me] == "1")
handle_remember_cookie! new_cookie_flag
redirect_back_or_default('/')
flash[:notice] = "Logged in successfully"
else
note_failed_signin
#login = params[:login]
#remember_me = params[:remember_me]
render :action => 'new'
end
end
Application_Controller
def current_user_session
return #current_user_session if defined?(#current_user_session)
#current_user_session = UserSession.find
end
def current_user
return #current_user if defined?(#current_user)
#current_user = current_user_session && current_user_session.user
end
UserSession model is empty
Do not use restful_authentication if you can avoid it. There are a number of better alternatives out there that are actually RESTful and better maintained:
Clearance
Authlogic
Devise
When you say session, do you actually mean a session or is this some restful_authentication magic?
I used to use restful_authentication, and some older apps still do. However, they used cookie-based session management and not a user session model.
Are you using rails 2.3.5?
I am seeing issues with this using redirect_to, basically removing any variables added to the session before the redirecting.
Reverting to 2.3.4 seems to of solved my problem, but there is a bug on lighthouse in regards to some weirdness to session in rails 2.3.X
This may not be same issue for you, but has taken me hours to realise a revert fixed my issue, so might be worth a quick test.
Related
I'm new to rails and building app from data on an api, I have two tables I want to use for users - students and educators.
I can authenticate in the controllers with
private
def fetch_user_data(username, password)
require 'URI'
uri = URI('the url for the api')
res = Net::HTTP.post_form(uri, 'username' => username, 'password' => password)
xml = res.body
doc = Nokogiri::Slop(xml)
#status = doc.auth.status.content.to_s
#username = doc.auth.username.content.to_s
#token = doc.auth.token.content.to_s
#person_id = doc.auth.person_pk.content.to_i
#security_roles = doc.auth.security_roles.content.to_s.downcase
end
def assign_user
if /faculty/ =~ #security_roles
#user = Educator.find_by(person_id: #person_id)
elsif /student/ =~ #security_roles
#user = Student.find_by(person_id: #person_id)
end
end
def authenticate_user(username, password)
fetch_user_data(username, password)
assign_user
session[:user_id] = #user.id
redirect_to #user
end
Now I know it's probably not pretty, but I'm learning as I go. I use the authenticate_user() in the sessions controller, and based on the redirect, the authentication seems fine. I do have one question about the :user_id key in the session -- is that a key only created for the session or is it trying to pull a value from a user table? Knowing that would help. My guess is it's just created for the session, but I have no idea.
Ok so now for my real problem. I'm trying to use cancan and I'm getting stuck at defining current user.
I figured I could have the #current_user instance point to the #user I assigned in assign_user. This doesn't seem to be working though. I tried a couple things, but I'm stuck. Maybe I can't even do that? Larger question? Do I have to have a user model to make cancan work? Can I use the two models Educators and Students and mask the user references in cancan on those?
I tried this, but it's not working -- any help?
def current_user
#current_user ||= #user
end
edit: Figured out a bit.
1. methods were in ApplicationHelper. Moved current_user() to ApplicationController . Changed syntax to conventional, but added conditionals.
def current_user
if Student.where(id:session[:user_id]).count == 0
#current_user ||= Educator.find(session[:user_id])
else
#current_user ||= Student.find(session[:user_id])
end
end
This seems to fix things, and allows me to use both tables as the user models.
There are several ways to define current_user (if you aren't using Devise), but this one here is pretty standard:
class ApplicationController < ActionController::Base
def current_user
#current_user ||= User.find(session[:user_id])
end
end
As for your question about the session, the way you have it set up, you're setting session[:user_id] equal to #user.id (which is always the same for each user).
minor aside consider using the gem CanCanCan which is as it sounds. CanCan not maintained and some one set up replacement CanCanCan. You won'thave to change any code.
If you watch over any of Ryan Bates Authentication related Railscasts you'll see a recurring theme when creating sigin/signout functionality and I wanted to understand that a little bit more clearly.
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
For example usually in a session controller the create action will contain an assignment to the sessions hash such as session[:user_id] = user.id given that the variable user is set to an Active Record Object.
The above helper method is then used throughout the views to find the current signed in user.
However when signing out the destroy action contains only the line session[:user_id] = nil
My question is wouldn't #current_user also be needed to set to nil since it would be set to the previous User that was signed in?
Typically after setting session[:user_id] = nil your controller will return so #current_user still being active doesn't matter. You have to remember that #current_user only exists for that request, the next request that comes through is a new instance of that controller class.
You are right that if you did something like this:
def destroy
session[:user_id] = nil
logger.debug current_user.inspect # Current user is still set for this request
redirect_to admin_url, notice => "You've successfully logged out."
end
You would see the user information in the log file, but normally you are doing a redirect right after clearing the session[:user_id] so that controller instance is done.
I'm working on an application that needs to use session id information. My session is stored in cookies. The problem I have is that my session is not immediately available to the controller when a user comes to the site for the first time. I think I may be missing something about how sessions are initialized in Rails. But I'm positve about the fact that the session is not loaded because this is the output of session.inspect:
#<Rack::Session::Abstract::SessionHash:0x15cb970 not yet loaded>
Here is how to reproduce the problem with Rails 3.2.11 and ruby 1.9.3:
Create a new application with a test controller:
rails new my_app
cd my_app/
rails g controller test
rm app/assets/javascripts/test.js.coffee
touch app/views/test/index.html.erb
Try to get the session id in that controller:
class TestController < ApplicationController
def index
puts session[:session_id]
puts session.inspect
end
end
Add the needed routes:
MyApp::Application.routes.draw do
resources :test
end
Then access the application and see what it does:
rails server
got to: http://localhost:3000/test
That is the output in the console:
#<Rack::Session::Abstract::SessionHash:0x3fd10f50eea0 not yet loaded>
Then again http://localhost:3000/test and this time we have a session:
400706c0b3d95a5a1e56521e455075ac
{"session_id"=>"400706c0b3d95a5a1e56521e455075ac", "_csrf_token"=>"Euaign8Ptpj/o/8/ucBFMgxGtiH7goKxkxeGctumyGQ="}
I found a way to force initialization of the session. Accessing the session apparently does not force initialization but writing into the session does. What I do in my controller is this now:
class MyController < ApplicationController
protect_from_forgery
def index
session["init"] = true
do_stuff
end
end
Still I'm not sure if this should be considered normal behavior in Rails. It doesn't look right to me having to write into the session to force initialization. Reading should be enough.
I agree with #joscas answer but instead of writing a value, I'd delete it as to not have redundant data.
class MyController < ApplicationController
protect_from_forgery
def index
session.delete 'init'
do_stuff
end
end
The session is loaded this way too.
Note: Make sure you don't use the key to be deleted in your application.
Here's some relevant code from ActionDispatch::Session:
def [](key)
load_for_read!
#delegate[key.to_s]
end
private
def load_for_read!
load! if !loaded? && exists?
end
Which implies that the session object will be loaded as soon as you access any value by its key via [].
I don't really understand your question. If you require a user to register or sign in before being able to access the site there should be no problem. When creating a user his information is immediately stored in a cookie. For example:
User controller: (registering is done through users#new)
def create
#user = User.new(params[:user])
if #user.save
cookies.permanent[:remember_token] = user.remember_token
redirect_to root_path, notice: "Thank you for registering!"
else
render :new
end
end
Sessions controller: (signing in is done through sessions#new)
def create
user = User.find_by_email(params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
cookies.permanent[:remember_token] = user.remember_token
redirect_to root_path, notice: "Logged in."
else
flash.now.alert = "Email or password is incorrect."
render :new
end
end
So the problem probably boils down to the fact that the cookie that stores session_id wasn't created yet at the point where you tried to access the session.
When you read session object, Rails calls a private method session.load_for_read!, but, as its name suggests, it only loads session for reading and doesn't instantiates the session if it simply doesn't exist.
On the other hand, calling session.merge!({}) (for example) forces session instantiation.
Yo! #zetetic's answer above made me realize that we can just do
session.send(:load!)
and it'll load the session 🙂.
I have finished the Ruby on Rails Tutorial by Michael Hartl. I know some basic ideas about instance variable, getters and setters.
The sign_in method is here
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
Now I'm stuck at this line
self.current_user = user
I found this related question, but I still don't get it.
After sign_in, the user will be redirected to another page, so #current_user will be nil. Rails can only get current_user from cookie or session, then set #current_user, so that it doesn't need to check cookie or session again in current request.
In sign_out method
def sign_out
self.current_user = nil
cookies.delete(:remember_token)
end
For the same reason, why do we need self.current_user = nil since the user would be redirected to root_url?
Here's the code for getter and setter
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
You are right that the #current_user is not set after the redirection.
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
This statement helps avoid repeated calls to the database, but is only useful if current_user variable is used more than once for a single user request. Consequently, setting the current user is only helpful during a single call.
Similarly, setting the current user to nil and removing the token from cookies during sign_out ensures that subsequent processing will take the signing out into account. Otherwise, there is a risk of other methods referring current user variable and thinking that the user is still logged in.
You have a full explanation on the next section of the book
Current User
Basically when you do self.current_user= you invoque the method 'def current_user= ()' this is the setter, you will be probably not only assigning the #current_user variable here but also keeping some reference in the cookies or session for future reference. In the same way you will probably be creating an accessor that will look like
def current_user
#current_user ||= get_user_from_cookies
end
In order to have accesible the current user. I think you just went to fast and the book is trying to go step by step for users not familiarised with web dev
I believe you're right in saying that for the code you've written so far it doesn't make much difference.
However it doesn't make sense for your sign_in/sign_out methods to know the ins and outs of how users travel through you application. It would be very brittle (and not its business) if it assumed that the only thing your application did after login was to redirect the user to the root page.
You could be doing all sorts of things, from collecting audit data (record every time someone logs in for example) to redirecting them to a different page depending on the users preferences or some other attribute of the user.
I use authlogic to authenticate users. In my controllers I use current_user, defined (as documented) as follows:
def current_user_session
return #current_user_session if defined?(#current_user_session)
#current_user_session = UserSession.find
end
def current_user
return #current_user if defined?(#current_user)
#current_user = current_user_session && current_user_session.record
end
I also use declarative_authorization to manage the current user's permissions. All works fine in the normal runtime scenario, but when I create functional tests that use request statements like " get_with", current_user in the controller is nil. I looked through the declarative_authorization test helper code and found that in this scenario, the declarative_authorization gem actually stores the current user in Authorization.current_user (which in turn comes from Thread.current["current_user"]). So there seems to be quite a mixup of how a current user is handled in different scenario's.
My question: what is the appropriate way of finding the current_user in both the normal runtime and the test scenario?
You can define a before_filter like this in application_controller.
before_filter { |c| Authorization.current_user = c.current_user }
Angelus, you were right. I shouldn't be using get_with, post_with, etc. These just set the Authorization.current_user, session[:user] and session[:user_id] and these seem to be obsolete with authlogic. And for some reason, these even set UserSession to nil, which was causing the problem. So UserSession.create(users(:admin)), followed by a regular get, post, etc. is the way to go.