I want to implement HTTP basic authentication on my staging server, but only for those outside the local network. I have a Rails 3.1 app. In application.rb, I have the following:
class ApplicationController << ActionController::Base
http_basic_authenticate_with :realm => "Staging", :name => "user", :password => "password" if :need_authentication?
private
def need_authentication?
Rails.env == "staging" && request.remote_addr !~ /^192.168.0.\d{1,3}$/
end
end
Here's the rub: even when the need_authentication? method explicitly returns false, the app still asks me to authenticate, as if it's completely ignoring the if clause at the end.
So, is there any way to only require authentication under certain conditions?
In Rails 4, the :if condition works. For example,
class ApplicationController < ApplicationController::Base
http_basic_authenticate_with name: "user", password: "password" if Rails.env == 'staging'
end
or if you want a helper method to set the condition,
class ApplicationController < ApplicationController::Base
http_basic_authenticate_with name: "user", password: "password", if: :need_authentication?
private
def need_authentication?
Rails.env == 'staging'
end
end
This is what worked:
class ApplicationController < ActionController::Base
before_filter :authenticate_if_staging
private
def authenticate_if_staging
if Rails.env == 'staging' && request.remote_addr !~ /^192.168.0.\d{1,3}$/
authenticate_or_request_with_http_basic 'Staging' do |name, password|
name == 'username' && password == 'secret'
end
end
end
end
'Staging' is the name of the realm. This is not required, but can be used for clarification.
Related
How to add HTTP AUTH at custom controller action?
class MyController < ApplicationController
def index
#NO AUTH
end
def custom
#I NEED HTTP AUTH ONLY HERE
end
end
routes.rb:
get 'my/custom', to: 'my#custom'
class MyController < ApplicationController
http_basic_authenticate_with name: "dhh", password: "secret", only: [:custom]
def custom
#I NEED HTTP AUTH ONLY HERE
end
end
You can also call the auth directly in the action:
class MyController < ApplicationController
def custom
authenticate_or_request_with_http_basic do |username, password|
username == "dhh" && password == "secret"
end
...
end
end
Here are the docs for more advanced usage: https://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Basic.html
You can use the http_basic_authenticate_with method. I have passed the :custom symbol to the :only option, which means the authentication will only apply to that method.
class MyController < ApplicationController
http_basic_authenticate_with name: "username", password: "password", only: :custom
def index
#NO AUTH
end
def custom
#I NEED HTTP AUTH ONLY HERE
end
end
I use Rails 5 API mode
routes.rb:
Rails.application.routes.draw do
constraints Basic do
mount Rswag::Ui::Engine => '/api-docs'
mount Rswag::Api::Engine => '/api-docs'
mount Sidekiq::Web => '/sidekiq'
# etc
end
end
constraints/basic.rb:
class Basic
def self.matches?(request)
authenticate_or_request_with_http_basic do |email, password|
email == 'foo' && password = 'bar'
end
end
end
But I'm getting an error:
NoMethodError (undefined method `authenticate_or_request_with_http_basic' for Basic:Class)
Can I use http basic auth in constraints?
I dont think you can use authenticate_or_request_with_http_basic method out of controllers scope. You can set up before_filter with auth check in general controller. Here is an example taken from docs comments:
class AdminController < ApplicationController
before_filter :authenticate
def authenticate
authenticate_or_request_with_http_basic('Administration') do |email, password|
email == 'foo' && password == 'bar'
end
end
end
Also here is what I found Rails Authentication Routing Constraints Considered Harmful.
That being said I think there is a way:
class Basic
def self.matches?(request)
if ActionController::HttpAuthentication::Basic.has_basic_credentials?(request)
credentials = ActionController::HttpAuthentication::Basic.decode_credentials(request)
email, password = credentials.split(':')
email == 'foo' && password == 'bar'
end
end
end
Here is docs on HTTP Basic authentication with examples
I have some strange issue : if I run
class ApplicationController < ActionController::Base
http_basic_authenticate_with name: "test", password: "test"
it works fine. But if I put this
before_filter authenticate_incoming
def authenticate_incoming
http_basic_authenticate_with name: "hi", password: "ho"
end
I get undefined method http_basic_authenticate_with. Where am I joint wrong ?
the source code of http_basic_authenticate_with is
def http_basic_authenticate_with(options = {})
before_filter(options.except(:name, :password, :realm)) do
authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
name == options[:name] && password == options[:password]
end
end
end
So you see that it is implementing before_filer. So what you should do is using something like (you can also store some session login data ;))
def authenticate_incoming
authenticate_or_request_with_http_basic do |name, password|
if name == 'hi' && password == 'ho'
session[:logged_in] = true
return true
else
return false
end
end
end
http_basic_authenticate_with is a class method and authenticate_incoming is an instance method. You could do self.class.http_basic_authenticate_with but that doesn't make a whole lot of sense to do in a before_filter. What's your goal? Maybe I can help you think through how to acomplish it.
I want to set basic authentication for ActiveAdmin, which internal devise solution doesn't apply to my case. For that I would like to be able to add middleware to the ActiveAdmin Engine before this is bundled into my app. What I managed to do was:
ActiveAdmin::Engine.configure do |config|
config.middleware.use Rack::Auth::Basic do |username, password|
username == 'admin' && password == 'root'
end
end
But apparently this doesn't make it work, since my active admin routes are still unprotected. How can I effectively do this? And no, I don't want to protect my whole site with basic authentication.
Here's a few ideas:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# ...
http_basic_authenticate_with :name => "frodo", :password => "thering", :if => :admin_controller?
def admin_controller?
self.class < ActiveAdmin::BaseController
end
Or, the monkeypatching version
# config/initializers/active_admin.rb
# somewhere outside the setup block
class ActiveAdmin::BaseController
http_basic_authenticate_with :name => "frodo", :password => "thering"
end
If you only want to protect specific resources, you can use the controller block:
# app/admin/users.rb
ActiveAdmin.register Users do
controller do
http_basic_authenticate_with :name => "frodo", :password => "thering"
end
# ...
end
I was hoping that I would be able to extend the controller in this way in config/initializers/active_admin.rb in the setup block, but this didn't work for me:
# app/admin/users.rb
ActiveAdmin.setup do |config|
config.controller do
http_basic_authenticate_with :name => "frodo", :password => "thering"
end
# ...
end
You might try it though, as it could be an ActiveAdmin version thing (I could have sworn that I saw that documented somewhere...)
Good luck, I hope this helps.
UPDATE: A couple more options:
I hadn't realized before that :before_filter in activeadmin config takes a block.
# config/initializers/active_admin.rb
ActiveAdmin.setup do |config|
# ...
config.before_filter do
authenticate_or_request_with_http_basic("Whatever") do |name, password|
name == "frodo" && password == "thering"
end
end
end
And... just one more idea. It sounds like you are not keen on adding anything to application_controller, but this version is not conditional like the first above:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def authenticate_admin
authenticate_or_request_with_http_basic("Whatever") do |name, password|
name == "frodo" && password == "thering"
end
end
end
# config/initializers/active_admin.rb
ActiveAdmin.setup do |config|
# ...
config.authentication_method = :authenticate_admin
end
If you just want to protect the admin area of ActiveAdmin, then you should try this:
# app/admin/dashboard.rb
controller do
http_basic_authenticate_with :name => "mega-admin", :password => "supersecret"
end
that works like a charm ;-)
have fun
just another solution for you would be:
# app/controllers/application_controller.rb
protected
def authenticate
authenticate_or_request_with_http_basic do |username, password|
username == "admin" && password == "superpassword"
end
end
# config/initializers/active_admin.rb
config.before_filter :authenticate
the big plus for this solution ist, that you can call
before_filter :authenticate
in every area you want to protet.
Using Rails 3.2.
I have half a dozen controllers, and want to protect some (but not all) of them with http_basic_authenticate_with.
I don't want to manually add http_basic_authenticate_with to each controller (I could add another controller in the future and forget to protect it!). It seems the answer is to put it in application_controller.rb with an :except arg which would list the controllers that should not be protected. The problem is, the :except clause wants method names rather than external controller module names, e.g.:
http_basic_authenticate_with :name => 'xxx', :password => 'yyy', :except => :foo, :bar
So then I thought "Wait, since I already have the protected controllers grouped in routes.rb, let's put it there." So I tried this in my routes:
scope "/billing" do
http_basic_authenticate_with :name ...
resources :foo, :bar ...
end
But now I get
undefined method `http_basic_authenticate_with'
What's the best way to approach this?
Do it the way Rails does it.
# rails/actionpack/lib/action_controller/metal/http_authentication.rb
def http_basic_authenticate_with(options = {})
before_action(options.except(:name, :password, :realm)) do
authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
name == options[:name] && password == options[:password]
end
end
end
All that http_basic_authenticate_with does is add a before_action. You can just as easily do the same yourself:
# application_controller.rb
before_action :http_basic_authenticate
def http_basic_authenticate
authenticate_or_request_with_http_basic do |name, password|
name == 'xxx' && password == 'yyy'
end
end
which means you can use skip_before_action in controllers where this behavior isn't desired:
# unprotected_controller.rb
skip_before_action :http_basic_authenticate