Ejemplo: Basic Authentication con Rack::Auth::Basic

Rack::Auth::Basic implements HTTP Basic Authentication, as per RFC 2617.

Introducción

  1. In the context of an HTTP transaction, basic access authentication is a method for an HTTP user agent to provide a user name and password when making a request.

  2. HTTP Basic authentication (BA) implementation is the simplest technique for enforcing access controls to web resources because it doesn't require cookies, session identifier and login pages. Rather, HTTP Basic authentication uses static, standard HTTP headers which means that no handshakes have to be done in anticipation.

  3. The BA mechanism provides no confidentiality protection for the transmitted credentials. They are merely encoded with BASE64 in transit, but not encrypted or hashed in any way. Basic Authentication is, therefore, typically used over HTTPS.

  4. Because BA header has to be sent with each HTTP request, the web browser needs to cache the credentials for a reasonable period to avoid constant prompting user for the username and password. Caching policy differs between browsers.

  5. While HTTP does not provide a method for web server to instruct the browser to "log out" the user (forget cached credentials), there are a number of workarounds using specific features in various browsers. One of them is redirecting the user to an URL on the same domain containing credentials that are intentionally incorrect

Protocolo

  1. When the server wants the user agent to authenticate itself towards the server, it can send a request for authentication.
  2. This request should be sent using the HTTP 401 Not Authorized response code containing a WWW-Authenticate HTTP header.
  3. The WWW-Authenticate header for basic authentication (used most often) is constructed as following:
    WWW-Authenticate: Basic realm="insert realm"
    
  4. When the user agent wants to send the server authentication credentials it may use the Authorization header.
  5. The Authorization header is constructed as follows:
    1. Username and password are combined into a string username:password
    2. The resulting string literal is then encoded using Base64
    3. The authorization method and a space i.e. Basic is then put before the encoded string.
    4. For example, if the user agent uses Aladdin as the username and open sesame as the password then the header is formed as follows:.
        Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
      

Ejemplo de BA en Rack

Initialize with the Rack application that you want protecting, and a block that checks if a username and password pair are valid.

Puede encontrar el fuente en GitHub

[~/local/src/ruby/sinatra/rack/rack-lobster(master)]$ cat protectedlobster.rb
require 'rack'
require './lobster'
require 'yaml'

lobster = Rack::Lobster.new

passwd = YAML.load(File.open('etc/passwd.yml').read)

protected_lobster = Rack::Auth::Basic.new(lobster) do |username, password|
  passwd[username] == password
end

protected_lobster.realm = 'Lobster 2.0'
pretty_protected_lobster = Rack::ShowStatus.new(Rack::ShowExceptions.new(protected_lobster))

Rack::Server.start :app => pretty_protected_lobster, :Port => 9292


lobster.rb

[~/local/src/ruby/sinatra/rack/rack-lobster(master)]$ cat lobster.rb 
require 'rack/request'
require 'rack/response'

module Rack
  class Lobster
    LobsterString = "a lobster"

    def call(env)
      req = Request.new(env)

      req.env.keys.sort.each { |x| puts "#{x} => #{req.env[x]}" }

      if req.GET["flip"] == "left"
        lobster = LobsterString.reverse
        href = "?flip=right"
      elsif req.GET["flip"] == "crash"
        raise "Lobster crashed"
      else
        lobster = LobsterString
        href = "?flip=left"
      end

      res = Response.new
      res.write <<-"EOS"
      <title>Lobstericious!</title>
      <pre>
      #{lobster}
      </pre>
      <p><a href='#{href}'>flip!</a></p>
      <p><a href='?flip=crash'>crash!</a></p>
      EOS
      res.finish
    end
  end
end

if $0 == __FILE__
  require 'rack'
  require 'rack/showexceptions'
  Rack::Server.start(
    :app => Rack::ShowExceptions.new(
              Rack::Lint.new(
                Rack::Lobster.new)), 
    :Port => 9292,
    :server => 'thin'
  )
end

etc/passwd.yml

[~/local/src/ruby/sinatra/rack/rack-lobster(master)]$ cat etc/passwd.yml 
--- # Indented Block
   casiano: tutu
   ana: titi

Rakefile

[~/local/src/ruby/sinatra/rack/rack-lobster(master)]$ cat Rakefile
...

desc "run the server for protectedlobster"
task :protected do
  sh "ruby protectedlobster.rb"
end

desc "run the client with user and password flip left"
task :protectedleft do
  sh %q{curl -v --basic -u casiano:tutu 'http://localhost:9292?flip=left'}
end

...

task :crash do
  sh %q{curl -v 'http://localhost:9292/?flip=crash'}
end

Ejecución

  1. Servidor:
    [~/local/src/ruby/sinatra/rack/rack-lobster(master)]$ rake protected
    ruby protectedlobster.rb
    >> Thin web server (v1.5.1 codename Straight Razor)
    >> Maximum connections set to 1024
    >> Listening on 0.0.0.0:9292, CTRL+C to stop
    
  2. Cliente:
    [~/local/src/ruby/sinatra/rack/rack-lobster(master)]$ rake protectedleft
    curl -v --basic -u casiano:tutu 'http://localhost:9292?flip=left'
    * About to connect() to localhost port 9292 (#0)
    *   Trying ::1... Connection refused
    *   Trying 127.0.0.1... connected
    * Connected to localhost (127.0.0.1) port 9292 (#0)
    * Server auth using Basic with user 'casiano'
    > GET /?flip=left HTTP/1.1
    > Authorization: Basic Y2FzaWFubzpzZWNyZXRv
    > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8y zlib/1.2.5
    > Host: localhost:9292
    > Accept: */*
    > 
    < HTTP/1.1 200 OK
    < Content-Length: 168
    < Connection: keep-alive
    < Server: thin 1.5.1 codename Straight Razor
    < 
          <title>Lobstericious!</title>
          <pre>
          retsbol a
          </pre>
          <p><a href='?flip=right'>flip!</a></p>
          <p><a href='?flip=crash'>crash!</a></p>
    * Connection #0 to host localhost left intact
    * Closing connection #0
    
  3. Servidor después de la petición:
    [~/local/src/ruby/sinatra/rack/rack-lobster(master)]$ rake protected
    ruby protectedlobster.rb
    >> Thin web server (v1.5.1 codename Straight Razor)
    >> Maximum connections set to 1024
    >> Listening on 0.0.0.0:9292, CTRL+C to stop
    ...
    HTTP_AUTHORIZATION => Basic Y2FzaWFubzp0dXR1
    REMOTE_USER => casiano
    ...
    

Autentificación Básica: vista en el navegador

Si pulsamos "cancel" obtenemos:

Véase

  1. Código de rack-lobster en GitHub
  2. Código fuente de Rack::Auth::Basic
  3. Documentación en Rack::Auth::Basic
  4. La Wikipedia Basic Access Authentication



Subsecciones
Casiano Rodriguez León 2015-01-07