Question

I'm writing a Sinatra Rack App and I want to use Warden for authentication. I'm using heroku's toolbelt so I use foreman to run my app. I've found some code that's presumably supposed to get this working. Unfortunately, when I attempt to actually access the Warden env object, it is nil.

I've attempted to use the sinatra_warden gem, but it also has its own bugs (might be related to this one).

config.ru:

require './web.rb'
use Rack::Static, :urls => ["/css", "/img", "/js"], :root => "public"
run MyApp

web.rb:

require 'sinatra'
require 'warden'
require 'data_mapper'

require './config/datamapper.rb'
require './config/warden.rb' # I've tried this inside of MyApp, still didn't work

class MyApp < Sinatra::Base

  get '/test' do
    env['warden'].authenticate! # env['warden'] is nil :(
  end

end

config/warden.rb:

use Rack::Session::Cookie, :secret => ENV['SESSION_SECRET']

use Warden::Manager do |manager|
  manager.default_strategies :password
  manager.failure_app = MyApp.new
end

Warden::Manager.serialize_into_session { |user| user.id }
Warden::Manager.serialize_from_session { |id| User.get(id) }

Warden::Manager.before_failure do |env,opts|
  # Sinatra is very sensitive to the request method
  # since authentication could fail on any type of method, we need
  # to set it for the failure app so it is routed to the correct block
  env['REQUEST_METHOD'] = "POST"
end

Warden::Strategies.add(:password) do
  def valid?
    params["email"] || params["password"]
  end

  def authenticate!
    u = User.authenticate(params["email"], params["password"])
    u.nil? ? fail!("Could not log in") : success!(u)
  end
end

Versions:

  • Sinatra: 1.1.0
  • Warden: 1.2.1
  • Rack: 1.4.1
  • Ruby: 1.9.3p194
  • Foreman: 0.60.0

Any ideas how to use Warden the set up I've described?

(P.S. Out of curiosity, what exactly is the env variable?)

Was it helpful?

Solution

Rack internally uses the class Rack::Builder to parse your config.ru file and wrap directives to build up the middleware components.

I believe your builder calls to use in config/warden.rb are getting ignored. It may work to remove the directives from that file and add them to the middleware stack in config.ru:

require './web.rb'

use Rack::Session::Cookie, :secret => ENV['SESSION_SECRET']

use Warden::Manager do |manager|
  manager.default_strategies :password
  manager.failure_app = MyApp.new
end

use Rack::Static, :urls => ["/css", "/img", "/js"], :root => "public"

run MyApp

OTHER TIPS

Put a link to your config/warden in your config.ru

require File.dirname(__FILE__) + '/config/warden'

Read the warden readme. Or look right in the lib/warden.rb

I put

Warden.test_mode!

in place of the env call at the /test path and get a nice blank page at

http://localhost:9292/test

Some bloggers have stated that there isn't a lot of documentation for warden but I disagree. There is a whole wiki. see https://github.com/hassox/warden/wiki

Take it slow and find out how to use middleware in Rack. Here's a very good article https://blog.engineyard.com/2015/understanding-rack-apps-and-middleware

I think maybe you might want to start out with tests as I found a good example and you could use it with your app.

ENV['RACK_ENV'] = 'test'
require 'test/unit'
require 'rack/test'

require File.dirname(__FILE__) + '/web'

class AuthenticationTest < Test::Unit::TestCase
  include Rack::Test::Methods

  def app
    WardenTest #MyApp
  end

  def test_without_authentication
    get '/protected'
    assert_equal 401, last_response.status
  end

  def test_with_bad_credentials
    authorize 'bad', 'boy'
    get '/protected'
    assert_equal 401, last_response.status
  end

  def test_with_proper_credentials
    authorize 'admin', 'admin'
    get '/protected'
    assert_equal 200, last_response.status
    assert_equal "You're welcome, authenticated client", last_response.body
  end
end

Then a few routes added to your app.

helpers do
  def protected!
    return if authorized?
    headers['WWW-Authenticate'] = 'Basic realm="Restricted Area"'
    halt 401, "Not authorized\n"
  end

  def authorized?
    @auth ||=  Rack::Auth::Basic::Request.new(request.env)
    @auth.provided? and @auth.basic? and @auth.credentials and     
    @auth.credentials == ['admin', 'admin']
  end
end

get '/' do
  "Everybody can see this page"
end

get '/protected' do
  protected!
  "You're welcome, authenticated client"
end 

In my experience working with Ruby, it's always a good idea to start out with tests for any new project. I often test little pieces first though just to gain an understanding of how they work.

Once you get a better understanding of Rack, especially Rack::Builder, you can use

map '/test' do
  ...all the middleware needed
  run App
end

and try out different configurations to see which ones work best for your needs as I'm doing while I write this.

Enjoy! ;-)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top