I'm using devise to authenticate my users via username / password or a couple of OAuth providers. Now I have to support a custom authentication Strategy that uses a token inside a header.
module WardenStrategies
class ApiKey < Warden::Strategies::Base
def valid?
api_token.present?
end
def authenticate!
user = User.find_by(api_token: api_token)
if user
success!(user)
else
fail!("Invalid email or password")
end
end
private
def api_token
env["HTTP_AUTHORIZATION"].to_s.remove("Bearer ")
end
end
end
I've added the strategy to devise and warden
# config/initializers/devise.rb
Devise.setup do |config|
# ...
config.warden do |manager|
manager.default_strategies(:scope => :user).unshift :api_key
end
# ...
end
# config/initializers/warden.rb
Warden::Strategies.add(:api_key, WardenStrategies::ApiKey)
The problem now is: I have a playground on the same site that sends requests to my API via JavaScript. In the playground I can provide a custom API key for a different user. But when the user is already logged in via a normal session, warden doesn't run the strategy any more, because it already has a User assigned.
So, how can I force warden / devise to try a strategy, even when the user is already authenticated?
Before calling any strategies warden checks if there's already a stored user in session (from previous requests, where some strategy with store?=true (the default) has succeeded)
You can try faking a 'non-set' user (without full log out) by something like:
# manager is Warden::Manager
manager.prepend_on_request do |proxy|
proxy.set_user(nil, scope: :user, store: false) if proxy.env["HTTP_AUTHORIZATION"].present?
end
PS. your strategy probably should also have def store?; false; end, as api keys are usually required with each request, and also should not result in persisted session
Related
I am looking to set a secondary password by which I can authenticate a user for a login as from admin. The reason for this work around is the front end is a single page application.
Each user has been given a unique login_as string. now I need to configure Devise to compare the login_as if the password fails.
Any help is appreciated. I am of course open to an alternative solution if there is a better way.
Thanks.
This post from Duncan Robertson was very helpful in solving my issue. I essentially created an override strategy and called it in the devise.rb file. I had some concern regarding tampering with a large user base but it has proved successful. By adding a column to users named ":signin_as" and then setting it to a default unique string with a rake I then had what I needed to fallback on if the initial sign in failed.
the override strategy (config/initializers/auth_override.rb)
module Devise
module Strategies
class AuthOverride < Authenticatable
def custom_auth(user, signin_as)
if user[:signin_as] == signin_as
return true
else
return false
end
end
def authenticate!
user = User.find_by_email(email)
if user
if user.valid_password?(params[:password])
success!(user)
elsif custom_auth(user, params[:password])
success!(user)
else
fail
end
else
fail
end
end
end
end
end
including the strategy in devise (config/initializers/devise.rb)
config.warden do |manager|
manager.default_strategies(:scope => :user).unshift :auth_override
end
In my app I need to have a database authentication but without a password. Just by entering your phone number. When the user sings up he enter phone number, adress and name and no password.
Is it real? Just can't figure out how to make it.
Thanks for help in advance!
Figured out like this:
In devise_no_pass.rb initializer add:
require 'devise/strategies/authenticatable'
module Devise
module Strategies
class DeviseNoPass < Authenticatable
def authenticate!
return super unless params[:customer_sign_in]
customer = Customer.find_by_phone(params[:customer_sign_in]
customer ? success!(customer) : raise
end
end
end
end
Warden::Strategies.add(:devise_no_pass, Devise::Strategies::DeviseNoPass)
In devise.rb:
config.warden do |manager|
manager.default_strategies(:scope => :customer).unshift :devise_no_pass
end
My setup: Rails 3.0.9, Ruby 1.9.2, Devise 1.3.4, Warden 1.0.4
I'm trying to figure out if it possible to authenticate a custom strategy and not have to create a devise user in the process upon successful authentication. In my config.warden block, the authentication works fine but if I don't create a Devise user, I won't be authenticated. My ideal scenario requires me to either successfully authenticate against a 3rd party provider and sign into my app (using Devise without a corresponding Devise user record) or if I fail to authenticate, then try Devise standard login path.
Here is the devise.rb code snippet I got working but I have to create a devise user for the authentication to work, this is something I wish to avoid
config.warden do |manager|
manager.strategies.add(:custom_strategy) do
def valid?
params[:user] && params[:user][:email] && params[:user][:password]
end
def authenticate!
...perform authentication against 3rd party provider...
if successful_authentication
u = User.find_or_initialize_by_email(params[:user][:email])
if u.new_record?
u.app = 'blah'
u.save
end
success!(u)
end
end
end
manager.default_strategies(:scope => :user).unshift :custom_strategy
end
I realized the question is old but I saw it a couple of time when I was searching for a solution to similar thing so I decided to post the answer in case anyone in the future stumbles upon similar issue. Hope this will help!
I recently had to do similar thing -> had users in my database that were authenticated with some devise/warden strategies but had created another app that has to have access to some of the endpoints to my application. Basically I wanted to do a HMAC authentication.
But I didn't want to involve any user objects in that process and here is what I had to do (provided that you already have you custom strategy that authenticates incoming request without using user object)
create a fake user model that is used so that devise wont blow op. You dont have to create any database table for that
mine looked similar to below:
class Worker # no need to create a table for him
extend ActiveModel::Callbacks
extend Devise::Models
include ActiveModel::Validations
include Concerns::ObjectlessAuthenticatable
define_model_callbacks :validation
attr_accessor :id
def persisted
false
end
def initialize(id)
#id = id
end
def self.serialize_from_session(id)
self.new(id: id)
end
def self.serialize_into_session(record)
[record.id]
end
def self.http_authenticatable
false
end
end
then in devise initializer (/initializers/devise.rb) I've added separate authentication strategy like below:
...
config.warden do |manager|
manager.scope_defaults :user, :strategies => [
...strategies i was using for users
]
manager.scope_defaults :worker, :strategies => [:worker_authentication], store: false, action: 'unautenticated_worker'
manager.failure_app = CustomFailingApp
end
...
then in routes.rb I had to create a mapping for devise to use like so
devise_for :worker # you can pass some custom options here
then wherever I needed to authenticate the worker, not the user I just had to call (in the controller) authenticate_worker!
I would expect that this is against the design of devise where all actions are done using restful routes for a resource. That said, the comments in Warden's success! method say:
# Parameters:
# user - The user object to login. This object can be anything you have setup to serialize in and out of the session
So could you not change the object u to some other object that represents the user, like a plain old Hash?
I have a Rails app using Devise for authentication. Users belong to Dealers and I want to prevent users who belong to disabled dealers from being able to sign in.
Is there a straightforward way to extend Devise's authentication finder so that it will not include users from deleted dealers? Perhaps using a named scope on User?
Cheers
Tristan
Turns out all I needed to do was override my user model's find_for_authentication method:
class User < ActiveRecord::Base
...
# Intercept Devise to check if DealershipUser's Dealership is active
def self.find_for_authentication(conditions)
user = super
return nil if user.is_a?(DealershipUser) && user.dealership.deleted?
user
end
...
end
Find the user in the normal way by calling super.
I'm using STI so I check that the user is a DealershipUser and then check if the dealership is deleted (acts_as_paranoid).
Return the user.
This is a very specific solution for my scenario but you could override find_for_authentication however you like provided you return the user afterwards.
Searching Stackoverflow.com gave me this question/answer: Custom authentication strategy for devise
Basically, you have to implement a custom authentication strategy at Warden's level (that underlies Devise). For my project, I did the following:
In config/initializers/devise.rb:
Devise.setup do |config|
config.warden do |manager|
manager.default_strategies(:scope => :user).unshift :user_has_login_access
end
end
Warden::Strategies.add(:user_has_login_access) do
def valid?
# pass the commit parameter as 'login' or something like that, so that this strategy only activates when the user is trying to login
params[:commit] == 'login'
end
def authenticate!
u = User.find_by_email(params[:user][:email])
if u.can_login? # retrieves boolean value stored in the User model, set wherever
success! u
else
fail! "Account does not have login privilages."
end
end
end
You can read more about custom Warden strategies here: https://github.com/hassox/warden/wiki/Strategies
Hope that helps!
I need to write a custom authentication strategy for https://github.com/plataformatec/devise but there doesn't seem to be any docs. How's it done?
I found this very helpful snippet in this thread on the devise google group
initializers/some_initializer.rb:
Warden::Strategies.add(:custom_strategy_name) do
def valid?
# code here to check whether to try and authenticate using this strategy;
return true/false
end
def authenticate!
# code here for doing authentication;
# if successful, call
success!(resource) # where resource is the whatever you've authenticated, e.g. user;
# if fail, call
fail!(message) # where message is the failure message
end
end
add following to initializers/devise.rb
config.warden do |manager|
manager.default_strategies.unshift :custom_strategy_name
end