tags:

views:

296

answers:

2

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)?

A: 

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
James A. Rosen
@james I think your suggestion would cause my app to use basic auth (standard browser dialog box presented to user) which is usually a great solution but for this specific app I have to present the user with an **html form** for login where they can enter a username and password into input fields and click on a submit button
Brandon Toone
In that case, I suggest just using a different middleware. It would still return a 401 Unauthorized but would not use the "WWW-Authenticate: Basic" header and would include a body. This is largely equivalent to Konstantin's suggestion to use a separate Sinatra app as a middleware.
James A. Rosen
+1  A: 

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('/') { ... }
Konstantin Haase