Added by Ross Vandegrift, last edited by Ross Vandegrift on Aug 20, 2010  (view change)

Labels:

Enter labels to add to this page:
Wait Image 
Looking for a label? Just start typing.

There was some talk on the list about homegrown authentication and authorization systems, so I thought I'd draft up a bit on how I do it. The basic idea is this:

  • for interactive users, at authentication time, configure the user's session object so all A&A requests can just look at the session.
  • for API access, the user must provide an API key and application name with each request.

In my case, interactive user logins are authenticated by a RubyCAS server and authorization is granted by looking up specific attributes in an LDAP directory. API keys are stored locally with an authorization level.

My A&A is decorator based. The app only adds a "username" key to the session object if the authentication completes. So first thing, we have a decorator that looks for a valid username or an API key for automated access:

@decorator
def authenticated(fn, *a, **kw):
    '''decorator to redirect to login if user's session isn't valid'''
    if session.has_key('username'):
        return fn(*a, **kw)
    elif request.params.has_key('apiname') and request.params.has_key('apikey'):
        k = meta.session.query(model.ApiKey)
        apiname = request.params['apiname']
        apikey = sha.new(request.params['apikey']).hexdigest()
        try:
            key = k.filter_by(name=apiname, key=apikey).one()
        except:
            abort(403)
        return fn(*a, **kw)
    else:
        return redirect_to('/auth')

The app adds session keys according to the authorization stored, so the second thing we need is a decorator that looks at the session authorization and compares it to an argument.

def authorized(*perms, **keywords):
    '''decorator to redirect if a user's session isn't authorized'''
    def _authorized(fn, *a, **kw):
        if session.has_key('username'):
            for p in perms:
                if session.has_key(p):
                    return fn(*a, **kw)

            if not keywords.has_key('error'):
                keywords['error'] = "You are not authorized to access that portion of the application."
            session['mesg'] = keywords['error']
            session.save()
            return redirect_to('/home')
        elif request.params.has_key('apiname') and request.params.has_key('apikey'):
            k = meta.session.query(model.ApiKey)
            apiname = request.params['apiname']
            apikey = sha.new(requst.params['apikey']).hexdigest()
            try:
                key = k.filter_by(name=apiname, key=apikey).one()
            except:
                abort(403)
            if key.authorization in perms:
                return fn(*a, **kw)
            else:
                abort(403)
    return decorator(_authorized)

You might need to adjust these based on how you return errors to the user and how you store A&A information.

Finally, to use these, we can stack them up on controller actions:

class SomeController(BaseController):
    def public(self):
        '''This stuff can be seen by anyone without authenticating'''

    @authenticated
    def index(self):
         '''This stuff can be seen by any authenticated user.'''

    @authenticated
    @authorized("admin")
    def secret(self):
        '''If your session doesn't have the key "admin", you'll get kicked out
        with the generic error message "You are not authorized to access that
        portion of the application."

    @authenticated
    @authorized("admin", error="Only admins can blow stuff up!")
    def explode(self):
        '''If your session doesn't have the key "admin" then you'll get kicked
        out with the specified error message above.'''

User storage for the API key looks exactly like you think - it has the app name, hashed key, and a field for the authorization level.

You could have saved time using AuthKit authorization's decorators. Reinventing the wheel
is good for learning, but for production you'll want for sure to have a authentication framework
that is build on solid foundations.

Comment: Posted by Anonymous at Aug 23, 2010 11:39 | Reply To This