I'd like to use HTTP Digest authentication for a specific route in my modular Sinatra App.
The examples listed within the sinatra recipes website simply describe how to enable digest auth for an entire app. The solution presented for getting this to work for specific routes is to create two separate apps, (one with digest auth and another without) placing the protected and unprotected routes in their respective applications.
That would require something like the following:
require 'sinatra/base'
class Protected < Sinatra::Base
use Rack::Auth::Basic, "Protected Area" do |username, password|
username == 'foo' && password == 'bar'
end
get '/' do
"secret"
end
end
class Public < Sinatra::Base
get '/' do
"public"
end
end
This seems like overkill to me.
Is there a way to protect a single route in a modular sinatra app without having to create an entirely new app?
There is an example in the FAQ which references creating an instance of Rack::Auth::Basic::Request and passing in the environment but doing something like this with digest auth would differ greatly and be much more of a manual authentication procedure.
Here is the basic authentication example:
def authorized?
#auth ||= Rack::Auth::Basic::Request.new(request.env)
#auth.provided? &&
#auth.basic? &&
#auth.credentials &&
#auth.credentials == ['admin', 'admin']
end
Does anyone have thoughts on how this could be done?
Using a before Filters. Do you need different behaviour for the same route?
Related
I have an app I created on Heroku which is written in Ruby (not rails) and Sinatra.
It is hosted on the default herokuapp domain so I can address the app with both HTTP and HTTPS.
The app requests user credentials which I forward on to an HTTPS call so the forwarding part is secure.
I want to ensure my users always connect securely to my app so the credentials aren't passed in clear text.
Despite lots of research, I've not found a solution to this simple requirement.
Is there a simple solution without changing my app to Ruby rails or otherwise?
Thanks,
Alan
I use a helper that looks like this:
def https_required!
if settings.production? && request.scheme == 'http'
headers['Location'] = request.url.sub('http', 'https')
halt 301, "https required\n"
end
end
I can then add it to any single route I want to force to https, or use it in the before filter to force on a set of urls:
before "/admin/*" do
https_required!
end
Redirect in a Before Filter
This is untested, but it should work. If not, or if it needs additional refinement, it should at least give you a reasonable starting point.
before do
redirect request.url.sub('http', 'https') unless request.secure?
end
See Also
Filters
Request Object
RackSsl::Enforcer
I'm building a Grape API alongside Sinatra. So far I've been mounting them in separate routes like this:
run Rack::URLMap.new("/" => Frontend::Server.new,
"/api" => API::Server.new)
Where the "/api" is served by a Grape app and "/" by a Sinatra app. But I wanted to use subdomains to separate those concerns instead of the actual "sub-URL". Any clues on how to do this?
Thanks in advance for the help.
There is a rack-subdomain gem, but it only handles redirection to paths, not rack apps. You could fork it and make it redirect to rack apps instead.
You could also just roll your own :
class SubdomainDispatcher
def initialize
#frontend = Frontend::Server.new
#api = API::Server.new
end
def call(env)
if subdomain == 'api'
return #api.call(env)
else
return #frontend.call(env)
end
end
private
# If may be more robust to use a 3rd party plugin to extract the subdomain
# e.g ActionDispatch::Http::URL.extract_subdomain(#env['HTTP_HOST'])
def subdomain
#env['HTTP_HOST'].split('.').first
end
end
run SubdomainDispatcher.new
I want to verify that a method was called on a service I want to inject into a Sinatra application using rspec but I can't find an example of how this is done. Here is my spec...
RSpec.configure do |config|
config.include Rack::Test::Methods
end
def app
App
end
describe 'Login' do
context 'when the user is logged out' do
describe 'POST on /signup' do
it 'invokes signup on the user service with the correct parameters' do
service = double('user_service').as_null_object
service.should_receive(:signup).with(:username => 'RobA2345')
post '/signup'
end
end
end
end
Here the App is a modular Sinatra app. I come from a .NET background and I'd use constructor injection here to solve this problem but I know this isn't the ruby way to do it.
Help, as always, is appreciated.
Assuming that you're expecting to receive the message on a new instance of UserService, there are a couple of ways to do this. If you are using a recent version of rspec, this should work:
it 'invokes signup on the user service with the correct parameters' do
UserService.any_instance.should_receive(:signup).with(:username => 'RobA2345')
post '/signup'
end
Alternatively, this should work in just about any version of rspec:
it 'invokes signup on the user service with the correct parameters' do
service = double('user_service').as_null_object
UserService.stub(:new).and_return(service)
service.should_receive(:signup).with(:username => 'RobA2345')
post '/signup'
end
I'm using the delayed_job_web gem to monitor delayed jobs. https://github.com/ejschmitt/delayed_job_web
It is accessible using this line in my routes.rb:
match "/delayed_job" => DelayedJobWeb, :anchor => false
Every other area of my site requires a login using the Devise gem. How do I make this require a login too?
In the readme, they suggest adding the following to the config.rb:
if Rails.env.production?
DelayedJobWeb.use Rack::Auth::Basic do |username, password|
username == 'username'
password == 'password'
end
end
But that just uses plain text browser authentication.
UPDATE:
I tried something similar to the railscast on resque, and I think it's on the verge of working but giving me a redirect loop now:
authenticate :admin do
mount DelayedJobWeb, :at => "/delayed_job"
end
Any thoughts on why would it be giving a redirect loop?
Thanks,
Use authenticated instead of authenticate as described here: http://excid3.com/blog/rails-tip-5-authenticated-root-and-dashboard-routes-with-devise/
Works for me!
You could do something like this define this inside config/routes.rb file
authenticate_user = lambda do |request|
request.env['warden'].authenticate?
end
constraints authenticate_user do
mount DelayedJobWeb, :at => "/delayed_job"
end
Alternately if you have cancan for any other role management library you could do it something like this
I have used both of this in my applications to control access to resque-web depending on the needs of the application
Hope this help
Now it's 2017 I tried the other solutions and they didn't work, but the following admin check does work:
authenticated :user, -> user { user.admin? } do
mount DelayedJobWeb, at: "/delayed_job"
end
I recently created a Sinatra app with a login form (no basic auth). To prevent access to the app unless the user logged in I put a before block in place
before do
unless request.path_info == '/login'
authenticated?
end
end
I quickly realized that this prevented me from accessing resources in the public directory like my style sheet and logo unless authenticated first as well. To get around that I changed my filter to the following:
before do
unless request.path_info == '/login' || request.path_info == "/stylesheets/master.css" || request.path_info == "/images/logo.png"
authenticated?
end
end
If there were lots of resources I needed to provide exceptions to this way of making them would quickly become overwhelming. What is a better way to code this so I can make exceptions for the public directory or even its specific sub-directories and files like /stylesheets, /images, /images/bg.png but not /secret or /secret/eyes-only.pdf?
Or ... Is there a completely different best-practice to handle this situation of locking down everything except the stuff related to logging in (handlers, views, resources)?
You could extract the login logic into it's own Rack middleware (which can be a Sinatra app).
The authentication middleware will serve the public files.
require 'sinatra'
class Authentication < Sinatra::Base
def logged_in?
# your login logic goes here
end
get '/login' do
# login formular and logic here
end
get(//) do
pass if logged_in?
redirect '/login'
end
end
configure { |c| c.use Authenitcation }
get('/') { ... }
Instead of putting the authorization information into your Sinatra application directly, why don't you extract it into Rack using Rack::Auth:
# my_app.ru
app = Rack::Builder.new do
use Rack::Static, :urls => /^(stylesheets|javascripts|images|fonts)\//
map '/login' do
run MyApplication
end
map '/' do
use Rack::Auth::Basic do |username, password|
# check the username and password sent via HTTP Basic auth
end
run MyApplication
end
end