class OAuthenticator::RackAuthenticator

Rack middleware to determine if the incoming request is signed authentically with OAuth 1.0.

If the request is not authentically signed, then the middleware responds with 401 Unauthorized, with the body a JSON object indicating errors encountered authenticating the request. The error object is structured like rails / ActiveResource:

{'errors' => {'attribute1' => ['messageA', 'messageB'], 'attribute2' => ['messageC']}}

Public Class Methods

new(app, options = {}) click to toggle source

options:

  • ‘:bypass` - a proc which will be called with a Rack::Request, which must have a boolean result. if the result is true, authentication checking is bypassed. if false, the request is authenticated and responds 401 if not authenticated.

  • ‘:config_methods` - a Module which defines necessary methods for an {OAuthenticator::SignedRequest} to determine if it is validly signed. See documentation for {OAuthenticator::ConfigMethods} for details of what this module must implement.

  • ‘:logger` - a Logger instance to which OAuthenticator::RackAuthenticator will log informative messages

  • ‘:realm` - 401 responses include a `WWW-Authenticate` with the realm set to the given value. default is an empty string.

# File lib/oauthenticator/rack_authenticator.rb, line 28
def initialize(app, options = {})
  @app = app
  @options = options
  unless @options[:config_methods].is_a?(Module)
    raise ArgumentError, "options[:config_methods] must be a Module"
  end
end

Public Instance Methods

call(env) click to toggle source

call the middleware!

# File lib/oauthenticator/rack_authenticator.rb, line 37
def call(env)
  request = Rack::Request.new(env)

  if @options[:bypass] && @options[:bypass].call(request)
    env["oauth.authenticated"] = false
    @app.call(env)
  else
    oauth_signed_request_class = OAuthenticator::SignedRequest.including_config(@options[:config_methods])
    oauth_request = oauth_signed_request_class.from_rack_request(request)
    if oauth_request.errors
      log_unauthenticated(env, oauth_request)
      unauthenticated_response(oauth_request.errors)
    else
      log_success(env, oauth_request)
      env["oauth.signed_request"] = oauth_request
      env["oauth.consumer_key"] = oauth_request.consumer_key
      env["oauth.token"] = oauth_request.token
      env["oauth.authenticated"] = true
      @app.call(env)
    end
  end
end

Private Instance Methods

log(level, message) click to toggle source
# File lib/oauthenticator/rack_authenticator.rb, line 97
def log(level, message)
  if @options[:logger]
    @options[:logger].send(level, message)
  end
end
log_success(env, oauth_request) click to toggle source

write a log entry for a successfully authenticated request

# File lib/oauthenticator/rack_authenticator.rb, line 93
def log_success(env, oauth_request)
  log :info, "OAuthenticator authenticated an authentic request with Authorization: #{env['HTTP_AUTHORIZATION']}"
end
log_unauthenticated(env, oauth_request) click to toggle source

write a log entry regarding an unauthenticated request

# File lib/oauthenticator/rack_authenticator.rb, line 86
def log_unauthenticated(env, oauth_request)
  log :warn, "OAuthenticator rejected a request:\n" +
    "\tAuthorization: #{env['HTTP_AUTHORIZATION']}\n" +
    "\tErrors: #{JSON.generate(oauth_request.errors)}"
end
unauthenticated_response(errors) click to toggle source

the response for an unauthenticated request. the argument will be a hash with the key ‘errors’, whose value is a hash with string keys indicating attributes with errors, and values being arrays of strings indicating error messages on the attribute key.

# File lib/oauthenticator/rack_authenticator.rb, line 65
def unauthenticated_response(errors)
  # default to a blank realm, I suppose
  realm = @options[:realm] || ''
  response_headers = {"WWW-Authenticate" => %Q(OAuth realm="#{realm}"), 'Content-Type' => 'application/json'}

  body = {'errors' => errors}
  error_message = begin
    error_values = errors.values.inject([], &:+)
    if error_values.size <= 1
      error_values.first
    else
      # sentencify with periods
      error_values.map { |v| v =~ /\.\s*\z/ ? v : v + '.' }.join(' ')
    end
  end
  body['error_message'] = error_message if error_message

  [401, response_headers, [JSON.pretty_generate(body)]]
end