merb-auth-core
MerbAuth is an authentication framework for use with the Merb web framework.
MerbAuth does not try to dictate what you should use as a user model, or how it should authenticate. Instead it focuses on the logic required to check that an object passes authentication, and store the keys of authenticated objects in the session. This is in fact the guiding principle of MerbAuth. The Session is used as the place for authentication, with a sprinkling of controller helpers. You can choose to protect a controller action, or a route / group of routes. This makes sense to talk about an authenticated session. For example, inside your controller:
- session.authenticated? returns true if the session has been authenticated. False otherwise # session.authenticate(controller) authenticates the session based on customizable user defined rules
- session.user returns the currently authenticated user object
- session.user= manually sets the currently authenticated user object
- session.abandon! sets the session to unauthenticated, and clears all session data
- session.authenticate! authenticates the session against the active strategies
MerbAuth makes use of Merb’s exception handling facilities which return correct HTTP status codes when a 200 OK would be inappropriate. To fail a login, or to force a login at any point in your controller code, simply raise an Unauthenticated exception, with an optional message and the user will be presented with login page. The login page is in fact the html view for Extensions#unauthenticated
To protect your controllers, add a simple before
filter to your controller.
before :ensure_authenticated
It is possible to use MerbAuth with any object as a user object, provided that object does not evaluate to false and it can be serialized in an out of the session. For this reason, merb-auth-core does not try to implement even a simple login form for you, since it may not meet your requirements.
How Does It Authenticate my arbitrary user?
This is very similar to the BootLoader process in Merbs initialization.
You declare a class that inherits from Merb::Authentication::Strategy and
define an instance method run!
class PasswordStrategy < Merb::Authentication::Strategy
def run!
if params[:login] && params[:password]
user_class.authenticate(params[:login], params[:password])
end
end
end
This login strategy uses the
authenticate
method on the User class to retrieve a user bylogin
andpassword
. Remember, you can put as much logic here as you require. The strategy uses an instance variable so the full power of classes are available to your strategy.
The strategy provides access to the current request giving you access to the params hash, session etc.
To pass authentication, simply return a non-nil
non-false value from the run!
method. Any false or nil value will cause
that strategy to fail. Then the next strategy will be tried.
You can add as many strategies as you like and they will be tried one after another until either one is found that works (login), or none of them have passed (failed attempt).
class PasswordLoginBasicAuth < Merb::Authentication::Strategy
def run!
if params[:api_key] && params[:api_token]
Machine.api_authenticate(params[:api_key], params[:api_token])
end
end
end
Now that we have two, they will be executed in the order that they were declared
when we call session.authenticate!(self)
. The first one that
returns a value that doesn’t evaluate to false, will be considered the winner.
Customizing the user_class
Notice the user_class
method in the above strategy examples. This is a convenience method on a strategy
to provide you with the user_class to use for this strategy. You can overwrite
this method on a per strategy basis to use different user model types. You do not have to use this method
and it’s only there to keep track of the “default” user class. (if any)
By default the strategy#user_class method will defer to Merb::Authentication#user_class. You can set which is the “default class” that Merb::Authentication will use in the provided strategies by setting it in Merb.root/merb/merb-auth/setup.rb
Merb::Authentication.user_class = Person
This will cascade throughout the default strategies, and your own strategies using the user class that you defined. In this case Person.
There is no default class set for Merb::Authentication.user_class by default
Strategies and Inheritance
Strategies may be inherited multiple times to make the job of combining similar aspects easier. You can inherit as many levels as you like and at any point you may mark a strategy as abstract
An abstract strategy just means that it will not be run when it comes time to authenticate. Instead it’s good to put common logic in and then inherit from it to keep your strategies DRY.
To mark a class as abstract, use the abstract!
class method.
class AbstractStrategy < Merb::Authentication::Strategy
abstract!
end
At any point you can activate a registered strategy. You don’t need to register your strategies, you just declare them, but plugin developers make life easier when they do.
To activate a registered strategy:
Merb::Authentication.activate!(:defualt_password_form)
You can easily mix this in with your own strategies. In you Merb.root/merb-auth/strategies.rb
class MyStrategy < Merb::Authentication::Strategy
def run!
#...
end
end</p>
Merb::Authentication.activate!(:default_openid)
class AnotherStrategy < Merb::Authentication::Strategy
def run!
#…
end
end
<p>
This will collect them in order of declaration. i.e.: MyStrategy, Merb::Authentication::Strategies::Basic::OpenID, AnotherStrategy
Customizing the order of the strategies
By default, strategies are run in the order they are declared. It’s possible to customize the order that the strategies are called.
Merb::Authentication.default_strategy_order
will return an array of
the strategy classes in the order that they will be run.
You can customize this by setting the default_strategy_order array
manually.
Authenticateion.default_strategy_order.order = [Second, First, Fourth]
It’s possible to leave some out, and re-order existing ones. It will error out if you specify one that doesn’t exist though.
Authentication based on Routes
With MerbAuth you can protect routes rather than individual actions on a controller. The benefit with doing this is that the request is stopped earlier in the request process. This is a more efficient method of protection than controller based protection.
The downside is that a controllers action is not guaranteed to be protected. For example
authenticate do
match("/one").to(:controller => "one", :action => "index")
end
match("/two").to(:controller => "one", :action => "index")
The /one route is protected, but you can see that both of these routes point to the same controller action. If the One#index method is accessed through the /two route, there is no protection. You can specify which strategies to use as arguments to the authenticate method. The default strategies are used if there is no argument given.
Specifying selected strategies per action
If you need to protect an action regardless of which route leads to it, you can use
controller level protection. Use a before :ensure_authenticated
filter to protect actions
in your controller whenever the are accessed.
It’s possible to configure each call to ensure_authenticated
with a custom list
of strategies to run. These will be run in order and should have an instance method
of #run!
class ApiMethods < Application
before :ensure_authenticated, :with => [
Merb::Authenticated::Strategies::Basic::Form,
Merb::Authenticated::Strategies::Basic::BasicAuth,
Merb::Authenticated::Strategies::Basic::OpenID,
],
:only => [:index]
before :machine_only, :only => [:create]
def index
display @stuff
end
def create
stuff = Stuff.create(params[:stuff])
display stuff
end
private
def machine_only
ensure_authentiated Merb::Authenticated::Strategies::Basic::OAuth, Merb::Authenticated::Strategies::Basic::BasicAuth
end
end
You can see in this example that you can specify a list of strategies to use. These will be executed in the order of the array passed in, with the default order ignored completely.
Where should Strategies be defined?
You should store your strategies in
merb
`-- merb-auth
|-- setup.rb
`-- strategies.rb
This is a good place to put everything together so you can see what you’re doing at a glance. It is also auto included by merb-auth-core when you’re using it.
What Strategies are there?
See merb-auth-more
Storing you user object into the session
You need to tell MerbAuth how to serialize your object into and out of the session. If possible try not to store large or complex data items in the session but just store the objects key.
To configure your user object to go in and out of the session, here’s how you could do it.
class Merb::Authentication
# return the value you want stored in the session
def store_user(user)
return nil unless user
user.id
end
# session info is the data you stored in the session previously
def fetch_user(session_info)
User.get(session_info)
end
end
Registering Strategies
Intended for plugin developers as a way to make it easy to use strategies there is the possibility to register a strategy without loading it.
Authentication.register(:my_strategy, "/absolute/path/to/strategy.rb")
This then allows developers to use
Authentication.activate!(:my_strategy)
Providing feedback to users (Error Messages)
There’s at least 4 ways to provide feedback to users for failed logins.
- Overwrite Merb::Authentication#error_message The return of this method is the default message that is passed to the Unauthenticated exception. Overwrite this to provide a very basic catch all message.
- Provide a default message when you declare your before filter.
When you pass a message, it will replace the Merb::Authentication#error_message default for this actionbefore :ensure_authenticated, :with => [Openid, :message => "Could not log you in with open ID"]</li> <li>OR before :ensure_authentication, :with => {:message => “Sorry Buddy… You Lose”}
- Use an after filter for your login action. This can be used to set your messaging system. For example:
after :set_login_message, :only => [:create]</li> </ul> private def set_login_message if session.authenticated? flash[:message] = “Welcome” else flash[:error] = “Bad.. You Fail” end end
- Use the authentications error messaging inside your strategies to set error messages there. You can add to these errors just like adding to DataMappers validation errors.
Add as many as you like, asksession.authentication.errors.add(“Label”, “You Fail”)
session.authentication.errors.on(:label)
to get specific errors etc Really… They’re just like the DataMapper validation errors. The bonus of using this system is that you can add messages inside your Strategies, and then in your views you can do this:<%= error_messages_for sessions.authentication %>
Additional checks / actions to perform after the user is found
Sometimes you may need to perform additional operations on the user object after you have found a valid user in the strategy. There is a hook method Merb::Authentication.after_authentication which is designed for this.
Here’s an example of checking that a user object is active after it’s been found:
Merb::Authentication.after_authentication do |user, request, params| user.active? ? user : nil endPass the user model on if everything is still ok. Return nil if you decide in the after_authentication hook that the user should in fact not be allowed to be authenticated.
By default this plugin doesn’t actually authenticate anything ;) It’s up to you to get your model going, and add an authentication strategy.
To logout use
session.abandon!
and to force a login at any time useraise Unauthenticated, "You Aren't Cool Enough"
Contributors
- Adam French – http://adam.speaksoutofturn.com/
- Daniel Neighman – http://merbunity.com
- Ben Burket – http://benburkert.com/