How to set cookies when log in/out with Devise - ruby-on-rails

I am using Devise for rails 3 application. For page caching, I need to set cookies for log in/out info.
What's the simplest way to set cookies when log in/out occurrs with Devise? I read 'how to customize controller' part but it seems to be a lot of work.

Since Devise is based on Warden, another solution is to use Warden's callbacks, e.g in your devise.rb:
Warden::Manager.after_set_user do |user,auth,opts|
auth.cookies[:signed_in] = 1
end
Warden::Manager.before_logout do |user,auth,opts|
auth.cookies.delete :signed_in
end

It actually wouldn't be too hard to extend the devise SessionsController to add cookies on log in and log out, you could create a controller similar to this:
# app/controllers/sessions_controller.rb
class SessionsController < Devise::SessionsController
# POST /resource/sign_in
def create
cookies[:sign_in] = "Sign in info you want to store"
super
end
# GET /resource/sign_out
def destroy
cookies[:sign_out] = "Sign out info you want to store"
super
end
end
Then you would have to add the following to your routes.rb:
devise_for :users, :controllers => { :sessions => "sessions" }
That should get you most of the way there.

Adapted from #karl-rosaen answer this solution create a new initializer or add to the end of your devise.rb initializer.
This will add the cookie to remember the email if remember me options is set, if not it will delete the cookie
Warden::Manager.after_authentication do |user, auth, opts|
if user.remember_me
auth.cookies[:email] = {value: user.email, expires: 2.weeks.from_now}
else
auth.cookies.delete :email
end
end

Related

Integrating Wicked, Devise and Omniauth-Facebook

I am developing an application using these three gem I wrote in the title of this post. I set up devise with the confirmable module(?), so when a user creates an account with its email/password, it receives a confirmation email. If the user sign up with facebook (using omniauth-facebook gem) devise skips the confirmation step.
In user.rb
"Of course the :confirmable is active in the model"
...
# Omniauth-facebook
def self.find_for_facebook_oauth(auth)
where(auth.slice(:provider, :uid)).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.first_name = auth.info.first_name
user.last_name = auth.info.last_name
user.skip_confirmation!
# user.image = auth.info.image # assuming the user model has an image
end
end
...
The thing comes when I added the wicked gem for the wizard. I configured the routes file
in routes.rb
MyApp::Application.routes.draw do
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks",
:registrations => "registrations" }
root 'home#index'
# Registration wizard routes
resources :after_register
end
The I created a registration_controller to override the devise registration methods
class RegistrationsController < Devise::RegistrationsController
def create
super
end
protected
def after_sign_in_path_for(resource)
puts "<<<<<<<<<<<<<<<< SIGN IN"
after_register_path(:import_contacts)
end
def after_sign_up_path_for(resource)
puts "<<<<<<<<<<<<<<<< SIGN UP ACTIVE"
after_register_path(:import_contacts)
end
def after_inactive_sign_up_path_for(resource)
puts "<<<<<<<<<<<<<<<< SIGN IN INACTIVE"
after_register_path(:import_contacts)
end
end
And then, I created a new controller to handle the steps of the wizard with wicked.
class AfterRegisterController < ApplicationController
include Wicked::Wizard
before_filter :authenticate_user!
steps :import_contacts, :select_agents, :wish_form
def show
#user = current_user
render_wizard
end
def update
#user = current_user
#user.attributes = params[:user]
render_wizard #user
end
end
When I create a user rith email/password, the wizard comes and everything works fine, but when I try to sign up with facebook, the wizars never comes.
Any hint???
Thank you!!
If everything is configured in the usual way (and what I can see looks pretty standard), then signing in using Facebook won't go via the RegistrationsController at all. It will go via the OmniauthCallbacks controller which you haven't posted the code for. It depends what you do in there when they log in via Facebook. I assume that you have a facebook() method which calls User.find_for_facebook_oauth(auth). Are you then just signing them in rather than going to the RegistrationsController? If so, then the RegistrationsController won't be touched, so its overridden after_sign_in_path_for won't have any effect.
If you want to have your overridden after_sign_in_path_for take effect throughout your app, you can define it in your ApplicationController. If you only want it to take effect in your OmniauthCallbacksController, you could define it there. In either case, they'll be hitting it every time they log in using Facebook (and if you put it in the ApplicationController, every time anyone logs in using any method), so you'd need to keep track of the fact that they have already been through the wizard, assuming you want to make sure that it only happens the first time they sign in. If you're using the devise :trackable module, perhaps checking the user.sign_in_count would be appropriate, or perhaps you have some other way to easily check if they have been through the wizard already.
UPDATE FOR COMMENT QUESTIONS:
Your first question: "Assuming I put the after_sign_in_path_fot in ApplicationController, I should remove the after_inactive_sign_up_path_for method in RegistrationsController, right?" It depends on what behaviour you want. With the RegistrationsController as in your question it will go to the wizard after they've signed up and before they've confirmed their email (because of after_inactive_sign_up_path_for which will be called in this case). When they confirm and sign in, the after_sign_in_path_for in ApplicationController will send them to the wizard again. So yes, remove inactive from RegistrationsController if you just want wizard after sign in. Then probably after_sign_up_path_for(resource) in RegistrationsController is unnecessary because the default implementation in devise just calls after_sign_in_path_for(resource) which you will have in your ApplicationController. Anyway, after_sign_up_path_for() won't be called if you always require confirmation because of the logic in the default implementation of RegistrationsController.create() - requiring confirmation will result in resource.active_for_authentication? returning false which causes after_inactive_sign_up_path_for(resource) to be called.
For the question in your second comment, you said "If I remove the after_sign_in_path_for in ApplicationController" - I assume you meant RegistrationsController? If that's right, then yes, there would be no methods needed in your overridden version of RegistrationsController (if that's the whole controller you pasted in your question) because your create() just calls super, the after_sign_in_path_for would be in ApplicationController and you probably don't want either of the after_(inactive)_sign_up_path_for methods as discussed above. So yes, there would be no need for your RegistrationsController. You could remove it completely and remove the :registrations => "registrations" in routes.rb - the devise implementation of RegistrationsController will then be used again.
Then you say "just override the methods of RegistrationsController in ApplicationController?". The only method you will have left from your RegistrationsController is the after_sign_in_path_for(resource) in ApplicationController, so I don't think there will be any other methods from your RegistrationsController that you need in ApplicationController. Let me know if I've missed one of your requirements or made an incorrect assumption.

