MerbAuth - The Basics

November 05, 2008 | Author: hassox | Publisher: hassox

Merb auth is pretty new, pretty different and since it was made public at Merbcamp, pretty quiet. In this tutorial I’ll go through the basics of what it is and how it works.

What is MerbAuth?

MerbAuth is an authentication framework for your application. It’s not any particular implementation of authentication since a lot of applications have wildly different authentication requirements. So far as I know there’s no one true way to do authentication. MerbAuth does it’s best to provide an extremely flexible system that allows you to implement your required authentication scheme without getting in your way.

With MerbAuth it’s possible to consider a user to be any kind of ruby object. It could be an instance of class User, Person, Account, Staff, Student, Customer, Employee, String, Url, FileIO, Hash, Array. It could even be a class itself if you want. All these are possible to use in the one application. The only limit is that it must be able to be serialized in and out of the session in some fashion.

MerbAuth works on the idea of an authorized session. Once you’ve required authentication on an action or route, if you’re not already logged in the authorization will try to authenticate the session via cascading strategies. You declare a bunch of “Strategies” each one containing logic to authenticate in a given situation, but we’ll look at exactly what a strategy is in a bit. Each strategy is then executed in turn until either one returns a user object (success), or all fail (return false / nil) resulting in an Unauthenticated Exception.

MerbAuth has a couple of parts. merb-auth-core, merb-auth-more, and merb-auth-slice-password.

  • -core provides all the functionality you need in your app. Use just this if you need heavy customizations. This is the only required component of the merb-auth family.
  • -more provides some basic strategies, and some mixins you may find useful
  • -slice-password. This provides login and logout function. It is unfortunately poorly named, and would be better called merb-auth-slice-default since it does not actually require you to use a password. It is just the default. It is good for providing login / logout functions for any app however.

You don’t need to know all this to use it in the default configuration of a stack application. It sure helps though ;)

Failed Authentication

When authentication fails, an Unauthenticated exception is raised. This does two things due to merbs exception handling.

  1. Executes Exceptions#unauthenticated
  2. Sets the status to 401

You can put any explicit logic you like in the Exceptions#unauthenticated action. What’s special about this action really, is that this is where your login form lives. So to build your login form write your view in app/views/exceptions/unauthenticated.html.haml

What’s a Strategy?

A strategy is a class that inherits from Merb::Authentication::Strategy and has a run! instance method. That’s it. You can put any logic you like in it to ensure the session is authenticated. You get the request, and the router params made available to you in the strategy to help make the decision. Best to take a look I think.

module Merb::Authentication::Strategies
  class MyStrategy < Merb::Authentication::Strategy
    def run! 
       if params[:login]
         User.authenticate(request.params[:login], request.params[:password])
       end
    end
  end
end

Ok so the strategy itself is the class inside the module. Namespacing it inside Merb::Authentication::Strategies has benefits. You can refer to it as a string or symbol later if you namespace it to keep everything crisp in your app. When you declare a strategy, it’s added automatically to the list of strategies to try authentication with by default.

You can see the strategy is a very simple one. It just uses the :login and :password params to see if the use is authenticated. The if statement just prevents a db lookup if the requirements for this strategy to be a potential aren’t met.

If the strategy returns an object, no more strategies will be tried, and the returned object will be stored into the session. If it fails the next strategy will be tried until they’re all exhausted, at which time an Unauthenticated exception will be raised.

You can have as many strategies as you like for whatever different situations you may encounter.

What can I do in a Strategy

You can do quite a bit in a strategy. The main thing being the run! method, also you can redirect! to another url. This will halt the processing of strategies and redirect the browser to another url. Useful in openid and similar strategies.

You can also make a strategy abstract, meaning that it will not take part in authentication directly. Useful for creating a parent level strategy that you can inherit from. Great for code reuse in your app for similar strategies. Do this by declaring it abstract!

module Merb::Authentication::Strategies
  class MyStrategy < Merb::Authentication::Strategy
    abstract! # will not participate directly in authentication

    def run!
       # Authentication logic
    end   

    def some_helper_method
      #stuff
    end
end

You can stretch strategies any way you see fit. Since they’re just classes, they have all the flexibility of normal ruby classes. Go wild.

You can also return information to use in a rack array directly. I won’t go into it now… I’ll save that for a more advanced topic.

