I have a devise enabled route as:
#config/routes.rb
authenticated :user {
root :to => 'home#signed_in'
}
root :to => 'home#index
and controller:
#app/controllers/home_controller.rb
class HomeController < ApplicationController
def signed_in
Rails.application.routes.recognize_path '/'
end
end
which raises:
NoMethodError: undefined method `authenticate?' for nil:NilClass
...lib/devise/rails/routes.rb:286:in `block in authenticated'
I need such thing to render different templates in destroy action based on request.referer controller name. How can get 'authenticated' controller/action name for such URL?
It seems using Rails.application.routes.recognize_path does not work well with custom routes constraints like devise #authenticated method.
See https://github.com/plataformatec/devise/issues/3747
recognize_path won't work as is with constraints that require request related objects (like the warden instance that Devise expects to be in the request env Hash). If you are doing this inside a controller code you might be able to pass the warden object from request.env to the recognize_path call, or try to pass down the object down the the class that is recognizing the path.
In the other hand, as recognize_path isn't a documented public API, I strongly suggest you to not use it and save pieces of the params Hash instead of the raw URL on your application.
One solution to your problem could be to save the controller/action names in session and access them when you need.
authenticated :user do
root :to => 'home#signed_in'
end
unauthenticated :user do
root :to => 'home#index'
end
You can use this: https://gist.github.com/NullVoxPopuli/8c8af217b7404336c72a
class RouteRecognizer
include Singleton
ROUTE_LIST = Rails.application.routes.routes.collect{|r| r.path.spec.to_s}
REGEX_ROUT_LIST = ROUTE_LIST.map{|r|
Regexp.new(r.gsub(/\:(.*)id/, "(\d+)").gsub("(.:format)", ""))
}
def self.is_route?(path)
REGEX_ROUT_LIST.each do |regex|
return true if !!(path =~ regex)
end
false
end
end
and then call with
RouteRecognizer.is_route?('http://whatever')
Related
Here is the situation. I have a multi-tenant rails app using the apartment gem where I need to implement a LinkedIn OmniAuth Strategy.
As you can see by my routes, Devise users, and the associated routes, are only persisted on the individual schemas of the subdomains.
Example Route:
Good: https://frank.example.io/users/sign_in
Bad: https://example.io/users/sign_in
Routes
class SubdomainPresent
def self.matches?(request)
request.subdomain.present?
end
end
class SubdomainBlank
def self.matches?(request)
request.subdomain.blank?
end
end
Rails.application.routes.draw do
constraints(SubdomainPresent) do
...
devise_for :users, controllers: {
omniauth_callbacks: 'omniauth_callbacks'
}
devise_scope :user do
get '/users/:id', to: 'users/registrations#show', as: "show_user"
end
...
end
end
My specific problem is that LinkedIn does not support wildcards with their callback URLs so I am lost on how I might be able to direct users to the right domain after OAuth authentication.
So it turns out the answer was to pass parameters in the authorization link which would eventually get passed to the callback action throught request.env["omniauth.params"]
Authorization Link format:
Here I was having trouble adding the parameters to the Devise URL builder so I just manually added the parameters. This can probably be moved to a url helper
<%= link_to "Connect your Linkedin", "#{omniauth_authorize_path(:user, :linkedin)}?subdomain=#{request.subdomain}" %>
Routes:
Then I defined a route constrained by a blank subdomain pointing to the callback action.
class SubdomainPresent
def self.matches?(request)
request.subdomain.present?
end
end
class SubdomainBlank
def self.matches?(request)
request.subdomain.blank?
end
end
Rails.application.routes.draw do
constraints(SubdomainPresent) do
...
devise_for :users, controllers: {
omniauth_callbacks: 'omniauth_callbacks'
}
resources :users
...
end
constraints(SubdomainBlank) do
root 'welcome#index'
...
devise_scope :user do
get 'linkedin/auth/callback', to: 'omniauth_callbacks#linkedin'
end
...
end
end
Controller:
I used this tutorial to set up my callback controllers: Rails 4 OmniAuth using Devise with Twitter, Facebook and Linkedin. My main objective with the callback controller was to have it reside in in the blank subdomain so I only had to give one call back URL to my LinkedIn Dev App. With this controller I search the omniauth params for the subdomain parameter and use that to switch to the proper schema.
def self.provides_callback_for(provider)
class_eval %Q{
def #{provider}
raise ArgumentError, "you need a subdomain parameter with this route" if request.env["omniauth.params"].empty?
subdomain = request.env["omniauth.params"]["subdomain"]
Apartment::Tenant.switch!(subdomain)
...
end
}
end
could you register each domain as a callback with linked (I guess if you have a lot that becomes unmanageable quickly).. You could cookie the user before sending them to linkedin so when they return you know which subdomain they belong to.
I am using Rails 4 and Devise 3.
I am using the following in the routes file to prevent access to a page from non authenticated users (not signed in):
authenticate :user do
#page to protect
end
This redirects me to the user/sign_in page, but I want the user to be redirected to the root. So, I added the following as well to the routes page:
get 'user/sign_in' => redirect('/')
But this will mess up what I did in the sessions_controllers:
def new
return render :json => {:success => false, :type => "signinn", :errors => ["You have to confirm your email address before continuing."]}
end
This will stop showing. So, I would like another solution that redirects users to the root directly, instead of having to use authenticate :user and then get 'user/sign_in' => redirect('/').
The following may not have anything to do with redirecting the user to the root, but I would like to explain more about why I am overwriting the new method in the sessions_controller. I moved the sign_in and sign_up views to the root page (home page). In this case, I also needed to hack the error messages so that they appear in the home page, instead of redirecting the user to user/sign_in to show the errors. I used ajax for that.
Update
What I am looking for is something like this:
if user_authenticated?
#show the protected page
else
# redirect the user to the ROOT
end
You can set your devise scope to look something like this.
devise_scope :user do
authenticated :user do
root :to => 'pages#dashboard', as: :authenticated_root
end
unauthenticated :user do
root :to => 'session#new', as: :unauthenticated_root
end
end
EDIT (Based on the new info):
If you have something like this in your routes.rb
root to: 'visitors#index`
then you can have something like this in your visitors_controller.rb
class VisitorsController < ApplicationController
def index
if current_user
redirect_to some_authenticated_path
else
# business logic here
end
end
end
You will still want to handle the propper authorization requirements and authentication requirements in the some_authenticated_path controller and action.
I'm sorry I didn't understand your question, anyway you can do something like this:
class CustomFailure < Devise::FailureApp
def route(scope)
#return super unless [:worker, :employer, :user].include?(scope) #make it specific to a scope
new_user_session_url(:subdomain => 'secure')
end
# You need to override respond to eliminate recall
def respond
if http_auth?
http_auth
else
redirect
end
end
end
And in config/initializers/devise.rb:
config.warden do |manager|
manager.failure_app = CustomFailure
end
This was taken from the wiki of devise:
https://github.com/plataformatec/devise/wiki/How-To%3a-Redirect-to-a-specific-page-when-the-user-can-not-be-authenticated
I'm using Devise with Ruby on Rails.
What is the recommended way to redirect unauthenticated users to the sessions#new page if they attempt to access a page that requires authentication?
Right now I get an error that says no route matches the one they attempt to access (leading to a 404 error in production).
Just simple add this method to application_controller.rb
protected
def authenticate_user!
if user_signed_in?
super
else
redirect_to login_path, :notice => 'if you want to add a notice'
## if you want render 404 page
## render :file => File.join(Rails.root, 'public/404'), :formats => [:html], :status => 404, :layout => false
end
end
And you can call this method on before_filter another controllers you want.
e.g :
class HomesController < ApplicationController
before_filter :authenticate_user!
## if you want spesific action for require authentication
## before_filter :authenticate_user!, :only => [:action1, :action2]
end
Don't forget add login_path into routes.rb
devise_scope :user do
match '/sign-in' => "devise/sessions#new", :as => :login
end
note : I always use this way when play with devise for my apps authentication.. (rails 3.2 and rails 4.0.1)
You can do just like GeekTol wrote, or just put
before_action :authenticate_user!
in your controller.
In this case, devise uses the default authenticate_user! method, that will redirect to the "user_session_path" and use the default flash message.
It's not necessary to rewrite authenticate_user! method, unless you want to customize it.
I thought you could just add:
before_action :authenticate_user!
to each controller that required the user to be logged in.
I'm a Rails beginner but I found this in my own searches and it works well in my application.
You should refer to Devise's own How To: How To: Redirect to a specific page when the user can not be authenticated.
Another alternative I can think of is creating a routing Constraint wrapping your protected routes. You'd better stick to Devise's way, but here is an example:
#On your routes.rb
constraints(Constraints::LoginRequired) do
get '/example'
end
#Somewhere like lib/constraints/login_required.rb
module Constraints
class LoginRequired
def self.matches?(request)
#some devise code that checks if the user is logged in
end
end
end
Add this code in your config/routes.rb devise_for :users and resources :users and you can generate devise in views.
Does devise have a method built in that you can pass a variable which contains a URL to redirect the user to after they sign in or sign up?
Thanks
If your model is called User, then define a user_root route in your config/routes.rb:
match '/profile', :to => "user#profile", :as => "user_root"
Devise will then automatically redirect the user to this path.
There's a page in the Wiki that explains this: https://github.com/plataformatec/devise/wiki/How-To:-Redirect-to-a-specific-page-on-successful-sign-in-out.
application_controller.rb
...
def after_sign_in_path_for(resource_or_scope)
example_path
end
def after_sign_up_path_for(resource_or_scope)
example_path
end
...
I'd like to be able to set the root url in routes.rb based on the logged in/out state of Authlogic. Here is an example of what I need to do:
root :to => 'users#index', :constraints => lambda { |request| # ??? }
root :to => 'welcome#index'
get '/' => 'users#index', :as => 'user_root'
This would enable logged in users to go to users#index and logged out users to go to welcome#index without doing a redirect. I do know how to set a before_filter and redirect accordingly, but I'd rather not do that.
I know this is possible with Devise but I need to solve this for Authlogic. Similar question here without an answer.
You can create a custom route constraint pretty easily. Any object that responds to matches? can be handed to the :constraints route option. To check for Authlogic authentication, you would need to pull your Authlogic methods from your ApplicationController and put them somewhere else. I recommend using a concern for these anyways.
# app/concerns/authlogic_methods.rb
module AuthlogicHelpers
extend ActiveSupport::Concern
module InstanceMethods
private # All methods are private
def current_user_session
return #current_user_session if defined?(#current_user_session)
#current_user_session = UserSession.find
end
def current_user
return #current_user if defined?(#current_user)
#current_user = current_user_session && current_user_session.user
end
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include AuthlogicHelpers
end
# config/initializers/route_constraints.rb
module RouteConstraints
class LoggedIn
include AuthlogicHelpers
def matches?(request)
current_user
end
end
end
# config/routes.rb
constraints RouteConstraints::LoggedIn.new do
root :to => 'dashboard#show'
end
Note that we're instantiating a new RouteConstraints::LoggedIn object. This is because the Authlogic methods current_user and current_user_session are defined as instance methods and create instance variables — if they aren't separated for each request, you won't be sure a user is actually logged in or another one was logged in before.
You might run into problems if Authlogic tries to access params directly. In this case, you will probably need to override the UserSession.new method to accept the request object as an argument and call request.params to get the params.
Authlogic already have the machinery native (tested against Rails 6.0 and Authlogic 6.0):
class AuthlogicSignedInConstraint
def matches?(request)
Authlogic::Session::Base.controller = Authlogic::ControllerAdapters::AbstractAdapter.new(request)
user_session = UserSession.find
user_session && user_session.user
end
end
Authlogic in recent versions require a controller object to be instantiated otherwise it can't find the session data necessary for loading a session.
In fact, contained under authlogic/controller_adapters/ are adapters for various controller implementations: Rails Controllers, Sinatra and Rack.
The interface is fairly simple, so I wrote an adapter to work with the ActionDispatch::Request that one has access to within a constraint.
This has been tested on Rails 4.2, Rails 5 should be similar.
class AuthlogicRequestAdapter
attr_reader :request
def initialize(request)
#request = request
end
def authenticate_with_http_basic
end
def cookies
request.cookies
end
def params
request.params
end
def session
_, session_hash = Rails.application.config.session_store.new(Rails.application, Rails.application.config.session_options).load_session(request.env)
session_hash
end
end
Example of use in Rails
Route Constraint
class UserRouteConstraint
def matches?(request)
Authlogic::Session::Base.controller = AuthlogicRequestAdapter.new(request)
user = UserSession.find
if user&.record.present?
true
else
false
end
end
end
routes.rb
MyRailsApp::Application.routes.draw do
# ... other routes
constraints(UserRouteContraint.new) do
root :to => 'users#index'
end
root :to => 'welcome#index'
get '/' => 'users#index', :as => 'user_root'
end