AuthLogic perishable_token resets on every request - ruby-on-rails

In my User model I have:
acts_as_authentic do |c|
c.perishable_token_valid_for = 30.minutes
end
In my Application Controller I have the standard boilerplate code:
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
Now in my view I need to see if a user is logged in:
<% if current_user %>
Sign Out
<% else %>
Sign In
<% end %>
On every single request, current_user is being called, and that causes a SELECT call to be made to the database to find the user, then an UPDATE call that updates the last_request_at and perishable_token even though I set perishable_token_valid_for = 30.minutes.
Does anyone have a better way to see if a user is logged in without causing a SELECT and UPDATE on every single page of my app.
Does anyone know why the perishable token keeps updating even if I set it to be valid for 30 minutes???

perishable_token_valid_for isn't doing what you think it is. It's intended to work in tandem with find_using_perishable_token which is intended for things like account validation and resetting a forgotten password. The default timeout is 10 minutes.
The token is supposed to update on every request like it's doing. You can just remove the column if you don't want it. It's completely optional with authlogic.
If you really do want to keep the perishable token but update it completely by hand, you can do disable_perishable_token_maintenance = true

Related

Defining a method on current_user without it throwing a no method error on non logged in users

I made a moderator method thats in the user model
def mod_of_game?(guide_id)
game_mods_relationships.exists?(game_category_id: guide_id)
end
Problem is that whenever the user isn't logged in it just throws a no method error on the page.
I'll be making more user methods in the future and i can only assume i'll come across this problem every time.
I haven't tried it but i guess i could put an if else statement in the method
def mod_of_game?(guide_id)
if current_user.nil?
#empty method
else
game_mods_relationships.exists?(game_category_id: guide_id)
end
But I feel there is a more efficient way that i'm not aware of. I'm Building an app to learn rails better so i guess this is one of the things I just dont know.
The problem is that if no user is logged in, current_user will be nil, not an instance of the User class. So, there is no way to fix this inside the User model, as current_user is not a User if it is nil. Also, current_user is generally not available in the model, just in the controller and view.
What I would recommend is to add a filter in the controller, to make sure that if no user is logged in, the visitor will be redirected to the log in page. This can be done with a before_action filter in the controller, something like:
class YourController < ApplicationController
before_filter :authenticate_user!
...
end
Otherwise you can always check if current_user is nil before calling .mod_of_game?, like so:
current_user.mod_of_game?(#guide) unless current_user.nil?
Try following:
# It will return `nil` if user is not logged in
def mod_of_game?(guide_id)
game_mods_relationships.exists?(game_category_id: guide_id) if current_user
end
Your pattern is wrong.
Calling mod_of_game? is an instance method, which means it's got to be called on an instance of User.
By the nature of current_user, you wouldn't be able to call this method unless the user was logged in, or at least invoked.
You'll have to use all the conditions on the front-end to determine firstly whether current_user exists, and then to call mod_of_game? on it...
<% if user_signed_in? && current_user.mod_of_game?(#guide) %>
--
A much better way would be to either create your own helper method, or to use the .try method:
#app/helpers/application_helper.rb
class ApplicationHelper
def mod? guide
return false unless current_user
current_user.mod_of_game? guide
end
end
This would allow you to call:
<% if mod? #guide %>
... which will return false if the user is not signed in, or the user is not a mod.
The reason the pattern is bad is because you're having to base logic on two conditions: user signed in? AND are they a mod?
What you want is a single point of logic, which will return true or false:
<% if current_user.try(:mod_of_game?, #guide) %>

Session Helper Methods in Michael Hartl Tutorial Chapter 8

I'm going through Michael Hartl tutorial on Ruby on Rails and I'm having trouble understanding some logic. Note that the logic works, it's just not resonating with me on what's actually happening.
In this chapter we're logging in users and creating a session. Here the helper methods:
module SessionsHelper
# Logs in the given user.
def log_in(user)
session[:user_id] = user.id
end
# Returns the current logged-in user (if any).
def current_user
#current_user ||= User.find_by(id: session[:user_id])
end
# Returns true if the user is logged in, false otherwise.
def logged_in?
!current_user.nil?
end
end
Depending on whether the user's logged on or not we change the navigation with this conditional:
<% if logged_in? %>
do something....
<% else %>
do something else...
<% end %>
As I understand it, the code checks to see if the user is logged in by calling the logged_in? method. The logged_in? method calls the current_user method to see if it's nil. If it is nil it returns false, if it's not nil it returns true. As a test I tried to change the logged_in? method to the following:
def logged_in?
!#current_user.nil?
end
When I run this method for some reason, after I log in with credentials that are authenticated, #current_user returns nil. Why is this? Note this works if I change it back to the original logged_in? method where I'm just calling the method current_user.
This is not a direct answer to your question since you have that figured it out while I was trying to answer. But I want to clarify on some points.
In rails, in fact most of the web application, we track a user's log in state in server's session. the log_in method in your code does that.
Then when the new request comes in to a controller that requires authentication, we check the session if there is a stored user. If it exists then the request is authenticated, else it's unauthenticated. So, logged_in? method's actual responsibility is to check the session.
However, it is quite common that we want to access the authenticated user's attributes in the controller and/or views. So we set an #current_user variable on the controller so that you can access the User object of authenticated user. Again, using an instance variable directly is not a good practice. So we wrapped it in the current_user method.
Then you might ask, why don't we store the whole user object in session? Because it is bad to store much in session(see here). So, we just store the id and use it to get the user from db.
Here is where the ||= part comes in. ||= caches the result of db. Otherwise, we would be hitting db every time we call current_user method.
Hope this clarifies a bit on what's actually happening.
As I was formulating my question I figured this out. In the later case
def logged_in?
!#current_user.nil?
end
#current_user isn't set yet because the current_user method was never called. To test, I changed the method to the following:
def logged_in?
current_user
!#current_user.nil?
end
where the method was called first and then #current_user was evaluated. It worked without issue. The original method works because current_user returns #current_user to the logged_in? method as either a user object or nil (#current_user is set as the last line in the method -- it's the only line, so it's retuned implicitly to the logged_in? method).

Rails - Why use self.current_user = user in sign_in method

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.

Why am I getting error Undefined method `name' for nil:NilClass with Ruby on Rails?

I thought methods such as name and email were default in rails?
In my static pages view, in profile.html.erb I have:
<% if logged_in? %>
<% provide(:title, #user.name) %>
<% else %>
<% provide(:title, 'Profile')%>
<% end %>
I put in my static_page_controller
def profile
#user = User.find_by_remember_token(:remember_token)
end
When I go to the console User.find_by_remember_token("actualtoken").name returns me the appropriate users name, but :remember_token does not. How do I make :remember_token = the logged in users remember token?
In my sessions_helper I have
def log_in(user)
cookies.permanent[:remember_token] = user.remember_token
current_user = user
end
def logged_in?
!current_user.nil?
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= user_from_remember_token
end
def log_out
current_user = nil
cookies.delete(:remember_token)
end
private
def user_from_remember_token
remember_token = cookies[:remember_token]
User.find_by_remember_token(remember_token) unless remember_token.nil?
end
end
copying it to my static_pages_helper didn't accomplish anything.
Quick things you should be aware of the rails framework and the ruby language:
A function defined in any of your helpers will be available to all helpers and views (so there is no reason to copy and paste the same functions through different helpers);
You're probably using an authentication gem and I guess it is the Devise gem. If this is right, then you should not be overriding their helpers unless you have a reason to do this;
User.anything will call the static function anything from the User class;
user = User.find_by_anything(the_thing) is a class static helper provided by ActiveModel that will query the database looking for a user that has *anything = the_thing*; this user or nil will be returned;
user.an_attribute will call a function that returns the user specified attribute (which is the same as the column name of this attribute by default);
user.try(:anything) will try to call the function anything from the user and return its value. If user is nil, the returned value will also be nil.
That said, I guess you just wanted to retrieve the current user remember token, which can be accomplished with the following:
user = current_user.try(:remember_token)
EDITED: The question is a bit messy, but I also think the following code will work with your controller:
def profile
#user = User.find_by_remember_token(params[:remember_token])
end
You must access the request's parameters through the params hash.
EDIT: completely replaces my first answer with one hopefully not as stupid :-)
(While there are several ways to implement and manage sessions in Rails, the default uses a cookie in the browser to reference a key stored in memory. Sessions are created by a request from a browser, so while it's certainly possible to use the console to get at an existing session, it's probably not what you want.)
So your method, user_from_remember_token will either return a user or nil. What I don't see in your code is where you're setting the remember_token on the User model. I'll assume it's there, but you may want to have code that tells the user to log in if you don't find them. A common pattern would be
def current_user
#current_user ||= user_from remember_token
unless #current_user
flash[:notice] = "Yo! Log in first."
redirect_to login_path and return
end
end
There's no problem calling a model finder from a separate controller. But why call User.find_by_remember_token(:remember_token) -- you don't have the remember_token yet (right?). Don't you just want to call the current_user method in your sessions helper?
If the method is not visible, you may want to include or require the session helper in your application_controller.rb

"Rails by Example" authentication implementation

In Michael Hart's book this code is used to implement authentication:
module SessionsHelper
def sign_in(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt] #permanent
# -alternatively-
# cookies.signed[:remember_token]={
# :value => [user.id, user.salt],
# expires => some_time.from_now
# }
current_user = user
end
def current_user=(user)
#current_user = user
end
def current_user
return #current_user ||= user_from_remember_token
end
private
def user_from_remember_token
#passes array of length two as a parameter -- first slot contains ID,
#second contains SALT for encryption
User.authenticate_with_salt(*remember_token)
end
def remember_token
#ensures return of a double array in the event that
#cookies.signed[:remember_token] is nil.
cookies.signed[:remember_token] || [nil,nil]
end
end
It does it's job very well, I can either log in for an infinite amount of time, or for a limited period of time as I wish.
But it has a downside, cookies are stored on the client and they dont go away even if the browser is closed
Now I was wondering, since rails sessions get destroyed after a browser is closed, how would I combine them and the cookies presented here to implement authentication with the following characteristics:
-- if a user logs in,
the data should be stored in a session
so that after a user closes his browser they get logged of
-- if a user logs in, with a 'remember me' checkbox selected
their data should be stored in a cookie with a long expiration date
What would be a take on this that remains secure and simple?
I googled on the web and found nothing recent enough (Rails 3) that
could guide me in the right direction.
I was thinking of creating 2 separate modules for sessions and cookies and fire their respective sign_in methods in the controller whether a remember_me param was present or not, but that would seem a lot of duplication.
PS I am not looking into any authentication gems to provide this functionality, id prefer to implement it on my own.
Thanks

Resources