Can someone explain the def current_user method in rails pls - ruby-on-rails

This is the line I don't quite get.
#_current_user ||= session[:current_user_id] && User.find(session[:current_user_id])
If there is a User model with an email field and a password field what is User.find looking for? ie there is no session field stored in the User model.

Session has to do with the controller in your application.
A session is a way to store information (in variables) to be used across multiple pages. Unlike a cookie, the information is not stored on the users computer.
Sessions are usually saved as a key: value hash pair, and they can get expired.
so, according to the code example you gave:
#_current_user ||= session[:current_user_id]
User.find(session[:current_user_id])
The line: #_current_user ||= session[:current_user_id] is setting the #_current_user to the value of current_user_id in session, if #_current_user is nil.
User.find(session[:current_user_id]) on the other hand is getting the value of current_user_id in session, which should be an id, and is going to the database to find the user with that id.
So, User.find(session[:current_user_id]) is finding by id, and not by email or by password

#current_user is meant as an instance variable to bring back the User object for the currently logged-in user.
The typical use case for #current_user (at least to Devise users like us) is to utilize it within our code infrastructure:
def create
#model = current_user.models.new ...
end
Thus, the answer to your question:
what is User.find looking for?
... is that it's looking for the User object for the signed-in member.
I think you're getting confused with how an authentication system would be expected to work. Namely that once you log in (authenticate), the app sets a session (as described by Sunny K) to denote which user is browsing.
This is why you have User.find(session[:current_user_id]) -- your authentication system (whether homebrew or pre-packed) has already validated the email & password. Now it has to keep track of which user you are, so that each time you send a request, it can rebuild your current_user object.
--
Another important factor is the fact that HTTP is "stateless" - meaning that each request has to be "new" (IE the state has to be recreated each time).
This is opposed to a stateful application, such as a game, where the fact you're running the application allows you to retain its state throughout the session.
As such, when Rails receives requests from the browser, it does not "remember" who you are - it's literally a dumb machine.
If you want access to user-specific information, there needs to be a way to firstly authenticate the user (which you have), and then authorize their request. The authorization part is up to you, but in order to make it work, you basically have to send the current user id to the app each time.
Then, when you invoke an instance of your program, the #current_user object will be available. This is what User.find(session[:current_user_id]) is for.

Related

Some questions about security in Rails 5

I've got a number of security concerns about my current application and wondering if I am leaving myself open to abuse, in the following arenas.
a) .My main access control method is by maining a current_user, current_company current_project method in my application controller. These methods return object based on stored session keys established when a user logs in and cleared when they log out. I.e if I want to know something about the current user, I can call "current_user.role" or if I want see whether the account a user is trying to change belongs to him, I check whether the associated account id which is requested in the url actually belongs to that user, essentially as follows
in Account controller
def account_info
redirect_to login_path if !user.logged_in
account_id=params[:account_id]
#account = Account.find(account_id)
unless account_belongs_to_user(account_id)
redirect_to unauthorized_path
end
end
In my application controller, when a user is initially authenticated, I do something like this:
session[:current_user_id] = user.id
and clear that session key when the user logs out.
Then when account is requested, and account_belongs_to_user is called, the application controller processes it, more or less like this:
def account_belongs_to_user(account_id)
account = Account.find(account_id)
return account.user_id==session[:current_user_id]
end
So I guess my security scheme ultimately relies on whether the session data is secure and not trivially spoofable.
b) When I render pages I sometimes pass objects which have senstive data to my erb pages to generate the page text.
For example, I might pass a "company" object (ActiveRecord) to the view to generate an invoice screen. But the company object, passed as #company, has a lot of sensitive data like access keys and the like. Not really being fully aware of the the internals, if I don't specifically include something like:
<%= #company.access_token %>
on my web page, can I be confident that the attributes of #company won't somehow be passed into the browser unless I specifically ask for them to be rendered on the page?
This is obviously an issue when using rails to serve data for say, AngularJS single page applications, as everything I pass for Angular to render the page I assume is probably accessible to an evil-doer even if not on the page itself, but I'm hoping that's not the case with pages generated server side by rails.
This may be a naive question, but thanks as I just want to be certain what I am doing before start spilling secrets all over the place.
put an authentication for the token using active_record callback
https://guides.rubyonrails.org/active_record_callbacks.html