Error messages are also available within strategies. I’ll cover these in another tutorial.

What Strategies are Already Available

By default there are some strategies already implemented for merb-auth.

  1. Basic Authentication
  2. Login / Password authentication
  3. Open ID

Take a look at these in merb-auth-more for details.

Session Serialization

Flexible it is to be able to authenticate on any kind of object, even multiple different kinds of objects in your application. However, there is a price to pay. You need to tell MerbAuth how to serialize your chosen object(s).

Lets just take a look at how you’d serialize a DataMapper object in and out of the session.

  class Merb::Authentication

    def fetch_user(session_user_id)
      User.get(session_user_id)
    end

    def store_user(user)
      user.nil? ? user : user.id
    end
  end

The fetch_user method contains the logic to grab the user from the session. In this instance doing a lookup from the db. If it were a string or a hash, you would be able to just set it as a ruby object.

the store_user method provides the object to actually store into the session. In this case the id of the model. The session is available though you if needed you can manipulate the session directly. You may want to store the class of the object along with the key. For examle:


  class Merb::Authentication

    def fetch_user(session_info)
      klass, id = *session_info
      klass.get(session_user_id)
    end

    def store_user(user)
      id = user.nil? ? user : user.id
      [user.class, id]
    end
  end

This would allow you to use many different ORM style classes that have a key as the id. I’ll leave it up to you to get more creative.

How to use this in your Application

Using it in your application is pretty simple. Declare the strategies you want, setup serialization and protect your methods.

Setup your files for authentication in Merb.root/merb/merb-auth and they will be included automatically if you’re using merb-auth-core (which you kind of have to be ;)) .

Default Setup (MerbStack Apps)

When you generate an application with the default merb stack with merb-gen app my_app you get a lot of things for free. Generated so you can see it and tweak to you’re liking. Or just remove it if you don’t want it at all.

Checkout the setup for merb-auth in a default application in Merb.root/merb/merb-auth/setup.rb and Merb.root/merb/merb-auth/strategies.rb

One thing to notice is in the strategies.rb file. You’ll see this by default (for Merb 1.0)

Merb::Slices::config[:"merb-auth-slice-password"][:no_default_strategies] = true

Merb::Authentication.activate!(:default_password_form)
Merb::Authentication.activate!(:default_basic_auth)
The first line tell the password slice not to load any strategies. This means that it’s only used for the login/logout action. Any strategies you declare will be used instead of the default ones.

A lot of people have asked me about the activate! method. Do you need to activate strategies you declare yourself? Short answer… No… Long answer:

The activate! method just requires a file that has been registered. It’s used by plugins to register strategies without actually activating them. When you declare your own strategies though, they will just be activated when they’re declared. You can mix and match these to tweak the order that the authentication is run in. For example.

module Merb::Authentication::Strategies
  Merb::Authentication.activate!(:default_password_form)

  class MyStrategy < Merb::Authentication::Strategy
    def run! 
      #do_stuff
    end
  end

  Merb::Authentication.activate!(:default_basic_auth)
end

This will configure your authentication to run the Password, then MyStrategy, then BasicAuth strategies, in that order.

In the Router

You can ensure that the session is authenticated for your routes. The benefit of this is that for failed authentication you’re stopping the request a lot earlier. You’re nowhere near a mutex lock yet, there’s no controller instance or anything like that so you’re saving a fair amount of processing.

Lets take a look then:

Merb::Router.prepare do

  authenticate do
    resources :users
    resources :something
  end
end

This will run the default strategies when any of the :users resource or :something resource are accessed. Anything in the block will now require authentication.

Be careful with route based authentication. It’s possible to setup multiple routes that map to a single controller action. You can then just protect some of them.

You can specify a list of strategies to use in the call to authenticate. This will tell merb-auth which strategies you with to use to authenticate the user. Beware. It might not work how you think (more later)

Merb::Router.prepare do
  authenticate("Basic::BasicAuth") do
    resources :api
  end
end

This example shows requiring Basic Auth only as a login strategy. We can refer to it as a string because it’s namespaced in Merb::Authentication::Strategies. We could also refer directly to the class but that can get pretty long winded ;)

In Controllers

