So I am building an application that I am trying to never need a database as the application will just be a portal to an API. I have a sessions controller and I am trying to use a cookie based session but the setter method is never being hit. Here is what I have at this point.
sessions_controller.rb
class SessionsController < ApplicationController
def new
if current_user
redirect_to snapshots_path
end
end
def create
api = API.new
response = api.authenticate_user(params[:session][:username].downcase, params[:session][:password])
if response["Message"] == "success"
current_user = response["User"]
binding.pry
redirect_to snapshots_path, notice: "Signed in successfully."
else
flash.now[:error] = "Invalid username/password combination."
render :new
end
end
def destroy
current_user = nil
redirect_to sign_in_path
end
end
sessions_helper.rb
module SessionsHelper
def current_user=(user)
binding.pry
if user
#current_user = user
cookies[:userdata] = { :value => user, :expires => 8.hours.from_now.utc }
else
#current_user = nil
cookies.delete(:userdata)
end
end
def current_user
binding.pry
#current_user ||= (cookies[:userdata] ? cookies[:userdata] : nil)
end
end
The getter method is hit correctly every time but the setter is never getting hit. Any ideas as how to fix this thanks.
When you are assigning to current_user it's treating it as a local variable. To solve that simply assign to self.current_user instead. The getter doesn't need that because there is no local variable named that so ruby looks for a method and uses that. If you reference the getter as self.current_user that would also work.
For example change:
current_user = response["User"]
to:
self.current_user = response["User"]
Include SessionsHelper in your SessionsController in order to access SessionHelper methods within SessionsController.
Code will work fine without any modification i.e., you would be able to access current_user and current_user= directly.
class SessionsController < ApplicationController
include SessionsHelper ## Just add this
..
end
Related
I'm new to Rails, and am working on a practice app that involves a simple login function. I've been following a tutorial from CodeAcademy by the books, however the code is not working in quite a few ways. First of all, the sessions do not set, even though Rails is executing the rest of the code inside the "if" block shared with the session declaration (btw, no errors are returned).
The session controller:
class SessionsController < ApplicationController
def new
end
def create
#user = User.find_by_username(params[:session][:name])
if #user && #user.authenticate(params[:session][:password])
session[:user_id] = #user.id
redirect_to '/posts'
else
session[:user_id] = nil
flash[:warning] = "Failed login- try again"
redirect_to '/login'
end
end
def destroy
session[:session_id] = nil
redirect_to login_path
end
end
Extrapolating from that issue, my "current_user" function is not working, which is most likely because the session is not being set.
The application controller:
class ApplicationController < ActionController::Base
def current_user
return unless session[:user_id]
#current_user ||= User.find(session[:user_id])
end
def require_user
redirect_to '/login' unless current_user
end
end
Any help is much appreciated. Let me know if you need to see anything else.
NOTE: I know I should use Devise, and I am planning to in my future, more serious projects. However, like I said, this is a practice/test app to help develop my coding skills, and before using a "magical" gem like Devise I want to get hands-on experience with making a login system myself.
I think the error is that session_controller is not able to find the current_user.
Write the following code in application_controller:
class ApplicationController < ActionController::Base
helper_method :current_user
def current_user
return unless session[:user_id]
#current_user ||= User.find(session[:user_id])
end
def require_user
redirect_to '/login' unless current_user
end
end
Letme know if it works
There are a few possible problems.
First, #current_user is not set until the current_user method is called. And as #Neha pointed out, you'll need to add a helper method to your ApplicationController so that all your views will have access to the current_user method. Add this line to your ApplicationController:
helper_method :current_user
Now, to diagnose the problem, let's set something up that lets you get some visibility into your session and current_user.
First, in views/layouts/application.html.erb, just after the line that says <= yield %>, add this:
<%= render 'layouts/footer' %>
Then add a new file views/layouts/_footer.html.erb:
<hr/>
Session:<br/>
<% session.keys.each do |key| %>
<%= "#{key}: #{session[key]}" %><br/>
<% end %>
<br/>
User:<br/>
<%= current_user&.username || '[None]' %>
Now at the bottom of every view you can see the details of your session.
In your sessions#create action, you have a potential problem with finding your User. You are using params[:session][:name] where you probably should be using params[:session][:username].
Also, tangentially, the proper way to destroy a session is not by setting session[:id] to nil, but instead to use reset_session. So your SessionsController should look like this:
class SessionsController < ApplicationController
def new
end
def create
#user = User.find_by_username(params[:session][:username])
if #user && #user.authenticate(params[:session][:password])
session[:user_id] = #user.id
redirect_to '/posts'
else
session[:user_id] = nil
flash[:warning] = "Failed login- try again"
redirect_to '/login'
end
end
def destroy
reset_session
redirect_to login_path
end
end
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.
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.
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
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.