I'm using Devise to authenticate user via the standard implementation. Once a user logs in, he/she can call an REST API that uses the same controller (and thus also Devise). The controller looks like this:
class FriendController < ApplicationController
before_filter :authenticate_user!
def create
...
end
def destroy
...
end
def index
...
end
end
I got the index action to work with authenticated_user!. In this case, if a user is authenticated, index will return data with 200 Status Code. If a user isn't authenticated, index returns Unauthorized with 40x Status Code.
However, when I'm calling create or destroy via POST and DELETE, it automatically logs me out of the application and returns 40x Unauthorized error. Anyone seen this before? Any ideas?
Here's the route.rb
resources :users do
resources :friends, only: [:index, :friended_me, :create, :destroy]
end
For example, does this needs to be within devise_scope :users block?
I figured out why this is happening. In ApplicationController, if protect_from_forgery is enabled. The API calls for POST/PUT/DELETE will check for auth_token. If it's not provided, it'll fail. However, for GET, if it's not provided, the action will still proceed if user is logged in.
The fix is to use the auth_token generated by resource.reset_authentication_token! after overriding SessionsController#create
Related
I'm relatively new to ruby on rails, and so I am now very confused how to setup a user management system for admins.
Besides, users should be able to register themselfs (Devise Registerable).
I have a User controller, using devise_for :users and resources :users .
I can sign_up users, since I used the :registerable, flag in my Users model.
What I want to do now is to add the ability for admins to create users.
If I used the described system, I always get the message 'You are already signed in' when creating a new user through /users/new as admin. This is a message from devise.
So I followed the tutorial www.tonyamoyal.com/2010/07/28/rails-authentication-with-devise-and-cancan-customizing-devise-controllers/ to use cancan to restrict some actions and created a own devise registrations controller like described there.
My cancan ability model looks like this:
if user.has_role?(:admin)
#admin
can :manage, :all
elsif !(user.new_record?)
#logged in but no admin
...
else
# Guest
can :create, User
end
and my registrations controller like in the tutorial
class RegistrationsController < Devise::RegistrationsController
before_filter :check_permissions, :only => [:new, :create, :cancel]
skip_before_filter :require_no_authentication
def check_permissions
authorize! :create, resource
end
end
I also added the controller to the routes.rb
With this I can create new users with the admin, but if I want to sign_up as not logged in user ("#Guest") I get always the message cancan exception "Access denied". And if I call exception.subject in the CanCan exception handling it is empty.
Can it be, that 'resource' from my controller is not initialized? How can I get the expected behaviour?
Thanks a lot for your help ;-)
Mhm, I figured out, that resource seems to be a method of the devise-controller.
No idea, why it is not called, or is not returning an object
My solution was now (since I can only register users) to change
def check_permissions
authorize! :create, resource
end
to
def check_permissions
authorize! :create, User #could also be User.new
end
And with this it works. But I'm not sure, if it is the best solution ;-)
Gems
ruby 1.9.3
rails 3.2.11
devise 2.2.3
acts_as_tenant 0.2.9
Code
All my models are scoped by a domain_id:
class User < ActiveRecord::Base
acts_as_tenant(:domain)
#...
end
Then, in my application_controller, I set the current tenant from the domain:
class ApplicationController < ActionController::Base
set_current_tenant_through_filter
before_filter :set_tenant
protect_from_forgery
#...
def set_tenant
#...
#domain = Domain.find_or_create_by_name(request.host)
set_current_tenant(#domain)
end
end
All works well for all models except for sessions: Everytime a page is loaded, it will log-out the first user who load a page with another tenant. By loading this page, it will log-out the first user who [... etc.]
Hypothesis: when Alice visits a domain, Rails loads current_tenant=alice_domain (ok). All works as expected, until Bob visits another domain, load current_tenant=bob_domain. When Alice refreshes her page, Rails still has current_tenant==bob_domain. Rails checks session: Alice does not exist with bob_domain scope, so Devise forces Alice logout. Then application_controller sets current_tenant=alice_domain... which logs-out Bob.
Dirty workaround: do not use acts_as_tenant in user model, scope users by domain myself in every controllers, then overwrite devise to scope login and registration by domain. And I'm unsure how to get Devise aware of current domain in sessions stuff. By the way, replacing acts_as_tenant by a manual default_scope in user falls in the same strange bugs. It seems very dirty to go this way.
I'm looking for a clean solution for days. I would be very grateful for any help.
Fixed, in application_controller, change
before_filter :set_tenant
to
prepend_before_filter :set_tenant
in order to default_scope everything, including User, before Devise checks the user's session.
I'm trying to figure out before_filters and I was hoping for a little help.
I have a simple blog app, with entries that can be (draft or published) and (public or private). I was wondering how I can do my authentication?
I currently have:
before_filter :authenticate, :except => [ :show ]
So that blocks all the CRUD actions. For show, I need to check that:
If its a draft, that the logged in user owns the entry.
If its private, a user is logged in (in this, all logged in users can see private entries)
I can do it within the action itself, but it seems that the rails way of doing it, is in a before_filter.
Thanks!
Do it in the before filter. If you are using devise then the current_user method is available if a user is logged in. Otherwise replace current_user with the value returned from your authenticate method.
def find_post
#post = Post.find(params[:id])
redirect_to root_path if #post.draft && #post.user != current_user
redirect_to root_path if #post.private && !current_user
end
I am using "check authorization" in the application controller so every action will require a permission. I'm starting with giving me, the superadmin :=], permissions to manage all. I thought manage all would give me access to the whole app without naming a resource.
user model:
def role?(role)
roles.include? role.to_s
end
application controller:
check_authorization
cancan's ability model:
def initialize(user)
if user.role? :superadmin
can :manage, :all
end
end
error message:
This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check.
Thank you.
As far as I am aware, you're going to need to call authorize_resource in your controller as a before filter so that this works.
Using Rails 3.0.6, Omniauth 0.2.0 and Devise 1.2.1, I'm encountering the following situation:
I want to offer users the option to authenticate via Facebook. I have a user system set up using Devise and I can successfully auth using Facebook. I've spent several hours trying to code the behavior I want for one specific situation:
user is not logged in
user has a site account
user authenticates via Facebook
I offer the user 2 choices at this point
create an account (can be a dummy account with no provided info)
link this Facebook authentication with an existing account
I'm having trouble with the latter option. The user has already authenticated but I still need him to log in with his site account. I have an action in my AuthenticationsController that will associate this authentication with a logged in user. Devise doesn't seem to offer a way for me to log the user in while staying in the same action, though. This was my first attempt to do this
class AuthenticationsController < ApplicationController
before_filter :authenticate_user!, :only => :auth_link_existing_user
...
def auth_link_existing_user
...
end
However, using this method, if the user logs in, they're simply redirected to my site's root page. I know I can change Devise's sign-in redirect, but that will be for all sign-ins. I wanted only this situation to have a separate redirect.
After reading through this mailing list question, I tried to extend SessionsController with my own custom behavior:
def create
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
if params[:redirect] #new
redirect_to params[:redirect].to_sym #new
else
respond_with resource, :location => redirect_location(resource_name, resource)
end
end
This doesn't work either. I've defined my auth_link_existing_user route to use a POST verb (which seems accurate) and redirects can only be GETs.
So now I do have a solution in mind: copy and paste code from Devise's authenticate_user! helper into a new function which can be called within a controller action without redirecting. This seems less than ideal to me because it's duplication of code and increases coupling--a Devise or Warden update that changes this behavior will break my code as well.
Has anyone else tried something like this and come up with a more elegant solution? Do you see a simpler way for me to offer this or similar behavior to my users?
UPDATE: For anyone who wants to use my dirty solution at the end, this is what I did:
def auth_link_existing_user
# FROM Devise/sessions/create
resource = warden.authenticate!(:scope => :user, :recall => "registrations#auth_new")
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(:user, resource)
# method defined in Ryan Bates' Railscast for Omniauth w/Devise
current_user.apply_omniauth(session[:omniauth])
current_user.save
end
note that this action MUST be placed in your sessions controller. If not, Warden will give you an "invalid email/password" error. It was an incredibly long debugging process to find the source.
With this in place, I use a login form to submit to this action after the user has authenticated.
I like how clean your solution is, though it goes deeper into the stack.
Here is how I've implemented something similar by following the Devise+Omniauth Facebook example on the Devise wiki and modifying the facebook method to pass on the session information to the Login form, with something like this:
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_session_url
end
Then, in your case, I'd check in Login controller action for session["devise.facebook_data"], submit the uid + token with the form and apply_omniauth if present.