Where is the Session Stored in Rails?

In Rails, I have implemented the below code for user auth (confirmed to be correct). However, I wanted to confirm my thinking for this strange session[:session_token]. is this the "cookie" that is stored in the browser?
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :current_user, :signed_in?
private
def current_user
#current_user ||= User.find_by_session_token(session[:session_token])
end
def signed_in?
!!current_user
end
def sign_in(user)
#current_user = user
session[:session_token] = user.reset_token!
end
def sign_out
current_user.try(:reset_token!)
session[:session_token] = nil
end
def require_signed_in!
redirect_to new_session_url unless signed_in?
end
end
My understanding so far of how this works is that whenever the browser/client sends a request to rails, the cookie (with the session[:session_token]) is also sent over, thus allowing the current_user method to find the user. Is my understanding correct? This is strange to me because there's a gap of knowledge of how exactly the browser/client gets access to the session cookie when we declare it in ApplicationController (Rails-side).
You are pretty much there. Although, I have a feeling you might be confusing apples with oranges...
Sessions:
Very often in dynamic web sites one would want to store user data between HTTP requests (because http is stateless and you can't otherwise associate a request to any other request), but you don't want that data to be readable and/or editable on the client-side inside of the URL (like.. yourwebsite.com/yourPage?cookie=12345&id=678), and so on..., because you don't want the client to play around with that data without passing through your server-side code.
One way to solve this problem is to store that data server-side, give it a "session_token"(as you called it), and let the client only know (and pass back at every http request) that token. This is how the session is implemented.
Cookies:
The most common technique for implementing sessions in Rails involve using cookies, which are small pieces of text placed on the user’s browser. Because cookies persist from one page to the next, they can store information (such as a session_token or whatever else you want) that can be used by the application to retrieve the logged-in user from the database.
Where is the Session Stored in Rails?
Using both of the above concepts I can now tell you that the default session store inside of Rails is CookieStore, which is about 4KB in size.
To put it simply...
def sign_in(user)
#current_user = user
session[:session_token] = user.reset_token!
end
...method that you defined places the user into a temporary session.
Then the idea is that the following...
def current_user
#current_user ||= User.find_by_session_token(session[:session_token])
end
...method would find and retrieve the user from the database corresponding to the session token and initialize it to a variable you specified.
Additional info:
You should also note that there is an important difference between Rails's session and cookies helper methods...
They both generate cookies, however, session[...] method generates temporary cookies, which should expire upon the browser exit, and cookies[...] method creates persistent cookies, which do not.
Additionally, I would suggest having a look at Section 2 of Ruby on Rails Security guide. You might find it useful.
Hope this helps you out.
Session is stored in server side. And,
Cookie is stored in client side (in browser cookie). And,
When client/browser send a request to rails server, every time cookies are sent to rails server.
When a session is set in rails server, like: session[:user_id] = 4,
Rails store it in server side.
Session is saved in server side like key value pair (like json object)
For each browser, Rails set a session identifier in cookie, so that, Rails can find the correct session information for a request.
Without session identifier in cookie, Rails do not know, what session belongs to what browser.
So, session will not work without cookie.
Edit: Explain: sessions are stored server side
Suppose, I am using your web application, and after login I will be redirected to home page.
I open login page, input username and password, and click login button.
The form is submitted to sessions#login action.
in sessions#login - you check username and password - and set session[:session_token]:
if username and password is correct
random_unique_identifier_string = #user.remember_token
session[:session_token] = random_unique_identifier_string
redirect_to root_url
end
When server run this code session[:session_token], server need an unique identifier for each browser session.
So, server generate an unique identifier for this browser, such as: abc123
Server set all session variables in a place (may be in some folder or in database), label this folder as abc123.
Now server send a cookie request to browser - to set cookie _ebook_session = abc123.
(I see, if my app name is ebook, in rails, cookie name is like: _ebook_session)
Now the page redirect to home page.
** Note: Everything above happen in single request **
Now, in my browser, I want to open some page that need authentication (suppose, dashboard page).
You added before_action: require_signed_in! in dashboard controller.
So, when I open dashboard page in my browser, browser by default send all cookies with every request. so _ebook_session cookie is sent to server. Your server gets the value of _ebook_session cookie is abc123. Now your application know we need to look in abc123 folder for session. Now you can get value of session[:session_token] from abc123 folder.
** I have explained second request above **
Each browser needs unique session identifier.
Important: _ebook_session cookie will be set in browser in first request. If we already have _ebook_session cookie set in a browser, we do not need to set it again, second, third and next requests in that specific browser.
I hope, you understand.

Ruby on Rails - login with cookie or username / password - always hitting the db

UPDATE: All, thanks for the responses - here is some more significant info.
I'm using a Neo4J graph database for the back-end, ROR 3 (MRI), and one single Neo4J database server accessed via REST.
If you don't know much about Neo4j, to use more than one database server (master/master) for data costs $26,000, which means I have to code for optimization now, not later, or come up with $26k...I'm sure you can guess which way I'm going with this..And I'm using it via rest, not locally etc, so performance matters...
I have one database server that has to handle all of the database work, and yes 1 ms counts under this scenario where some other queries take up to 40 ms. So no, I don't want to hit the database unnecessarily as it will simply add unnecessary work to it.
It might be easy to say "don't code for optimizations or problems you don't have yet" yet given the bottleneck and steep costs - and the fact I already have what I need done except for the authentication piece, it really doesn't apply.
What I simply wanted to know, was if the #current_user ||= is valid across pages.. The answer is that it's valid in a request, and not across them or pages. Yes this is a simple question, but sometimes they have to be asked in the midst of R&D and advanced stuff. Hence my gut feeling to stick with sessions to hold the id of the user logged in.
Thanks for your help!
I'm trying to allow a user to login to my site either by cookies or by username and password. The username/password part works fine, but having a problem with introducing cookies....
I've ready plenty of "how tos" including: http://ruby.railstutorial.org/chapters/sign-in-sign-out
I do not want to use a gem, devise, etc.
PROBLEM:
I notice that requests from the page to read current_user (a helper in the application controller) results in database reads, even though I'm using ||=..
I've tried the #current_user ||= User.find by blah blah blah
and it ALWAYS hits the database. This shoudn't be the case right? It should hit once and then that's it, correct?
But before you suggest any tutorial - I've seen tons of them - here is my question.. is #current_user saved across pages, or just for the current page? The link above mentions it only saves it for the current page...
You see, I don't want to keep hitting the database needlessly to find out the same person previously is logged in.. I can do that with session variables.
I really just want to check for a cookie,and would be happy to keep on using session[:user_id] after that.. for performance reasons, I do not want to keep hitting the database.
Any help?
My NEW code is below (and this too always hits the database as it should in this instance). I removed the typical #current_user ||= find_by.. because it was useless - it was always hitting the db.
.. I already tried https://github.com/cliftonm/basic-auth and http://ruby.railstutorial.org/chapters/sign-in-sign-out etc..
(ROR 3.2...)
class ApplicationController < ActionController::Base
protect_from_forgery
#before_filter :set_var
helper_method :current_user
private
def current_user
#current_user = User.find_by_id(session[:user_id]) #if session[:user_id]
if (#current_user)
return #current_user
end
#current_user =User.find_by_remember_token(cookies[:auth_token]) if cookies[:auth_token]
if (#current_user)
return #current_user
end
end
User.find will always hit the database unless you have some kind of cache extension loaded. Rails.cache can be configured several ways, but the most popular, if this sort of thing is required, is Memcached.
Unless you're dealing with massive scaling issues, the time required to fetch a user record should be less than 1ms, so it's hardly worth fussing about. Check your log/development.log to see the execution times of your various queries and focus first on the slowest ones.
Instance variables like #current_user persist only for the duration of the request. Remember that the HTTP protocol is stateless, each request exists independent of the others, and the only way to communicate state is via cookies, a persistent store like a database, a temporary store like an in-memory cache, or by parameters sent in with the request itself via GET or POST.
If you want to save something across pages, add it to the session, but be careful. You should only be persisting things like strings, numbers, or booleans. You should not be adding models. Further, using the default cookie store, each thing you put in the session increases the overhead on all requests made to your application from that point forward until you remove that from your session store.
Don't sweat the little things until you've got all the other problems solved. Business logic first, optimization second.
Neither of these should be slamming the database every time. Please let me know.
if (#current_user)
#current_user
else
#current_user = User.find_by_id(session[:user_id])
end
or
#current_user ||= User.find_by(id: session[:user_id])
Have you tried using a before_filter (instead of a helper) to load current_user only once? You would then store it / access it using an instance variable.
class ApplicationController < ActionController::Base
before_filter :load_current_user
def load_current_user
#current_user = # your code goes here.
end
end
Edit:
My bad. How about storing a server-side encrypted current_user record in the cookie, and keeping the hashsum in the session for later checkup?

Steps to create my own authentication system, need some guidance

I want to learn how to create my own authentication system, please provide some guidance if am doing this wrong.
I will create a Module in my /lib folder /lib/auth.rb
I will require this module in my ApplicationController.
when a user enters their email + password, I will call a method that will do a lookup in the user's table for a user with the same email, I will then compare the passwords. (i'll add encryption with salt later).
If the user entered the correct credentials, I will create a row in the Sessions table, and then write the session GUID to a cookie.
Now whenever I need to check if the user is logged in, or I need the user object, I will check if the cookie exists, if it does, I will lookup the session table for a row with the same guid, if it exists, I will return the session row and then load the User object.
I realize there are many suggestions one can give, but in a nutshell does this sound like a workable solution?
Now to make this usable, I will have to make some helper methods in my ApplicationController right?
How will I access the current_user from within my views?
P.S I know of other authentication systems, I just want to learn how to create my own.
The basic logic you're following is correct. Of course you can always expand on this with features that you need. For instance, you'll need helper methods for things like "logged_in?" and "current_user". Also, you might want to add session expiry, or session retention as a "remember me" feature.
Go for it, you won't learn authentication systems better than building your own then figuring what's wrong with it.
You should really check out the authlogic gem on github.
http://github.com/binarylogic/authlogic
It also has great instructions on how to set up your users.
After Faisal said what I would say, I only give you answer to the last part of your question:
"How will I access the current_user from within my views?"
try something like this:
class User < ...
def self.current=(u)
#current = u
end
def self.current
#current
end
end
In your views (or any part of your code) you can call User.current. Your controller has to assign a validated user to User.current. Your filters can react to "if User.current.nil?" and so on.
If you want to be thread safe, you may use a thread variable instead of #current:
Thread.current[:current_user] = u

Ruby on Rails session storage - how to *not* store certain fields in session store?

WARNING: Complete newbie to RoR and Ruby alert! *
I have a login method that looks like this:
#user = Person.find(:first, :conditions => ["email=?", params[:email]])
if #user and #user.password==params[:user_password]
session[:user] = #user
else
flash[:warn] = 'Invalid password!'
However, the user record can get very large, so I don't want to store the entire user record in my cookie session.
How can I modify this code so that a specific field does not get stored in the session? There are two fields that can get very large (very large user profile data) and will not fit within the cookie session 4 kilobyte limit, so I want to exclude those from being stored in the session.
I would do :
session[:user] = #user.id
And then create a before_filter going something like this:
before_filter :get_user
def get_user
#user = User.find_by_id(session[:user])
end
edit: This is not exactly what you were looking for, but if you can't store all the object in the session variable you might want to consider this option. It's only one request so it won't be too resource intensive. Plus like this you can check at every page load that the user exists and this might be helpful, security wise.
The design of Rails often serves to steer you in a very specific direction. In this case, the fact that by default Rails' sessions are stored in a cookie is a strong hint that you shouldn't be storing large objects in the session. By all means store the User ID, but not the User object itself.

Resources