I am using devise with multiple authentication strategies (SAML using the devise_saml_authenticatable gem):
devise :database_authenticatable, :saml_authenticatable, :timeoutable
A user can log in with a standard devise login page (database authentication) or with a SAML login. Once logged in, there is no difference in the model used for the user (both login methods give the same model: User).
Whenever the user is timed out, I want to redirect them to the login page they used to sign in with. Figuring out which login method was used is not a problem, but I have not found any way to access this information upon timeout.
The actual redirect happens in the Devise FailureApp. I have set a custom failure_app for the custom redirect:
class CustomFailureApp < Devise::FailureApp
def redirect_url
if warden_message == :timeout
# Find some way to obtain the login method
redirect_to ...
end
end
end
I need to now obtain the used login method in this method.
I tried using:
Warden::Manager.before_logout do |user, auth, opts|
opts[:login_method] = user.login_method
end
Here I still have access to the user object and can retrieve the login method, but anything I set in the options is not actually propagated to the CustomFailureApp.
I then tried:
Warden::Manager.before_failure do |env, opts|
opts[:login_method] = ?
end
Any options I set here are propagated to the CustomFailureApp, but I don't have access to the user and cannot determine their login method here.
I tried finding the user object in the CustomFailureApp from the warden session:
def redirect_url
user = warden.session(:user) # Crashes, session invalid
...
end
However, the this fails because the session has already been destroyed. Furthermore, looking through the request object I don't see any clear user-identifying information.
I know it is possible to redirect a user to a different login page based on scope, as stated in How to define custom Failure for devise in case of two different models User and active admin?. However, my users are not distinguishable by scope.
7 years ago someone posted a way which does identify the user in the Warden::Manager.before_failure
method, which I would be able to use: https://stackoverflow.com/a/33230548. However, this solution no longer seems to work/does not seem to work for me. I am unable to find any user-related information in the request parameters (or anywhere in the request), besides the (encrypted) warden session which warden refuses to still use as it is timed out.
Is there any way with which I can identify the user in CustomFailureApp or pass information to the CustomFailureApp from a point where I can?
Is there a way to make the scopes differ for users logged in with these different methods?
Otherwise, can I store the login method used in some different way where I can access it in the CustomFailureApp?
If not, is there any other way to have this "redirect to the correct login page" based on which login method the user used before?
I was having so much trouble with this. I just found your post, and I tried many of the same things you did but your question finally got me to a solution. Big thanks for your detailed question. Hope this helps you or others looking for an answer.
I wasn't ultimately able to base the logic off of the provider but instead off of an attribute on the user model. So, in place of some_stuff
below you could use a company_id
or something.
In your config/initializers/devise.rb
you want something like this:
Warden::Manager.before_failure do |env, opts|
env['warden.options']['some_stuff'] = @some_stuff
end
Warden::Manager.after_set_user do |record, warden, options|
@some_stuff = record.some_stuff
end
And then in your CustomFailureApp (I have it in lib/custom_failure_app.rb
):
class CustomFailureApp < Devise::FailureApp
def redirect_url
if request.env['warden.options']['some_stuff'] == 'The Good Stuff'
"https://your.saml.redirect/path/here"
else
super
end
end
end
This does feel hacky so if someone thinks it smells and has some suggestion please feel free. I would love to know a better way to achieve this behavior.