I'm learning Rails and I'm trying to restrict access to pages if a user hasn't logged in and to only allow them to view the login and sign up pages.
Currently, my code creates a session when a user logs in and clears it when the user logs out. I've got a Sessions helper so that I can check whether a user is logged in but I'm unsure how to redirect the user throughout the app if he/she's not logged in.
UPDATE:
As I posted the question, I managed to get something to work with a before_filter. Should I use a before_action or before_filter?
Do I need to copy the same method in all my controllers where I want to restrict access?
CODE:
/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
include SessionsHelper
end
/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out
redirect_to root_url
end
end
/helpers/sessions_helper.rb
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
# Logs out the current user.
def log_out
session.delete(:user_id)
#current_user = nil
end
end
You can use a before_action. The rails guide has a nice section with an example on that:
class ApplicationController < ActionController::Base
before_action :require_login
private
def require_login
unless logged_in?
flash[:error] = "You must be logged in to access this section"
redirect_to new_login_url # halts request cycle
end
end
end
Related
I'm going through Michael Hartl's The Ruby on Rails Tutorial, Chapter 8.3 Logging Out Sessions and I don't understand how removing the session[:user_id] can remove the #current_user as well:
here is the SessionController:
class SessionsController < ApplicationController
def new
end
def create
user =User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in(user)
redirect_to user
else
#flash.now will only flash once - if a new request or view is rendered,the flash will go away now
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out
redirect_to root_path
end
end
and here is the SessionsHelper for the login and logout helpers:
module SessionsHelper
def log_in(user)
session[:user_id] = user.id
end
def current_user
#find the user if #user is not defined yet, or else, just keep the current user
#that way we dont have to do a database search every time current_user is called
#current_user ||= User.find_by(id: session[:user_id])
end
def logged_in?
!current_user.nil?
end
def log_out
session.delete(:user_id)
end
end
The way I understand, once #current_user is defined once logged in, shouldn't the variable still last even though the session[:user_id] has been removed since it is being set to itself?
#current_user ||= User.find_by(id: session[:user_id])
There was no action that I am aware of that removed the #current_user variable. But when I tested it during the debugger, I can see that once someone logs out, #current_user becomes nil.
Can someone explain the mechanics to me?
The session persists between requests. But the instance variable #current_user only persists for the length of one request. When the destroy action redirects to the root_path that is the start of a new request which will load the root page.
You may want to try this out so see that clearing the user_id out of the session doesn't clear out the instance variable:
def destroy
# Test code to initialize #current_user
current_user
Rails.logger.debug("#current_user before: #{#current_user.inspect}")
log_out
# Test code to check #current_user after updating session
Rails.logger.debug("#current_user after: #{#current_user.inspect}")
redirect_to root_path
end
And then check what ends up in log/development.log. #current_user will still be present after log_out but it will go away at the end of the request.
The core of my app is build from Michael Hartl's rails tutorial. Recently I changed the User DB so the name column is named username and I also added in friendly_id for prettier URLs. I updated everywhere I could find in accordance with these changes and everything works smoothly except for friendly forwarding after a user logs in. e.g. if a non logged in user visits a user edit page they are taken to the login page, after they login they should be taken to their edit page. But instead it just logs them in and stays on the login page.
I cant see anywhere that would need changing so it works again and from what I can see it should work
session controller
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_back_or user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
user controller
before_action :correct_user, only: [:edit, :update]
before_action :logged_in_user, only: [:edit, :update]
def edit
end
private
# Confirms a logged-in user.
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
def correct_user
#user = User.friendly.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
application controller
def redirect_back_or(path)
redirect_to request.referer || path
end
private
# Confirms a logged-in user.
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "You gotta login first!"
redirect_to login_url
end
end
session helper
# Redirects to stored location (or to the default).
def redirect_back_or(default)
redirect_to(session[:forwarding_url] || default)
session.delete(:forwarding_url)
end
# Stores the URL trying to be accessed.
def store_location
session[:forwarding_url] = request.url if request.get?
end
Anyone know where the problem might lie?
Update
after chatting with #vishal the redirect is done from the application controller redirect_back_or method
def redirect_back_or(path)
redirect_to request.referer || path
end
If I change it to
def redirect_back_or(path)
redirect_to root_path || path
end
I am taken to the root path so I know this line os most likely the culprit.
In-between it working and not working I added the mailboxer gem for private messages, changed the Users db name to username and added friendly_id to the username column. Maybe something there might stand out to you as a cause for it stop working.
In your application controller, change redirect_back_or(path) method to this.
def redirect_back_or(path)
redirect_to session[:forwarding_url] || path
session.delete(:forwarding_url)
end
You had redirect_to request.referer before which was responsible for redirecting you to the last url, that is, the login_url.
I have no idea why you're defining two different methods - 1 in ApplicationController & 1 in SessionsHelper - with the same name but different intended behaviors though.
I am implementing omniauth for twitter and I have run into an error "Couldn't find User with 'id'=true" the error is pointing to the application controller current_user metho. Heere is my current_user method:
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
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
and here is my User model:
class User < ActiveRecord::Base
def self.find_or_create_by_auth(auth_data)
user = where(provider: auth_data[:provider], uid: auth_data[:uid]).first_or_create
user.update(name: auth_data[:info][:name])
end
end
and finally the SessionController is below:
class SessionsController < ApplicationController
def create
#user = User.find_or_create_by_auth(request.env["omniauth.auth"])
session[:user_id] = #user
redirect_to products_path, notice: "logged in as "
end
def destroy
session[:user_id] = nil
redirect_to root_path, notice: "Goodbye!!"
end
end
When I trying to log in the error pops and i can't get past login to load my index page.
I think your problem should be solved with following modification in User.find_or_create_by_auth method:
def self.find_or_create_by_auth(auth_data)
# all previous code
# you should return user from here
# your current code returned true of false
user
end
Also you should save #user.id in session, not full #user object:
session[:user_id] = #user.id
I finally solved this, the issue was that i had deleted the previous authenticated twitter user in the db and i was trying to authenticate with the same credentials again on the app.
so what i did is create a new twitter app and use different keys to authenticate into my rails App... hoep this explains it thanks
EDIT I've provided more detail on my objectives below.
I am having trouble deep linking users to my site after they log in through the landing page using the Omninauth gem.
The most common scenario is this: a user receives an email with a deep link to the site - say www.mysite.com/suggestions/15. We've placed a Facebook login (through Omniauth) on the landing page, (def landing page on the Authorization controller - see below). When a user tries to access any other page without being logged in they are bounced back via the authenticate_user! helper method placed in the relevant controllers. authenticate_user is defined in the Application Controller -see below.
Once they get bounced back they click a "log in with facebook" button on the landing page and we create a session for the user through def authcreate. This works in the classic omniauth way, i.e. a callback from facebook which captures the session token. Now at this point I want to redirect the user to the page they were trying to get to (e.g. www.mysite.com/suggestions/15) but instead they only go through the default page (www.mysite.com/suggestions). This is not a problem when the user is already logged in, but never works when they are logged out.
You can ignore authorize! as that is a separate module (confusing I know) which is concerned with admin rights. Ditto on check_authorization.
I've created a module and placed it into lib/
module RedirectModule
def self.included(controller)
controller.send :helper_method, :redirect_to_target_or_default
end
def redirect_to_target_or_default(default, *args)
redirect_to(session[:return_to] || default, *args)
session[:return_to] = nil
end
def redirect_to_or_default(target, *args)
redirect_to(target || default, *args)
end
private
def store_target_location # Friendly redirect: store URL.
session[:return_to] = request.url
end
end
There is an include in the ApplicationController:
class ApplicationController < ActionController::Base
protect_from_forgery
check_authorization
include RedirectModule
def current_user
if session[:user_id]
#current_user ||= User.find(session[:user_id])
end
end
helper_method :current_user
def authenticate_user!
if !current_user
store_target_location
redirect_to "/"
end
end
I initiate the sessions on the following controller:
class AuthorizationController < ApplicationController
def landingpage
authorize! :auth, :landingpage
if current_user
redirect_to_target_or_default('/suggestions')
else
store_target_location
end
end
def authcreate
authorize! :auth, :authcreate
reset_session
authHash = env["omniauth.auth"]
existingUser = User.where(authHash.slice(:provider, :uid)).first
user = User.from_omniauth(authHash)
session[:user_id] = user.id
redirect_to_target_or_default('/suggestions')
end
def authdestroy
authorize! :auth, :authdestroy
session[:user_id] = nil
redirect_to "/"
end
end
What am I doing wrong?
I am working on a basic authentication system for a rails app. The authentication is verifying account information from Active Directory using a net-ldap class (this part is working fine).
Something seems to be wrong with my session_helper however. Even though ActiveDirectoryUser.authenticate is successful, the signed_in helper always returns false. After signing in, the script redirects to root_path (default_controller's home) and then immediately redirects back to signin_path again-- as a result of the signed_in helper returning false.
See the code below. What am I missing?
Thanks
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
include SessionsHelper
end
default_controller.rb
class DefaultController < ApplicationController
before_filter :signed_in_user
def home
end
private
def signed_in_user
redirect_to signin_path, notice: "Please sign in." unless signed_in?
end
end
sessions_helper.rb
module SessionsHelper
def sign_in(user)
#current_user = user
end
def current_user
#current_user ||= nil
end
def signed_in?
!#current_user.nil?
end
def sign_out
#current_user = nil
end
end
sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = ActiveDirectoryUser.authenticate(params[:session][:username],params[:session][:password])
if user.nil?
# authentication failed
flash.now[:error] = 'Invalid email/password combination'
render 'new'
else
# authentication succeeded
sign_in #user
flash[:error] = 'Great success'
redirect_to root_path
end
end
def destroy
sign_out
redirect_to root_path
end
end
You should use session for to persist that kind of data (will be assessable for every request), it's user data. But I highly recommend you to use something like the devise gem that do all that authentication things and more for you. Why reinvent the weel right?
I believe this would work for you.
module SessionsHelper
def sign_in(user)
session[:user_id] = user.id
end
def current_user
ActiveDirectoryUser.find(session[:user_id]) ||= nil
end
def signed_in?
!session[:user_id].nil?
end
def sign_out
session[:user_id] = nil
end
end