For the app that I am writing, I am using simple hand made authentication (as described on Railscast.com). Using some code from Ryan Bates' NiftyGenerators gem, I have an authentication model that has some useful methods for authentication. This module is included into application_controller.rb.
One of the methods that I want to use is called redirect_to_target_or_default. I know this is what I need to redirect a user to the page that they were on once they have authenticated but I don't know where I should call this method? If someone could give me an idea on how to use this method, I would greatly appreciate it.
ControllerAuthenticaion Module Code
module ControllerAuthentication
# Makes these methods helper methods in the controller that includes this module.
def self.included(controller)
controller.send :helper_method,
:current_admin, :logged_in?,
:redirect_to_target_or_default
end
def current_admin
#current_admin ||= Admin.find(session[:admin_id]) if session[:admin_id]
end
def logged_in?
current_admin
end
def login_required
unless logged_in?
store_target_location
redirect_to login_url,
:alert => "You must first log in before accessing this page."
end
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
def store_target_location
session[:return_to] = request.url
end
end
Did you run the generator(s) within Ryan's gem? It should have generated a SessionController (refer to link) for you with also this method in it:
def create
#session = Session.new(params[:session])
if #session.save
flash[:notice] = "Logged in successfully."
redirect_to_target_or_default(root_url)
else
render :action => 'new'
end
end
I think you can get an idea of how to use it while reading this code. :)
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.
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 want to have a sign in page that redirects to user the last page he were before sign in. How could I do it in Rails. Do I need to override after_sign_in_path for each link of how?
Thanks :)
The short answer is yes, you will need to override the after_sign_in_path The simplest method I have found to do so is as follows:
Inside of your application controller you will need to add two methods,
include SessionsHelper
def after_sign_in_path_for(resource_or_scope)
case resource_or_scope
when :user, User
store_location = session[:return_to]
clear_stored_location
(store_location.nil?) ? requests_path : store_location.to_s
else
super
end
end
def check_login
if !anyone_signed_in?
deny_access
end
end
First we override the after_sign_in_path to save our new stored location which we pull from the Rails session into our store_location which we will define in our SessionsHelper. Next we create a method that we can use as a before_filter in any controller we want to use this on.
Next, set up sessions_helper.rb
module SessionsHelper
def deny_access
store_location
redirect_to new_user_session_path
end
def anyone_signed_in?
!current_user.nil?
end
private
def store_location
session[:return_to] = request.fullpath
end
def clear_stored_location
session[:return_to] = nil
end
end
Here we're just defining the methods we used inside of our application controller, which should all be pretty self explanatory. Just remember to use before_filter :check_login before any other before filters in the controllers you are wanting to remember the previous path for.
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