I'm developing a rails app that creates a widget that you can put on your website. When the widget is displayed (on a website with a different host) to a user who is logged in my app, I would like to display him some additional admin options.
What would be the best and easiest way to figure out if the user is logged in the app?
I was thinking of storing the IP when user logs in, and then compare the IP from the request that is sent to the widget controller.
I followed the Omniauth Railscast Episode and have been using session variables and a SessionsController to create and destroy sessions when the user logs in and out.
class SessionsController < ApplicationController
def create
# create user if new user, or find user account if returning user.
session[:user_id] = user.id
redirect_to root_url # or wherever
end
def destroy
session[:user_id] = nil
redirect_to root_url # or wherever
end
end
Then in the Application Controller,
class ApplicationController < ActionController::Base
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
You can then easily determine if a user is logged in or not via if current_user, or equivalently, if session[:user_id] is nil.
IP could be deceptive. Try cookies.
Edit: not only in an actively deceptive manner (i.e. spoofing/Tor) but rather if two people are on separate sites from the same public IP, then you have a false correlation.
Related
I want to make it so that a session has to be present in order to use a site. If not, then redirect to the root path, so that a user can choose whether to browse the site as a guest, log in or register. I'm using a basic authentication made from scratch, based on a Railscast.
in the app controller
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_action :set_artists
before_filter :check_session
helper_method :current_user
private
def set_artists
#artists = Artist.all
end
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def check_session
unless current_user
redirect_to root
end
end
end
I had a guest user logged in, then wiped out all of the guest users through the Rails console: User.where(guest: true).destroy_all. I have a rake task that wipes out guest sessions that are 1 day old, so this would a pretty typical situation. Trying to reload after that, the error comes up: Couldn't find User with 'id'=8
The problem is that your users will keep their cookies, so when you run:
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
the user still has the session cookie with a :user_id and the call to User.find is failing to find the now-deleted user.
A solution is to replace this with a call that can fail, such as:
def current_user
#current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
end
Where find will raise an exception if the User cannot be found, find_by will just return nil leaving your current_user empty.
That being said, I agree with the earlier commenter that this is not really the "Rails way." Deleting all the guest users each day is probably not the best way to get the behavior you want.
Instead, one idea is that you could add a timestamp to the User model indicating when the user was last asked to validate as a guest, and then if more than 24 hours have elapsed, you could bounce guest users back to the page that prompts them to register. You don't have to delete their User entry; you can just reuse it when they re-register.
How do I guarantee that users only access the routes on my web app if they are logged in? I already have Users and Session models and users are able to create accounts. But how do I make sure that if they are not logged in they are always redirected to the login/sign up page, but if they are they have access to all the routes?
EDIT: So this is what my Application Controller looks like right now:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
So if there isn't a current user, I want to allow access only to the my Pages controller and its actions (which are basically home, signup, login, etc.). If there is a user, on the other hand, I want that user to be able to access all the routes in my route file.
class SomeController < ApplicationController
def show
if current_user.nil?
redirect_to '/path/to/login'
end
end
end
could probably give a more detailed answer if you paste in some code otherwise we all are just guess what your methods are called.
If you are using devise it comes with the built in helper method authenticate_user! which should be placed in your application controller.
If you are not using devise you can define you own method (for this example I will copy devise) authenticate_user! in application controller and call the before action
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
hide_action :current_user
private
def authenticate_user!
redirect_to :root if current_user.nil?
end
end
My user model has the attributes password, password_confirmation, username, email, and admin.
I'm wondering how exactly do I check whether the currently logged in user is an admin. How would I go about the methods? I've tried if user.admin? on my views, but it seems that doesn't work.
I'm new to rails so any suggestions would be helpful!
There is a "session" hash which persists through the whole session.
Once a user has logged in, you would store the current user's id in the session hash, like so
session[:user_id] = user.id
Now, if you want the current user to be accessible from your controllers and in your views, you can go to apps/controllers/application_controller and make some useful methods...
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user, :signed_in?, :is_admin?
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def signed_in?
!!current_user
end
def is_admin?
signed_in? ? current_user.admin : false
end
end
Now in your views you can use "is_admin?" to check if the current user is an admin, use
"signed_in?" to check if a user is signed in, and "current_user" to access the user object if it exists (if no user is logged in, "current_user" will be nil)
Cheers
I suggest you to consult with this Devise guide. It shows how to create a basic user model with help of Devise and how to perform admin checks. And yes, by giving you this link, I strongly encourage you to use Devise gem for users and all that stuff ;)
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've been looking around recently into Rails and notice that there are a lot of references to current_user. Does this only come from Devise? and do I have to manually define it myself even if I use Devise? Are there prerequisites to using current_user (like the existence of sessions, users, etc)?
It is defined by several gems, e.g. Devise
You'll need to store the user_id somewhere, usually in the session after logging in. It also assumes your app has and needs users, authentication, etc.
Typically, it's something like:
class ApplicationController < ActionController::Base
def current_user
return unless session[:user_id]
#current_user ||= User.find(session[:user_id])
end
end
This assumes that the User class exists, e.g. #{Rails.root}/app/models/user.rb.
Updated: avoid additional database queries when there is no current user.
Yes, current_user uses session. You can do something similar in your application controller if you want to roll your own authentication:
def current_user
return unless session[:user_id]
#current_user ||= User.find(session[:user_id])
end