basic ruby on rails authentication trouble - ruby-on-rails

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

Related

Restrict access in Rails app only for users logged in

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

Michael Hartl’s Rails Tutorial chapter 9 (section 9.2.3 Friendly Forwarding) tests failed

I am new to programming and getting the following errors while trying to get through the 9th chapter of Rails Tutorial. I checked the code several times but still didn’t understand why my local variable or method isn’t being defined. Every time I rewrite the code I get similar errors: undefined local variable or method ‘current_user’.
Error:
ERROR["test_layout_links", SiteLayoutTest, 0.888518]
test_layout_links#SiteLayoutTest (0.89s)
ActionView::Template::Error: ActionView::Template::Error: undefined local variable or method `current_user' for #<#<Class:0x007fcd97c44cf0>:0x007fcd97c4c4a0>
app/helpers/sessions_helper.rb:22:in `logged_in?'
app/views/layouts/_header.html.erb:8:in `_app_views_layouts__header_html_erb__1982327839123609485_70260496954760'
app/views/layouts/application.html.erb:12:in `_app_views_layouts_application_html_erb___2753884707929057206_70260450931560'
test/integration/site_layout_test.rb:6:in `block in <class:SiteLayoutTest>'
app/helpers/sessions_helper.rb:22:in `logged_in?'
app/views/layouts/_header.html.erb:8:in `_app_views_layouts__header_html_erb__1982327839123609485_70260496954760'
app/views/layouts/application.html.erb:12:in `_app_views_layouts_application_html_erb___2753884707929057206_70260450931560'
test/integration/site_layout_test.rb:6:in `block in <class:SiteLayoutTest>'
sessions_controller
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
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_back_or user
else
flash.now[:danger] = 'Invalid email/password combination' #Not quite right!
render 'new'
end
end
def destroy
log_out if logged_in?
redirect_to root_url
end
end
users_controller
class UsersController < ApplicationController
before_action :logged_in_user, only: [:edit, :update]
before_action :correct_user, only: [:edit, :update]
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
log_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
def edit
end
def update
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# Before filters
# 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
# Confirms the correct user.
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
end
sessions_helper
module SessionsHelper
#logs in the given user.
def log_in(user)
session[:user_id] = user.id
end
#Remembers a user in a persistent session.
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
#Returns the user corresponding to the remember token cookie.
def current_user?(user)
user == current_user
end
#returns true if the user is logged in, false otherwise.
def logged_in?
!current_user.nil?
end
#Forgets a persistent session.
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
#logs out the current user.
def log_out
forget(current_user)
session.delete(:user_id)
#current_user = nil
end
# 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
end
sessions_helper_test
require 'test_helper'
class SessionsHelperTest < ActionView::TestCase
def setup
#user = users(:michael)
remember(#user)
end
test "current_user returns right user when session is nil" do
assert_equal #user, current_user
assert is_logged_in?
end
test "current_user returns nil when remember digest is wrong" do
#user.update_attribute(:remember_digest, User.digest(User.new_token))
assert_nil current_user
end
end
I think you may be missing the current_user method. It's defined here.
Though the name is similar, current_user is a completely different method to current_user?. The question mark is a convention that typically means the method will return either true or false.
You're missing a current_user method in your sessions_helper.rb. Check back in Chapter 8.57

is a correct_user before action here necessary?

I have 2 models, users and common_app.
Users has_one common_app.
In the common_app controller, I define almost everything using the current_user helper. This essentially makes it so that the edit forms ignore the id that the user POSTs via the web browser.
It looks like so -->
class CommonAppsController < ApplicationController
before_action :signed_in_user
def new
if current_user.common_app.present?
redirect_to current_user
else
#common_app = current_user.build_common_app
end
end
def create
#common_app = current_user.build_common_app(common_app_params)
if #common_app.save
flash[:success] = "Common App Created!"
redirect_to root_url
else
redirect_to 'common_apps/new'
end
end
def update
if current_user.common_app.update_attributes(common_app_params)
flash[:success] = "Common App Updated"
redirect_to root_url
else
render 'common_apps/edit'
end
end
def show
#common_app = current_user.common_app
end
def edit
#common_app = current_user.common_app
end
private
def common_app_params
params.require(:common_app).permit(:current_city,:grad_year,:read_type,
:listen_speak,:time_in_china,
:cover_letter,:resume) ####fill in the correct ones here
end
# is correct_user necessary?
end
What makes me wary though is that I am not using a correct_user before action. If I were to not use it, would there be a security hole here? I.e could someone POST through a shell or something?
If yes, how would you change the controller, to include the before filter?
PS: I'm also a bit confused about the correct use of # variables. If I am overusing them, or doing something wacky with them, please let me know and help me become a better noob :)
PPS: This is my SessionsHelper Module, for the signed_in_user before filter to work -- >
module SessionsHelper
def sign_in(user)
remember_token = User.new_remember_token
cookies.permanent[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
self.current_user = user
end
def signed_in?
!current_user.nil?
end
def current_user=(user)
#current_user = user
end
def current_user
remember_token = User.encrypt(cookies[:remember_token])
#current_user ||= User.find_by(remember_token: remember_token)
end
def current_user?(user)
user == current_user
end
def sign_out
self.current_user = nil
cookies.delete(:remember_token)
end
def redirect_back_or(default) # this creates friendly forwarding for the app
redirect_to(session[:return_to] || default)
session.delete(:return_to)
end
def store_location
session[:return_to] = request.url if request.get?
end
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
end
I don't see any security problem here. Even without the before_action :signed_in_user, since you always go through the current_user.common_app association, if a user where not signed in, the action would simply fail.
So the controller is sound. As long as your authentication system has no flaws, the controller itself exposes no weakness.
In Ruby, variables prefixed with '#' are instance variables. In the context of a Rails controller, the distinction is simple:
Use instance variables for values you want to make available to your view, and normal variables for everything else.

Does self refer to the ApplicationController, UsersController, or SessionsController?

There are questions similar to this but none were able to help me. I am still learning rails and am making a basic user signup system. In the signin method of the SessionsHelper module, I use the self keyword.
module SessionsHelper
def sign_in(user)
remember_token = User.new_remember_token
cookies.permanent[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
self.current_user = user
end
def current_user=(user)
#current_user = user
end
def current_user
remember_token = User.encrypt(cookies[:remember_token])
##current_user ||= User.find_by_remember_token(:remember_token) #The find_by method might not work
#current_user ||= User.where(remember_token: remember_token).first
end
def signed_in?
!current_user.nil?
end
end
I include the module in the ApplicationController class like so:
class ApplicationController < ActionController::Base
protect_from_forgery
include SessionsHelper
end
I think that means that the keyword self in the SessionsHelper module would therefore always refer to the ApplicationController class. However, shouldn't the current_user actually rrefer to the Sessions_controller? The sign_in method is also in the SessionsController and the UsersController, but based on my understanding of self, when the method is called inside these classes, it should still refer to the ApplicationController because that is where the SessionsHelper module is included. Here is the code for the UsersController:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
sign_in #user
flash[:success] = "Welcome to the Sample App"
redirect_to #user
else
render 'new'
end
end
end
Here is the SessionsController:
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
sign_in user
redirect_to user
else
flash.now[:error] = 'Invalid email.password combination'
render 'new'
end
end
end
Thanks to anyone who can help. I've been trying to understand this for hours.
#...snip
self.current_user = user
end
The self here is the class this module has been included into. So it executes the next line
def current_user=(user)
#current_user = user
end
This stores an instance variable on the controller -- the ApplicationController. Generally, all other controller inherit from the ApplicationController, so this affects the whole system.

Sessions Helper Method Not Defined

I have a sessionsController and I'm trying to add a redirect_back_or method in my sessions_helper to allow friendly forwarding.
Here is the error I get:
undefined method `redirect_back_or' for #<SessionsController:0x007f9fa1b51ec0>
I have restarted the server and can't figure out why it's not finding this method in my helper.
My Sessions helper code is as follows:
module SessionsHelper
def deny_access
store_location
redirect_to signin_path, :notice => "Please sign in to access this page."
end
def redirect_back_or(default)
redirect_to(session[:return_to] || default)
clear_return_to
end
private
def store_location
session[:return_to] = request.fullpath
end
def clear_return_to
session[:return_to] = nil
end
end
My sessions controller is
class SessionsController < ApplicationController
def create
auth = request.env["omniauth.auth"]
user = User.find_by_provider_and_uid(auth["provider"], auth["uid"]) || User.create_with_omniauth(auth)
session[:user_id] = user.id
redirect_back_or user
#redirect_to root_url, :notice => "Signed in!"
end
def destroy
session[:user_id] = nil
redirect_to root_url, :notice => "Signed out!"
end
end
Put that method in ApplicationController:
class ApplicationController < ActionController::Base
private
def redirect_back_or(default)
redirect_to(session[:return_to] || default)
clear_return_to
end
end
Or include the SessionsHelper module in your controller to use the method:
class SessionsController < ApplicationController
include SessionsHelper
...
You are attempting to call a method that's on your SessionsHelper file from your Controller. The helper file is for adding methods to be used in the view. You should move the redirect_back_or method to the SessionsController. Or if you want to be able to use this method repeatedly in multiple controllers, it might be preferable to put it in your ApplicationController. I would recommend you do the same with your deny_access method. It would make more sense to do that anyways since you wouldn't be redirecting from a view file anyways.

Resources