Ruby on Rails Devise code after login

I have an RoR app using Devise for logins. There is some code that is executed when a new User record is created, by being put in the user.rb file as an after_create call/macro/whatever. I need to make this code run after each login, instead of running on new user creation.
With some Googling, it seems that one option is to place Warden callbacks in the devise.rb code. My questions are:
Is this right, and/or is there a better way to do this?
If this is the right approach ...
Should the Warden::Manager... method defs go in devise.rb inside of Devise.setup, or after it?
Is after_authentication the callback I should use? I'm just checking to see if a directory based on the user's name exists, and if not, creating it.
Just subclass Devise's sessions controller and put your custom behaviour there:
# config/routes.rb
devise_for :users, :controllers => { :sessions => "custom_sessions" }
And then create your controller like this:
# app/controllers/custom_sessions_controller.rb
class CustomSessionsController < Devise::SessionsController
## for rails 5+, use before_action, after_action
before_filter :before_login, :only => :create
after_filter :after_login, :only => :create
def before_login
end
def after_login
end
end
I found that using the Devise Warden hook cleanly allowed after login event trapping by looking for the ":set_user" :event.
In user.rb:
class User < ApplicationRecord
Warden::Manager.after_set_user do |user, auth, opts|
if (opts[:scope] == :user && opts[:event] == :set_user)
# < Do your after login work here >
end
end
end
I think this is an duplicate question. Yes you can execute code after every successful log in. you could write the code in your ApplicationController. Also have a look at http://github.com/plataformatec/devise/wiki/How-To:-Redirect-to-a-specific-page-on-successful-sign-in. Also, check out How to redirect to a specific page on successful sign up using rails devise gem? for some ideas.
You can do something like:
def after_sign_in_path_for(resource_or_scope)
Your Code Here
end
Reference Can I execute custom actions after successful sign in with Devise?
You could also inherit from devise session's class and use after_filter for logins.
UPDATED 2022
Warden now has built-in callbacks for executing your own code on after_authentication:
Warden::Manager.after_authentication do |user, _auth, _opts|
TelegramService.send("#{account.name} just logged in")
end
Source: https://github.com/wardencommunity/warden/wiki/Callbacks#after_authentication

