How can you use a cookies based Sorcery authenticated session in Grape under Rails?
I am interested in utilizing commands like session and current_user from a grape controller.
Add the following helpers to your root API mount point:
class API < Grape::API
..
helpers APIHelpers
..
end
# add this to app/api/api_helpers.rb
module APIHelpers
include Sorcery::Controller
def session
env['rack.session']
end
def reset_session
if env['rack.session']&.respond_to?(:destroy)
session.destroy
else
env['rack.session'] = {}
end
end
def form_authenticity_token
session[:_csrf_token] ||= SecureRandom.base64(32)
end
def current_user
#current_user = login_from_session || nil unless defined?(#current_user)
#current_user
end
end
Including Sorcery::Controller gave me all the sorcery methods (login, current_user, etc), but there were a few missing session methods that needed to be added via the helpers to make sure sorcery was happy. Note, Grape does not provide the same CookieJar as rails, so you won't be able to utilize cookies.signed. This was not an issue for me, but it may be for you. I worked around the sorcery functions that would call a signed cookie.
Related
I'm working on a login/logout system. Instead of using devise, I created an active records User model and use sessions to remember if a user is logged in. Everything was working fine until I added these lines in the application_controller.rb to have a layout before login and one after.
layout :set_layout
def set_layout
if session[:current_user_id]
'afterlogin'
else
'application'
end
end
Now, after I log in and cancancan is being used somewhere in a html page I get undefined local variable or method 'current_user'. I think that I have to add a current_user method but I'm not exactly where and how to define it.
Edit: I already had something similar in another class that is being used by login:
class Admin::ApplicationController < ApplicationController
before_action :authorize
def authorize
begin
#current_user ||= User.find(session[:current_user_id]) if session[:current_user_id]
rescue ActiveRecord::RecordNotFound
session.destroy
redirect_to '/login',alert: 'Please login'
end
end
end
Should I modify this after I add that method ?
CanCanCan expects a current_user method to exist in the controller.
First, set up some authentication (such as Authlogic or Devise).
See Changing Defaults if you need different behavior.
I would suggest you to install Devise so that it comes with a complimentary current_user method.
FYI: https://github.com/plataformatec/devise
UPDATE
when a user logins successfully, you can store the user's id in session.
session[:current_user_id]=user.id
so that, in your applicationcontroller, you can do
def current_user
#current_user ||= session[:current_user_id] && User.find_by_id(session[:current_user_id])
end
helper_method :current_user
Spree 2.3 with spree_auth_devise, Rails 4.0
I am trying to redirect users after sign in based on their role. The best solution would be to modify a path, a la Devise, in an initializer, but these don't seem to exist for Spree. The next best solution would be to create a decorator on the sessions controller, but I can't find this controller, nor can I access it when I attempt to follow the information from rake routes
How can I redirect users to a new location after login, in a Spree app, based on their role?
UPDATE
Overwriting the after_sign_in_path_for(resource) method results in the method being triggered, but still rerouting to the admin_path.
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
byebug # this is triggered
root_path
end
end
### OR ####
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
byebug # this is triggered
if spree_current_user.has_spree_role?("admin")
admin_path
elsif spree_current_user.has_spree_role?("designer")
new_designers_spree_variant_path
else
root_path
end
end
end
My attempts are being documented here:
https://gist.github.com/asteel1981/0f258260974f4d748fb5
Thanks in advance.
Thanks to #mvidaurre for spending some time with me drilling down into this.
The issue is that spree_auth_devise has a second method that attempts to reroute to the last page attempted, meaning I needed to modify not only the after_sign_in_path_for method, but the Spree method redirect_back_or_default(default).
Additionally, because my user signs in through the admin/sign_in route, I needed to access the Spree::Admin::UserSessionsController instead of simply the Spree::UserSessionsController.
My early solution looks like this:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
if spree_current_user.has_spree_role?("admin")
admin_path
elsif spree_current_user.has_spree_role?("designer")
'/designers/spree_variants/new' #rails helper gives the wrong path, not sure why
else
root_path
end
end
end
# app/controllers/spree/admin/user_sessions_controller.rb
Spree::Admin::UserSessionsController.class_eval do
def redirect_back_or_default(default)
if spree_current_user && spree_current_user.has_spree_role?("admin")
redirect_to(session["spree_user_return_to"] || default)
session["spree_user_return_to"] = nil
else
redirect_to(default)
end
end
end
The relevant spree source code is here:
https://github.com/spree/spree_auth_devise/blob/8cb2d325b2c1da02cbe137404d8dda89cc1613a2/lib/controllers/backend/spree/admin/user_sessions_controller.rb
Thanks for your answer in the comments. spree_auth_devise defines the Spree::UserSessionsController you can decorate the controller and use the method described in: How To: redirect to a specific page on successful sign in
You can implement your custom method for:
def after_sign_in_path_for(resource)
current_user_path
end
Maybe something like this:
def after_sign_in_path_for(resource)
if spree_current_user.has_spree_role?("designer")
root_path
elsif spree_current_user.has_spree_role?("admin")
admin_path
else
root_path
end
end
Spree documentation seems to cover this, check out:
http://guides.spreecommerce.com/developer/authentication.html
It seems to be a good starting point for what you're trying to achieve.
I am using Rails 4.0.1 and ruby 2.1.1. I am trying to integrate cancan with rails_admin. My application_controller.rb looks like this
helper_method :current_user
private
def current_user
#current_user ||= User.find_by(email: session[:user_id]) if session[:user_id]
end
My current_user helper method is working fine from rest of the application. My ability.rb looks like this
class Ablity
include CanCan::Ability
def initialize(user)
if user
can :access, :rails_admin
end
end
end
Control is not going inside the if condition at all, which means the "user" parameter is "nil". When I try to access rails_admin I get a CanCan::AccessDenied exception. Where am I going wrong?
Edit : I am not using devise for authentication
Update : I've replaced cancan with cancancan version 1.8. Still not working
Solved. In config/initializers/rails_admin.rb we have to specify the current_user_method as
config.current_user_method { current_user } # refers to the current_user helper method in my case
Now everything is working perfectly. I am surprised why this isn't specified anywhere in the documentation.
I am using authenticate_or_request_with_http_digest for simple administration within my app. It would be nice if the admin could see all of the delete, edit links for an object when logged in but have these hidden for regular users.
The app has no scope for users signing up or multiple users so devise or a similar authentication platform seems overkill in this instance.
I have tried to use the authenticate method in the view, as you would with a current_user method. However, it infinitely prompts you to login, which isn't ideal.
Is there a way to replicate the popular current_user method to check whether a session has been created and use this as a helper method?
application_controller.rb
helper_method :authenticate
USERS = { "username" => "password",
"APP" => Digest::MD5.hexdigest(["APP", "realm", "password"].join(":"))}
private
def authenticate
authenticate_or_request_with_http_digest(CONFIG[:realm]) do |username|
USERS[username]
end
end
usage in controller
before_action :authenticate
Update
Thanks to Peter Goldstein's answer, I was able to save the username inside the authenticate block into a session[:admin] variable and use this inside the current_user helper method.
Something like this:
def authenticate
current_user_name = nil
is_authenticated = authenticate_or_request_with_http_digest(CONFIG[:realm]) do |username|
current_user_name = username
USERS[username]
end
#current_user = current_user_name if is_authenticated
is_authenticated
end
def current_user
#current_user
end
helper_method :current_user
should capture the username from the HTTP digest request and make it visible in the current_user method
Since Doorkeeper is an isolated Engine we have no access to whatever you did in ApplicationController. But what if you need a current_user? What could be a workaround here?
The first idea is to monkey-patch ActionController::Base. Any better thoughts?
My doorkeeper implementation was inside of the base app so this wont help if you are using a separate engine but will if you use the same rails app so I will share here:
class ApplicationController < ActionController::Base
protect_from_forgery
private
def current_user
if doorkeeper_token
return current_resource_owner
end
# fallback to auth with warden if no doorkeeper token
warden.authenticate(:scope => :user)
end
# Needed for doorkeeper find the user that owns the access token
def current_resource_owner
User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
end
end
Unless there are no answers, may be my own dirty monkey patch will be useful to someone.
in initializers/action_controller_patch.rb:
module ActionController
Base.class_eval do
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
end
end
I think you can find it on this page.
https://github.com/applicake/doorkeeper/wiki/Using-Resource-Owner-Password-Credentials-flow
I am using credential auth pattern, so in my case this works.
Doorkeeper.configure do
resource_owner_from_credentials do |routes|
request.params[:user] = {:email => request.params[:username], :password => request.params[:password]}
request.env["devise.allow_params_authentication"] = true
request.env["warden"].authenticate!(:scope => :user)
end
end