I need to set up a route that is only valid if the logged in user is admin (user.admin?) using devise. I'm doing this for sidekiq, but the question is valid for any similar use.
Here is my route code:
class AdminConstraint
def matches?(request)
!request.session['warden.user.user.key'].nil? and request.session['warden'].user.admin?
end
end
require 'sidekiq/web'
mount Sidekiq::Web => '/sidekiq', :constraints => AdminConstraint.new
I got this code from the sidekiq wiki: https://github.com/mperham/sidekiq/wiki/Monitoring
I tried the code they posted, which didn't work so I made some modifications as I just posted. The code above doesn't work as user evaluates to nil.
What is the correct secure way to deal with this?
thanks for any help!
If you are using devise to authenticate users, what you need to do is as follows:
Inside your routes.rb file, replace this line:
mount Sidekiq::Web => '/sidekiq', :constraints => AdminConstraint.new
with this block:
authenticate :user, lambda { |u| u.has_role? :admin } do
mount Sidekiq::Web => '/sidekiq'
end
What the block does, is telling to your devise mechanism to check first if the user is authenticated (the authenticate method is a devise method), and also that the user has the admin role (which the lambda block checks), before defining the route to the sidekiq UI.
This way, only users with an admin role will be able to access this route. Other users will get routing error in development and 404 error in production.
One more thing, the has_role? method is a rolify gem which in my case I use, but you can replace it with any other method/query that checks if the user is admin.
You're not really trying to change the route depending on who the user is (I'm not even sure if it's possible in rails without a lot of hacking), you're trying to authorize a user's action.
There are several good authorization libraries. Personally I like CanCan, but there are many others.
if you are doing any type of filtering in the controller for devise you can use the helpers
class ###Controller < ApplicationController
before_filter: :authenticate_admin!
def index
end
or whichever devise model you are trying to allow access for i.e
before_filter: :authenticate_user!
before_filter: :authenticate_author!
like bdares said. you can use the CanCan gem for authorization if the helper methods in devise isn't enough
hope it helps!
Related
My rails application is Devise gem for user login/authentication. I need to use a webservice call to retrieve data for manipulation. Can someone suggest how this can be done without having to use front end login?
I need some way of authenticating the web service call
SOLUTION->
I ended up using
https://github.com/gonzalo-bulnes/simple_token_authentication
Another option is
https://github.com/baschtl/devise-token_authenticatable
Before, devise use to include something called token authentication, but it was inheritely insecure and it got removed. The removed code and a better alternative was published in a gist by one of devise's team members and later written into a gem, devise-token_authenticatable, by someone else.
Simply put, you will store a authentication token in the users, and ask for both their email and token on each request to see if they match, but compare it securely. It's common not to store the user in session for this request.
You can create your own session create method and override the one that comes with devise. First inside your router you would customize devise
devise_for :users, controllers: {sessions: "sessions"}
Then you can implement this with your own controller and inherit from Devise::SessionsController
class SessionsController < Devise::SessionsController
def new
super
end
def create
self.resource = AuthService.perform(auth_options) # do the custom check here with your service call
sign_in(resource_name, resource) #this will set the devise session and login the user
respond_with resource, :location => after_sign_in_path_for(resource)
end
end
That should give you a way to have your own custom logic for creating session. Also have a look at github implementation:
https://github.com/plataformatec/devise/blob/master/app/controllers/devise/sessions_controller.rb
Hope it helps
I'm trying figure out a way to secure the login URL using Devise. I want to change the default route for users/sign_in. I don't want someone to stumble across the URL and gain access to the app.
Changing the following in the routes.rb file should give me the solution I'm looking for. However, I'm not sure if this is the best path to follow.
devise_scope :user do
get "/login" => "devise/sessions#new"
end
or
as :user do
get "/login" => "devise/sessions#new"
end
Using the following may be easier
devise_for :users, :path => '', :path_names => {:sign_in => 'login', :sign_out => 'logout'}
"But I haven't gotten it to work"
I'm using a simple install of devise without pundit or any other type of authorization. The other thought I had was to implement a role and add the current users to that role. Therefore, blocking access to the app by default. But that would require me to add every new legitimate user to that role as they sign up. Don't really want to do that.
I would question your logic here - using a different url is simply security by obscurity. Even if you had your users login with /gobeligook its pretty trivial for any dedicated attacker to figure that out by sniffing traffic for example.
However you might want to change the path for various reasons - but don't fool yourself that you are adding any real security benefits by doing so.
Also you need to separate the concerns of authentication - which what Devise does and authorization. Authentication is verifying that the users is who he/she claims to be. Authorization is who gets to do what.
If you want to lock down your site to users that are vetted that is a authorization concern and there are a few ways to solve it based on your requirements:
Disable signups
The most basic way to do this would be to disable signups and only allow users to be created by admins. Its relatively secure but really tedious for admins and pretty draconian. In this case your authentication would simply be to lock down everything save for the sign in unless the user is authenticated.
Thats where before_action :authenticate_user! comes in.
Invitation only
You can use something like the DeviseInvitable module to invite users by email and then override the sign up method to require an invitation token.
Walled garden approach
What you may want is users to be able to sign up - but they are really only allowed to access anything when they have been vetted by an admin or a peer.
This is a basic sketch of how something like this could be setup:
class Approval
belongs_to :user,
belongs_to :vetting_user, class_name: 'User'
end
class User
# ...
has_many :approvals, dependent: :destroy
has_many :granted_approvals,
class_name: 'Approval',
source: :vetting_user,
dependent: :destroy
def approved?
approvals.any?
end
end
class ApplicationController
before_action :authenticate_user!
before_action :authorize_user!, unless: :devise_controller?
def authorize_user!
redirect_to user_signup_path, unless current_user.approved?
end
end
For breivity this does not include stuff like the controller where peers or admins vet users - but thats the simple part.
Although I would seriously consider using a purpose built authentication library like Pundit instead of rolling your own.
Adding before_filter :authenticate_user! to controllers should force anyone to go through the login process if the session is not authenticated. That's the whole purpose of using Devise and/or any other gem of that type.
Its all up to you, the ways you listed are correct, please refer the devise-wiki : How-To:-Change-the-default-sign_in-and-sign_out-routes
And if you want user's authentication for most of your application then use
# application controller
before_action :authenticate_user!
# And then you can skip this before_action filter in your other controllers something like this
skip_before_action :require_login, only: [:new, :create]
And these are two things don't be confused in this.
authentication
Devise is a flexible authentication solution for Rails based on Warden
authorization
Pundit provides a set of helpers which guide you in leveraging regular
Ruby classes and object oriented design patterns to build a simple,
robust and scaleable authorization system.
Devise will help you in user authentication and if you want to authorization users based on some criteria then you should use Pundit
I'm upgrading my rails 2.3.8 app to Rails 3.
I'm using cancan for authorization which seems to be working for most of my actions apart from the root.
The way I have cancan is setup is that when a user is not authorized, he is redirected to the root url of the app (and displayed a short message).
You don't need to be logged in to view the root action yet cancan keeps trying to redirect to it - so it's stuck on a redirection loop.
This all worked in rails 2.3.8 so it makes me suspect one of two things:
The new version of cancan works differently
I've made a mistake in setting up routes
This is the root action / controller in routes.rb:
root :to => 'tours#browse_tours'
And this is what I've specified in cancan's ability.rb model (for non-logged in users):
can :browse_tours, Tour
Any thoughts?
I think in the upgrade from an earlier version of CanCan something changed.
I discovered the problem was actually in my controller. In my controller I had:
load_and_authorize_resource :user
load_and_authorize_resource :tour, :through => :user
I could use the above and 'browse_tours' was still accessible for guest users.
To work around this I told cancan not to bother performing authorization on the action:
load_and_authorize_resource :user
load_and_authorize_resource :tour, :through => :user, :except => :browse_tours
It does and doesn't make sense. In ability.rb we create an instance of User (without any roles) if the person has not logged in (just as in http://asciicasts.com/episodes/192-authorization-with-cancan).
E.G.
user ||= User.new
if user.has_role? :admin
#allow admin to do stuff
elsif user.has_role? :normal
#allow signed in user to do stuff
else
#guest user so allow to read
can [:read, :browse_tours], Tour
end
This 'role-less' user was then allowed to do certain things but for some reason this doesn't fly in the new version of CanCan.
Anyway, hope this explanation helps someone else out.
I am using devise as my authentication solution and now i am thinking about authorization. In my project I (the admin) is the only person authorized to create account for others.
I wonder if there is a way to do it without to much hack. In fact, Devise doesn't allow user to access to the signup page if he is already logged in.
Thanks for your advice on it!
Setting :skip => :registrations also kills the ability for a user to edit their user info. If that's not what you are after you can instead create a (minimal) custom registrations controller and only remove the new_user_registration_path while preserving the edit_user_registration_path.
# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
def new
# If you're not using CanCan, raise some other exception, or redirect as you please
raise CanCan::AccessDenied
end
end
# routes.rb
devise_for :users, :controllers => { :registrations => "registrations" }
Once you do this you also need to move the directory views/devise/registrations to just views/registrations.
You can try the rails_admin gem in conjunction with Devise to handle any admin-specific tasks. You'll need to add more code to set it up, but at least you avoid hacking around the solution in terms of changing your interactions with Devise.
It actually looks like in the later versions of Devise you can just remove the "registerable" declaration from your model and it will take care of this for you.
Trying to redirect users to their associated 'home' page after successful login w/out nil'ing out stored_location_for(resource_or_scope)...which is giving me some endless redirect loops (pretty sure I've set it up wrong).
Regardless, I'm looking for a better approach...
Devise's docs state: After
signing in a user, confirming the
account or updating the password,
Devise will look for a scoped root
path to redirect. Example: For a
:user resource, it will use
user_root_path if it exists,
otherwise default root_path will be
used. This means that you need to set
the root inside your routes: root :to => "home"
I'm sorta a newbie...how does one go about generating this home_root_path for each user?
rDocs also mention:
-- (Object) after_sign_in_path_for(resource_or_scope)
The default url to be used after
signing in. This is used by all Devise
controllers and you can overwrite it
in your ApplicationController to
provide a custom hook for a custom
resource.
By default, it first tries to find a resource_root_path, otherwise
it uses the root path. For a user
scope, you can define the default url
in the following way:
map.user_root '/users', :controller => 'users' # creates user_root_path
map.namespace :user do |user|
user.root :controller => 'users' # creates user_root_path
end
but these just gives me undefined local variable or methodmap' for #ActionDispatch::Routing::Mapper:…` errors.
If you would like to redirect using a route in answer to your question below:
how does one go about generating this home_root_path for each user?
This will work if you place it in your config/routes file. It will redirect a user to articles#index after (for example) a successful confirmation.
# Rails 4+
get 'user_root' => 'articles#index', as: :user_root
# Rails 3
match 'user_root' => 'articles#index', as: :user_root
See Devise: Controllers Filters and Helpers
You could try something like this:
application_controller.rb:
def after_sign_in_path_for(resource_or_scope)
# return home_page_path for user using current_user method
end
Dug around a bit to figure out the same thing. #polarblau's answer is correct,
def after_sign_in_path_for(resource_or_scope)
user_info_path(current_user)
end
where user_info_path is the path to the page you wish to display.
Also, I would allow this to fall back to super just in case, although I'm not entirely sure if that is necessary...
def after_sign_in_path_for(resource)
if resource.is_a(User)
user_info_path(resource)
else
super
end
end
I spent several hours trying to get the same functionality, and this is the code that ended up working for me:
def after_sign_in_path_for(resource)
current_user
end
If I ever tried current_user_path, I always got undefined local variable or method current_user_path errors.
Also, I'm using Rails 3.2.8 and Devise 2.1.2.
Hope that helps.
Based on #SnapShot answer, this worked for me. I'm using multiple devise models, trying to redirect back to the users profile edit page.
get 'user_root', to: redirect('/users/edit'), as: :user_root
ROR 7 answer
get '/users/home' => 'application#test', as: :user_root