I have a users_controller.rb. There are too many method including login, register, forgot_password and logout I want to put auth allow these actions in my ruby controller.
I have done $this->Auth->allow in the CakePHP.
$this->Auth->allow('register', 'login', 'forgot_password', 'logout');
But in the ruby this is very hard to put. Please suggest me -
def login
#title = 'Login'
#render layout: 'login'
end
def dashboard
if logged_in?
#title = 'My Dashboard'
#user = User.get_profile(session[:user_id])
#user = User.get_profile(session[:user_id])
#raise #myProfile.inspect
else
redirect_to '/login'
end
end
def my_profile
if logged_in?
#title = 'My Profile'
#user = User.get_profile(session[:user_id])
else
redirect_to '/login'
end
end
def logout
log_out
redirect_to '/login'
end
Each time I am adding if logged_in? ... else ... end in my every action. So
I want to put Auth Allow in ruby like CakePHP code. Please help me.
Those actions should be in separate controllers, there are plenty of resources available to explain this, search for "RESTful Rails".
Once they are in separate controllers you can use a "before" action to prevent unauthorised users from accessing those actions.
It looks like you've created your own authentication system, instead of using a gem, so if you want a method to check for a logged in user, you can add it.
In application_controller.rb
def authenticate_user
redirect_to login_path unless logged_in?
end
Then in any controller you want to require a user to be signed in you can do
class YourController < ApplicationContoller
before_action :authetnicate_user, except: [:actions_that_doesnt_need_auth]
...
# All normal methods
end
That being said - the previous answer about using RESTful resources is important to understand and keep in mind. If you have questions you can ask :)
Related
I've created a login controller so that people can log in using Facebook. When a user logs in he gets routed to frontpage/show and when they logout they get redirected to the root_url and it shows the login page.
The problem is that when a user logs in through Facebook, closes the site and then revisits the page he also gets directed to the root_url and not frontpage/show.
I've used Omniauth with Rails for this.
This is my session_controller.rb
class SessionsController < ApplicationController
def create
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to '/frontpage/show'
end
def destroy
session[:user_id] = nil
redirect_to root_url
end
end
and my frontpage_controller.rb
class FrontpageController < ApplicationController
def show
end
end
I've tried adding
def show
if authenticate_user?
redirect_to :controller=>'dashboard', :action => 'index'
else
redirect_to '/public/example_html_file.html'
end
end
To the frontpage_controller but it gives the error ndefined methodauthenticate_user` which makes sence. But I believe something like this is the answer.
Well, i'm assuming that you're using Devise. So, you have the method authenticate_user! and not authenticate_user?.
Now, to check if the user is logged, you can use the user_signed_in? instead of authenticate_user? and your code should works.
More information about those helper methods here https://github.com/plataformatec/devise#controller-filters-and-helpers
I have a controller PostsController, which allows user to create posts before logging in. But to save it, user has to log in using Omniauth.
In PostsController, I have:
class PostsController < ApplicationController
before_filter :authenticate_user_by_service, :only => :create
def create
...
end
private
def authenticate_user_by_service
redirect_to user_omniauth_authorize_path(:facebook)
end
Now, I have another controller to handle callback from facebook, which is called ServicesController
class ServicesController < ApplicationController
def create
auth = request.env["omniauth.auth"]
... authentication logic here ...
sign_in(:user, service.user)
end
method_alias: :facebook, :create
Normally, for authentication, after sign in, I'll redirect to :back.
However, services#create here is used as a before_filter. In this case, what should I do to get it back to my Posts#create ?
Update: I got this warning saying the filter chain is interrupted right at the moment I refer to a different method
Started POST "/posts" for 127.0.0.1 at 2013-02-26 23:47:41 -0500
Processing by PostsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"H0as8=", "post"=>{"post"=>"bla bla"}, "commit"=>"Create Post"}
Redirected to http://localhost:3000/users/auth/facebook
Filter chain halted as :authenticate_user_by_service rendered or redirected
You're approaching this wrongly. You're handling login and the check wether someone is logged in in the same step.
Consider using a sessions_controller to handle all your signup/login/logout logic, for example:
class SessionsController < ApplicationController
def new # this will be /login
session[:return_to] = params[:returnto] unless params[:returnto].nil?
redirect_to "/auth/facebook"
end
def create # this will be the callback after the user is authenticated
auth_token = request.env["omniauth.auth"]["credentials"]["token"]
# you'll need to write this based on your app's requirement.
# Find a user or create one if he doesn't exist yet.
user = User.find_or_create_authenticated_user(auth_token)
if user.present?
session[:user_id] = user.id # this stores the current user's id in your session and lets Rails remember him for you.
redirect_to return_or(products_url) # see below for the usage of return_or
return
end
redirect_to root_url, alert: 'User not found or invalid'
end
def destroy # /logout
session[:user_id] = nil
redirect_to root_url
end
end
#routes.rb
match '/logout' => 'sessions#destroy', :as => :logout
match '/login' => 'sessions#new', :as => :login
match '/auth/facebook/callback' => 'sessions#create'
Then, in your ApplicationController you set up a couple of helper methods:
class ApplicationController < ActionController::Base
protected
# Use this in your views and controllers to get
# a handle of the user that is currently logged in.
# it will return nil if there is no user logged in.
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
# Use this to check wether a user is logged in.
# returns true if there is a user, false otherwise.
def logged_in?
!current_user.nil?
end
helper_method :logged_in?
# Block access to controllers or actions based on
# wether there's a user or not.
def require_login
unless logged_in?
# if we need the user to log in, we send him on his way
# but send him back to the current page afterwards.
session[:return_to] = request.fullpath
redirect_to root_url(subdomain: false), :alert => "Please login"
end
end
# When a user is not logged in, but you send him to log in,
# you can force him to return to the url he was in or if nothing
# was set go to a standard path.
# See this being set up in SessionsController#new and in require_login and then
# being used in SessionsController#create
def return_or(path)
session.delete(:return_to) || path
end
helper_method :return_or
end
These helper methods are available in all your controllers since they all inherit from ApplicationController. You could then tell your PostsController to send users who are not logged in to go and log in and after doing that they get returned to the PostsController.
Then to address your requirement of saving a post only after authentication: Either you do create the post, save it, but only update it to being public after the user is authenticated, or you store the contents of the post in the session and restore them after the user is authenticated:
class PostsController < ApplicationController
def new
#post = Post.new(session[:post_params] || {})
end
def create
if logged_in?
#post = Post.create(params[:post])
# ... etc
else
session[:post_params] = params[:post]
session[:return_to] = new_post_path
end
end
end
Note that this is a rather vulnerable approach. I would rather suggest to actually create the Post, mark it as not yet public and store only the post's id in the Session. After authentication you could then find that post_id, recreate the object from, set its status to public and associate it with the current_user.
Using Devise, I know how to protect controller actions from non-signed-in users through:
before_filter :authenticate_user!
In order to illustrate what I am trying to achieve, please see an example:
I have the following controller: (a project belongs to a user)
projects_controller.rb
def create
#project = current_user.projects.new(params[:project])
if #project.save
redirect_to #project
else
render :action => 'new'
end
end
What I am looking for is a way that users can interact more with the website before having to sign up/sign in. Something like:
after_validation :authenticate_user!
if the user is not signed in, and redirect him after success (sign up/sign in) to the "project" show page.
Things I thought:
1.) Change the controller in order to accept a project object without user_id, ask for authentication if the user is not signed in, then update attributes with the user_id
I try to do it like this first and it results to a very ugly code. (Moreover authenticate_user! doesn't redirect to the #project which lead to more customization)
2.) Create a wizard with nested_attributes (project form and nested new registration form and session form)
3.) Something better? (a custom method?)
It seems authologic manages this more easily. I'm not sure it is a reason to switch so I would like to have your idea/answer on this. Thanks!
EDIT
references: Peter Ehrlich answer comment
CONTROLLER WITH VALIDATIONS LOGIC
projects_controller.rb
def create
unless current_user
#project = Project.new(params[:project]) # create a project variable used only for testing validation (this variable will change in resume project method just before being saved)
if #project.valid? # test if validations pass
session['new_project'] = params[:project]
redirect_to '/users/sign_up'
else
render :action => 'new'
end
else
#project = current_user.projects.new(params[:project])
if #project.save
redirect_to #project
else
render :action => 'new'
end
end
end
def resume_project
#project = current_user.projects.new(session.delete('new_project')) # changes the #project variable
#project.save
redirect_to #project
end
routes
get "/resume_project", :controller => 'projects', :action => 'resume_project'
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
def after_sign_in_path_for(resource)
return '/resume_project' if session['new_project'].present?
super
end
Something like this should work:
def create
unless current_user
session['new_project'] = params[:project]
redirect_to '/register'
return
end
# and on to normal stuff
# in your devise controller
def after_sign_in_path
return '/resume_project' if session['new_project'].present?
super
end
# back in projects_controller now
def resume_project
#project.create(session.delete('new_project'))
# you know the drill from here
# I'd also put in a check to make an error if the session is not set- in case they reload or some such
Keep in mind that session is a cookie in the browser, and thus has a size limit (4kb). If you're posting images or other media, you'll have to store them temporarily server-side.
Another option would be to create a userless project, and use a similar technique to allow them to claim it as their own. This would be nice if you wanted unclaimed projects displayed to all to be available as a flow.
I haven't tested it out, but it should be possible to store the action the user was going to, I.e. create, with the params hash that was submitted and redirect to it upon successful login. It would then handle the error cases as normal.
Have you tried that?
Hey guys I created some custom authentication thanks to railscasts.com but I'm somewhat stuck as I need to restrict my users from editing other users' profiles.
Here's my authenticate_user and current_user methods:
private
def current_user
#current_user ||= User.find_by_auth_token!(cookies[:auth_token]) if cookies[:auth_token]
end
def authenticate_user!
if current_user.nil?
redirect_to login_url, :alert => "You must first log in to access this page"
end
end
Here's the before_filter in my UsersController:
before_filter :authenticate_user!, :only => [:edit, :update, :destroy]`
EDIT: Fixed it thanks to alock27.
I had to edit my users_controller and modify the edit action as follows:
#user = User.find(params[:id]
redirect_to root_url unless current_user == #user
I think you want this:
Adding security on routes in Rails
you need to find the User by :id and check if current_user = #user
You don't have to provide an id for edit, update and destroy: you already have current_user.
Instead of editing #user = User.find(id), edit current_user. Thus, your authentication functions ensure the user will only edit its own profile.
I know I should put the code in the create action of the users controller, but I'm not sure what code I should put. I also assume it should call the create action in my sessions controller, but again I'm not sure how...
By the way I tried render :template => 'sessions/create' in the create action of the users controller, but I get this error when signing up:
Template is missing
Missing template sessions/create with {:locale=>[:en, :en], :formats=>[:html], :handlers=>[:rjs, :rhtml, :erb, :rxml, :builder]} in view paths "/rubyprograms/dreamstill/app/views", "/rubyprograms/dreamstill/vendor/plugins/facebox_render/app/views"
This is all in my application controller:
protected
# Returns the currently logged in user or nil if there isn't one
def current_user
return unless session[:user_id]
#current_user ||= User.find_by_id(session[:user_id])
end
# Make current_user available in templates as a helper
helper_method :current_user
# Filter method to enforce a login requirement
# Apply as a before_filter on any controller you want to protect
def authenticate
logged_in? ? true : access_denied
end
# Predicate method to test for a logged in user
def logged_in?
current_user.is_a? User
end
# Make logged_in? available in templates as a helper
helper_method :logged_in?
def access_denied
respond_to do |format|
format.html do
flash[:alert] = "You must log in to peform this action."
redirect_to root_path
end
format.js do
render_to_facebox(:partial => 'sessions/login_box')
end
end
false
end
Somewhere in your controllers you have something that looks like this:
user = User.new
# set attributes
user.save
render :template => 'sessions/create' # Probably based on your question
All you need to do is update the session to:
user = User.new
# set attributes
if(user.save)
session[:user_id] = user.id
# Send them somewhere useful
else
# Handle the error
end
They're signed in once session[:user_id] is set.
Technically?
In your controller, after you create your user, this code:
#current_user = user
should get you going (looks like you're using restful_authentication).
Now, whether it's a good idea to log in a user automatically without verifying their email address / whatever else is up for debate.
You seem that you just begin with Rails right ? I would highly recommend that you use a gem like Devise to handle your user registrations.
However, if you insist on doing it manually, you would just need to create a session variable that verifies whether a user is logged in or not. Then, you can add a helper like current_user, to get the user if user session shows he/she is logged in.
I see that you have a sessions controller there. Are you trying to use restful_authentication ? If so, once more i highly recommend switching to Devise :)
OLD CODE USING RESTFUL AUTHENTICATION - SESSIONS CONTROLLER
# This controller handles the login/logout function of the site.
class SessionsController < ApplicationController
# Be sure to include AuthenticationSystem in Application Controller instead
include AuthenticatedSystem
# render new.erb.html
def new
end
def create
logout_keeping_session!
user = User.authenticate(params[:login], params[:password])
if user
# Protects against session fixation attacks, causes request forgery
# protection if user resubmits an earlier form using back
# button. Uncomment if you understand the tradeoffs.
# reset_session
self.current_user = user
new_cookie_flag = (params[:remember_me] == "1")
handle_remember_cookie! new_cookie_flag
flash[:notice] = "Logged in successfully"
redirect_to :controller=>'Town'
else
note_failed_signin
#login = params[:login]
#remember_me = params[:remember_me]
render :action => 'new'
end
end
def destroy
logout_killing_session!
flash[:notice] = "You have been logged out."
redirect_back_or_default('/')
end
protected
# Track failed login attempts
def note_failed_signin
flash[:error] = "Couldn't log you in as '#{params[:login]}'"
logger.warn "Failed login for '#{params[:login]}' from #{request.remote_ip} at #{Time.now.utc}"
end
end