n. A solid piece of metal code used to protect your application.
Why another authentication library?
- Because most of the other libraries are too huge.
- Extending other libraries is a pain.
- Writing code is fun :-).
What shield is
- Simple (~ 110 lines of Ruby code).
- Doesn't get in the way.
- Treats you like a grown up.
What shield is not
- is not a ready-made end-to-end authentication solution.
- is not biased towards any kind of ORM.
Understanding Shield in 15 minutes
Shield::Model is a very basic protocol for doing authentication
against your model. It doesn't assume a lot, apart from the following:
- You will implement
User.fetchwhich receives the login string.
- You have an attribute
crypted_passwordwhich is able to store up to 192 characters.
And that's it.
In order to implement the model protocol, you start by
class User < Struct.new(:email, :crypted_password) include Shield::Model def self.fetch(email) user = new(email) user.password = "pass1234" return user end end
Shield::Model, you get all the general methods needed
in order to do authentication.
- You get
User.authenticatewhich receives the login string and password as the two parameters.
- You get
User#password=which automatically converts the clear text password into a hashed form and assigns it into
u = User.new("email@example.com") # A password accessor has been added which manages `crypted_password`. u.password = "pass1234" Shield::Password.check("pass1234", u.crypted_password) # => true # Since we've hard coded all passwords to pass1234 # we're able to authenticate properly. nil == User.authenticate("firstname.lastname@example.org", "pass1234") # => false # If we try a different password on the other hand, # we get `nil`. nil == User.authenticate("email@example.com", "wrong") # => true
To make Shield work with any ORM, make sure that an
. method which
fetches the user instance by id is implemented.
class User include Shield::Model # ... def self.(id) get id end end
Logging in with an email and username?
If your requirements dictate that you need to be able to support logging
in using either username or email, then you can simply extend
a bit by doing:
# in Sequel (http://sequel.rubyforge.org) class User < Sequel::Model include Shield::Model def self.fetch(identifier) filter(email: identifier).first || filter(username: identifier).first end end # in Ohm (http://ohm.keyvalue.org) class User < Ohm::Model include Shield::Model attribute :email attribute :username unique :email unique :username def self.fetch(identifier) with(:email, identifier) || with(:username, identifier) end end
If you want to allow case-insensitive logins for some reason, you can simply normalize the values to their lowercase form.
As the name suggests,
Shield::Helpers is out there to aid you a bit,
but this time it aids you in the context of your Rack application.
Shield::Helpers assumes only the following:
- You have included in your application a Session handler, (e.g. Rack::Session::Cookie)
- You have an
envmethod which returns the environment hash as was passed in Rack.
Note: As of this writing, Sinatra, Cuba & Rails adhere to having an
method in the handler / controller context. Shield also ships with tests for
both Cuba and Sinatra.
require "sinatra" # Satisfies assumption number 1 above. use Rack::Session::Cookie # Mixes `Shield::Helpers` into your routes context. helpers Shield::Helpers get "/private" do error(401) unless authenticated(User) "Private" end get "/login" do erb :login end post "/login" do if login(User, params[:login], params[:password]) remember(authenticated(User)) if params[:remember_me] redirect(params[:return] || "/") else redirect "/login" end end get "/logout" do logout(User) redirect "/" end __END__ @@ login <h1>Login</h1> <form action='/login' method='post'> <input type='text' name='login' placeholder='Email'> <input type='password' name='password' placeholder='Password'> <input type='submit' name='proceed' value='Login'>
Note for the reader: The redirect to
params[:return] in the example
is vulnerable to URL hijacking. You can whitelist redirectable urls, or
simply make sure the URL matches the pattern
If you have a keen eye you might have noticed that instead of redirecting
away to the login URL in the example above, we instead chose to do a
401 Unauthorized. In strict HTTP Status code terms, this is the proper
approach. The redirection is simply the user experience pattern that has
emerged in web applications.
But don't despair! If you want to do redirects simply add
Shield::Middleware to your middleware stack like so:
# taken from example above use Shield::Middleware, "/login" use Rack::Session::Cookie # rest of code follows here # ...
Now when your application responds with a
will be responsible for doing the redirect to
If you try and do a
curl --head http://localhost:4567/private with
Shield::Middleware, you'll get a response similar to the following:
HTTP/1.1 302 Found Location: http://localhost:4567/login?return=%2Fprivate Content-Type: text/html
Notice that it specifies
/private as the return URL.
You can install it using rubygems:
gem install shield