Devise: user has to login again each time he closes the browser

I'm using Devise and Omniauth for user authentication.
I want the users to stay signed in for 2 weeks after the authentication. However, whenever the user closes the browser window and reopen it, he gets the login screen again.
What is the correct configuration for the user to stay connected even after the browser was closed and re-opened?
Check out this page on Devise's wiki:
https://github.com/plataformatec/devise/wiki/Omniauthable,-sign-out-action-and-rememberable
Basically, Devise does not call rememberable by default when using omniauth. If you wish to do so, simple call remember_me(#user) on your omniauth callback and Devise will do the hardwork for you. It will also use all the configuration options set in your devise initializer.
You have to extend the devise SessionsController to add cookies on log in and log out, so the controller will look like this one:
class SessionsController < Devise::SessionsController
# POST /resource/sign_in
def create
cookies[:sign_in] = "Sign in info you want to store"
# add this for expiration { :expires => 2.weeks.from_now }
super
end
# GET /resource/sign_out
def destroy
cookies[:sign_out] = "Sign out info you want to store"
super
end
end
Then you would have to add the following to your routes.rb:
devise_for :users, :controllers => { :sessions => "sessions" }
You may also set the cookie expiration time for 2 weeks.

Rails 3 override Devise sessions controller

I need to override Devise sessions controller during the login process (Rails 3.0.9, Ruby 1.9.2, Devise 1.3.4), I tried this without any effect
class SessionsController < Devise::SessionsController
# GET /resource/sign_in
def new
resource = build_resource
clean_up_passwords(resource)
respond_with_navigational(resource, stub_options(resource)){ render_with_scope :new }
end
end
Ideas?
EDIT
As indicated in the answer, I also need to change the route. In addition, I also need to copy the views. It's better explained here
http://presentations.royvandewater.com/authentication-with-devise.html#8
My custom strategy:
devise.rb
config.warden do |manager|
manager.strategies.add(:custom_strategy) do
def authenticate!
... authenticate against 3rd party API...
if res.body =~ /success/
u = User.find_or_initialize_by_email(params[:user][:email])
if u.new_record?
u.save
end
success!(u)
end
end
end
Have you altered your route to use your new controller?
/config/routes.rb
devise_for :users, :controllers => {:sessions => "sessions"}

How to add authentication before filter to a rails 3 apps with devise for user sign up and sign in?

Given I'm on Rails 3.1, Ruby 1.9.2 with a standard setup for devise to log a user in by name and password (e.g. similar to https://github.com/RailsApps/rails3-devise-rspec-cucumber ):
I need to add a layer that authenticates a posted username and password against an external service before creating a new user (sign up) or when a user signs in.
(FWIW, further down the road I plan on using the https://github.com/chicks/devise_aes_encryptable strategy/gem to encrypt the sensitive password and, when logging in with a local password, decrypt the one for the remote service, authenticate, then continue with logging in, that is have two passwords, one encrypted one-way, the other reversible... don't ask why, anyway)
In lib/util/authenticate.rb I have an authentication class that returns a boolean against this service e.g.
Util::Authenticate.authenticate(username,password)
But, I can't figure out how to add a filter to authenticate against it on form post before authentication continues (for sign up or sign in).
What I've tried:
I haver a User model and I thought to put a
before_filter :authenticate_against_my_service, :only => [:create, :new]
in the UserController but that didn't work
So, I tried opening up the Devise Sessions controller, which didn't work, nor did subclassing it (e.g. in the README ),
class User::SessionsController < Devise::SessionsController
# something
end
# in config/routes.rb
devise_for :users, :controllers => { :sessions => "users/sessions" }
nor subclassing the Devise Registrations controller (e.g. http://www.tonyamoyal.com/2010/07/28/rails-authentication-with-devise-and-cancan-customizing-devise-controllers/ )and adding a before_filter (same as above).
# in app/controllers/users/registrations_controller.rb
class Users::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
# and in config/routes.rb
root :to => "home#index"
# replace "devise_for :users" with the below
devise_for :users, :controllers => { :registrations => "users/registrations" }
# other related code
devise_for :users do
get 'logout' => 'devise/sessions#destroy'
end
# resources :users, :only => :show MUST be below devise_for :users
resources :users, :only => :show
I think I have to do this in the controller because once the params get to the model, I won't have an unencrypted password to send to the external service.
I looked at some extensions for ideas such as https://raw.github.com/nbudin/devise_cas_authenticatable/master/lib/devise_cas_authenticatable/strategy.rb e.g.
require 'devise/strategies/base'
module Devise
module Strategies
class CasAuthenticatable < Base
# True if the mapping supports authenticate_with_cas_ticket.
def valid?
mapping.to.respond_to?(:authenticate_with_cas_ticket) && params[:ticket]
end
# Try to authenticate a user using the CAS ticket passed in params.
# If the ticket is valid and the model's authenticate_with_cas_ticket method
# returns a user, then return success. If the ticket is invalid, then either
# fail (if we're just returning from the CAS server, based on the referrer)
# or attempt to redirect to the CAS server's login URL.
def authenticate!
ticket = read_ticket(params)
if ticket
if resource = mapping.to.authenticate_with_cas_ticket(ticket)
# Store the ticket in the session for later usage
if ::Devise.cas_enable_single_sign_out
session['cas_last_valid_ticket'] = ticket.ticket
session['cas_last_valid_ticket_store'] = true
end
success!(resource)
elsif ticket.is_valid?
username = ticket.respond_to?(:user) ? ticket.user : ticket.response.user
redirect!(::Devise.cas_unregistered_url(request.url, mapping), :username => username)
#fail!("The user #{ticket.response.user} is not registered with this site. Please use a different account.")
else
fail!(:invalid)
end
else
fail!(:invalid)
end
end
protected
def read_ticket(params)
#snip
end
end
end
end
Warden::Strategies.add(:cas_authenticatable, Devise::Strategies::CasAuthenticatable)
and read about devise/warden authentication strategies, e.g. https://github.com/hassox/warden/wiki/Strategies but wonder if I need to actually create a new strategy (and if I can figure out how to do that)
EDIT, POSSIBLE SOLUTIONS:
I like alno's suggestion and will try that, though it seems more like a monkeypatch than how devise/warden is meant to be used
module Devise::Models::DatabaseAuthenticatable
alias_method :original_valid_password?, :valid_password?
def valid_password?
if Util::Authenticate.authenticate(username,password)
original_valid_password?
else
false
end
end
end
alternatively, I've looked into adding a warden authentication strategy, but it's hard to figure out all the moving parts, e.g. from Custom authentication strategy for devise
initializers/authentication_strategy.rb: # this could be in initializers/devise.rb as well, no?
Warden::Strategies.add(:custom_external_authentication) do
def valid?
# code here to check whether to try and authenticate using this strategy;
return true # always use the strategy as only user's authenticate
end
def authenticate!
# code here for doing authentication; if successful, call success!
# whatever you've authenticated, e.g. user; if fail, call fail!
if Util::Authenticate.authenticate(username, password)
success!(true) # I don't think I want to return a user, as I'll let database authenticatable handle the rest of the authentication
# I don't think I'm using success! correctly here: https://github.com/hassox/warden/wiki/Strategies
# success!(User.find(someid))
else
fail!("Username and password not valid for external service. Please ensure they are valid and try again.")
end
end
end
add following to initializers/devise.rb
Devise.setup do |config|
config.warden do |manager|
manager.default_strategies.unshift :custom_external_authentication # will this check before or after database authentication? I want before, I think
end
end
OR from How do I add a strategy to Devise
class ExternalServiceStrategy
def valid?
true # always use this
end
def authenticate!
# external boolean service call
end
end
Warden::Strategies.add(:database_authenticatable, ExternalServiceStrategy) # will this work before the db authentication?
If you see in devise source, you will find an valid_password? method, which accepts unencrypted password, so you may override it to authenticate versus some extenal service.
Something like:
def valid_password?(password)
ExternalService.authenticate(email, password)
end
You should be making your changes in the model layer, not in the controller. In fact I would advice you create a file in /lib/whatever that handles talking to the external service, and then modify your User model so that it checks the external service.

Resources