以人为本

Core developer of Mixin Network. Passionate about security and privacy.

Goliath Authenticate with Warden

Sep 12, 2012

Goliath is a high performance asynchronous concurrent Rack server, based on Ruby EventMachine, with WebSocket support built in. I’ve tried to use Goliath to build a chat room, absolutely with user authentication, so Warden comes.

Warden strategy

Warden is a brilliant gem, with simple but detailed documents. Follow the wiki I defined the basic password strategy as below.

Warden::Strategies.add(:password) do
  def authenticate!
    user = User.authenticate(env.params['email'], env.params['password'])
    user.nil? ? fail!('FAIL TO AUTHENTICATE!') : success!(user)
  end
end

Then I can choose when to authenticate the user by the REQUEST_PATH with a custom middleware named Authentication.

class Authentication
  def initialize(app)
    @app = app
  end

  def call(env)
    case env['REQUEST_PATH']
    when '/signout'
      env['warden'].logout
      [200, {}, 'Logged out']
    when '/signin'
      if env['REQUEST_METHOD'] == 'POST'
        env['user'] = env['warden'].authenticate!
        return [302, {'location' => '/'}, self] if env['user']
      end
      @app.call(env)
    else
      env['user'] = env['warden'].authenticate!
      @app.call(env)
    end
  end
end

At last, it’s time to use the Authentication in my Goliath WebSocket chat room.

class Chat < Goliath::WebSocket
  use Goliath::Rack::Params
  use Rack::Session::Cookie, :key => '_chat_goliath',
    :secret => BCrypt::Password.create(Time.now)
  use Warden::Manager, default_strategies: :password,
    failure_app: Proc.new { |env| [302, {'location' => '/signin'}, self] }
  use Authentication
end 

Thanks to Ruby’s beautiful syntax, the code is just self explained.

Troubleshooting

Nothing will be so lucky.

At first, I found Goliath::WebSocket can’t handle POST requests. If I want to sign in with a form to post email and password, I got an error:

undefined method `handler' for #<Goliath::Env:0x0000000229e350>

Then I tried to create two different Goliath API, one for HTTP and one for WebSocket. Guess what? Goliath 1.0 will not have a router, so I can’t do anything like map '/signin', Authentication.

No road leads to Rome, I can build my own one. Just override the on_body method of Goliath::WebSocket

class Chat < Goliath::WebSocket
  def on_body(env, data)
    if env.respond_to?(:handler)
      env.handler.receive_data(data)
    else
      env['params'] = Rack::Utils.parse_query(data)
    end
  end
end

Finally, I got the Goliath to work. It’s a bit harder to handle Goliath than Sinatra or Cramp, because it’s an app server more than an app framework. But it deserved.

About the Author

Core developer of Mixin Network. Passionate about security and privacy. Strive to formulate elegant code, simple design and friendly machine.

25566 @ Mixin Messenger

[email protected]