Sometimes, you need to protect individual controller actions, or, you want to make sure that no matter the route you want protection on a controller action, you’ll need to drop down to the controller level to protect it.

Here’s how:

class Posts < Application
  before :ensure_authenticated, :exclude => [:show, :index]

  #snip
end

This example shows authentication being required for all actions except :show and :index. It’s just like a normal before filter so you can use :index and :exclude.

Just like the router example, you can specify which authentication strategies to use for the before filter. Replicating the example from the router gives:

class Posts < Application
  before :ensure_authenticated, :exclude => [:show, :index], :with => ["Basic::BasicAuth"]

  #snip
end

Again though. This may not work how you think it does.

Caution. Authentication != Authorization

One thing that can potentially confuse people is the fact that you can specify which strategy to use as an action. Remember, remember, authorization is not authentication!

You might be tempted to do something like this (I know I have been)

before :ensure_authenticated, :with => ["Admin"]
That uses the “Admin” strategy to log you in right.. So we can check if you’re an admin in the strategy… Great!! No. It doesn’t mean that.

I consider specifying which strategies to use as an advanced topic. The reason being, if you’re already authenticated, you’re in. The strategies won’t be checked. The fact that you’re guarding the controller in this example with the “Admin” strategy will only make sure that people who are not yet logged in are Admin. Anyone who is already logged in will be let through.

I’m working on an authorization component to work with merb-auth(entication) that works in a similar manner but it’s not ready yet. If you want to make sure that only an admin uses a particular controller. Test for it in your controller. Authentication != Authorization. Seriously. That is the next logical step for this and is currently being developed.

This is the first tutorial in a bit of a series. I’m going to post at least one more, more advanced tutorial for merb-auth. Just remember, all this stuff is setup in a merb stack application and you only need to worry about it if you want to.

Comments

On November 07, 2008 at 11:34 nexneo says:

+1 for, Merb-Auth developer to generalize Authentication

On November 07, 2008 at 19:48 gsiener says:

Wow, this is really cool. You’ve got me thinking about how a lot of backend objects could authenticate themselves. Hmmm…

On November 30, 2008 at 10:00 mr_luc says:

Very nice.

For the past week, I thought that I was going to have to do a Python project, which isn’t a problem, but obviously I’d prefer ruby. So I put on my Serious Engineer hat and started digging into Pylons.

Turns out I don’t have to do the python project.

But I came away with a renewed appreciation for ruby, first of all, and merb, as a direct consequence of ruby’s awesomeness.

Pylons has AuthKit. It’s similar to merb-auth. It uses exceptional authentication, it’s wsgi middleware (like rack middleware), and you can use it in a pretty flexible way via decorators on actions, etc. However, it handles both authentication AND authorization, with defaults that let you (for instance) define a user, his password and his roles and optionally what “group” (exclusive) he belongs to; it has decorators for authorization on actions using those roles as well.

I didn’t prefer that default behavior. If I have two sub-apps that share identities, but in which a user could have different roles … but the roles in the two sub-apps share the same name, ie he’s an admin in one but not in another … meh. That’s something I’m more comfortable handling on my own, instead of building on the defaults pylons gives me.

On November 30, 2008 at 18:23 hassox says:

Hey @mr_luc thanx for your comments. I’ve been working on adding authorization to merb-auth and it should be ready pretty soon. It follows a similar idea and allows very granular, or very general authorization on your objects.

I’ll post a getting started tutorial when it’s done :D

On December 08, 2008 at 12:15 ernieboy says:

Merb auth is very nice. I have no problems using it in an application except when I use passenger and have a sub uri for the application. When the application runs in development with merb, things work great. when the application runs with passenger and apache and the application is the root on a virtual host. However when run as a sub uri, there is one strange behavior. I have added a path_prefix in init.rb. I have the correct statements in config.ru, including setting the prefix. When run as a sub uri the login form shows up. I log in the browser tries to go the controller where the ensure before statement is but with out the path prefix. The session is being created because I can use the url in the address bar, I’m taken there and everything goes along fine. All controllers and methods in the controllers are accessible. The only problem I have is with the after login not calling the correct url. I have tried changing the terms in the router.rb file for the call to slice. Inserting the path_prefix in each term individually and both terms together. None of that seems to work. Do you have any idea why this is happening?

Thanks, Ernie

Sign in to add your comment