Last updated by admin 5 years ago

Authorize Plugin

This is an idea (already mostly implemented) for a simple role-based authentication and authorization plugin that is a Grails-only plugin, meaning it does not incorporate an existing security framework such as Acegi or JSecurity. The JSecurity and Acegi plugins are capable and robust plugins, but they are more complex than I'm needing, and beforeInterceptor is not complex enough. The Authorize plugin falls somewhere in between.

The Authorize plugin requires minimal setup as will be documented here. At this time the plugin is still in development and this page serves as a snapshot of its current status, and the issues that still require resolution. Input from the Grails community would be greatly appreciated in this regard.

The plugin is currently only available via SVN at http://code.google.com/p/authorize/

Design

The basic design behind the Authorize plugin is currently quite simple but could certainly do with some feedback and input from you, the Grails community. So comments and suggestions are more than welcome.

Let's look at the two basic functions that an authentication framework provides: authentication (are you who you say you are?) and authorization (are you allowed to do what you want to do?) - and indicate how the Authorize plugin accomplishes these functions.

Authentication

Typically with Grails this is done using a beforeInterceptor. The Authorize plugin retains the use of the 'only' and 'except' action lists, as is used with the beforeInterceptor approach, but requires them to be declared statically as an 'authenticate' variable and enforces the action protection centrally, using the newly introduced Grails Filters.

Configuration

Configuration of your controller is done as follows:

  • a boolean
def static authenticate = true
This will enforce authentication for all closures.
  • an 'only' list
def static authenticate = [only:['index', 'list']]
This will enforce authentication only on the 'index' and 'list' actions
  • an 'except' list
def static authenticate = [except:['index', 'list']]
This will enforce authentication on all actions except for 'index' and 'list'

You can still make use of the beforeInterceptor, but you don't need to use it to enforce authentication in, say, a BaseController.

Authorization

Roles are usually easy to identify. Roles are also easy to associate with users. In addition to this, some systems also require the finer granularity of permissions. Authorize takes the approach that generally, permissions map quite well to controller actions. So what the Authorize plugin does is, at startup, map the controller actions to roles. The mapping is configured by the Grails application developer in the actual controller class. At startup, the Authorize plugin iterates through the controller classes, and introspects them for a statically declared 'protectedAccess' variable which indicates which roles should be applied to which actions. Enforcement of this association is once again done centrally, using the newly introduced Grails Filters.

At this time, the Authorize plugin reloads the association every time the application starts. This keeps the database in check with the code. At startup (and when the application reloads) the plugin deletes the previous associations and recreates them based on the current state of the controller classes. I don't see any issues with this currently. The relationships don't create any integrity violations, so deleting and recreating is doable. Or is there some issue I haven't spotted yet?
In addition to this, the Authorize plugin associates users with roles. Basic views are made available to associate a user with a role. This association can also be done programatically. So when your system creates a user, it can at that point also add a role to a user.

And that's basically it.

Configuration

As indicated previously, configuration is fairly simple. To link controller actions to roles at startup, simply declare a static authorize variable as follows:

  • a String
def static authorize = "admin"
This will map all actions to the 'admin' role.
  • a list
def static authorize = ["customer", "buyer"]
This will map all actions to the 'customer' and 'buyer' roles.
  • a map
def static authorize = [all:["admin"], list:["guest", "customer"]]
This will map all (note use of the word 'all' here) actions to the 'admin' role, and the 'list' action to the 'guest' and 'customer' roles.
While I have used Strings in the examples above, what I do is create a Roles class with static final Strings to represent the roles. I then use those constants in my 'authorize' declarations. This avoids the possibility of a spelling mistake. Can this design be improved?

What's in the bag?

So what is included in the Authorization plugin?

  • Security filters
    • authenticationCheck - checks whether a controller action requires a user to be logged in
    • authorizationCheck - checks whether a controller action is protected by a role and enforces any protection
  • Domain classes
It includes the following domain classes:
    • AuthUser
    • AuthRole
    • AuthControler (misspelt otherwise Grails thinks it is an actual controller class)
    • AuthAction (a controller action)
  • Controllers
Controller classes for the domain classes:
    • AuthUserController
    • AuthRoleController
    • AuthControlerController
    • AuthActionController

Defining Your Roles

Roles are defined in the Config.groovy class as follows: 

authorize {
    roles {
        "admin" {
            description = "Admin description here"
        }
        "customer" {
            description = "Customer description here"
        }
    }
}
Now if you're the pedantic type like me, you could also define a Roles class in your regular Java/Groovy code and import it in the Config class. This prevents any possibility of misspelling the role name and causing chaos. So it would look like this:
import com.myapp.account.Roles
authorize {
    roles {
        "${Roles.ADMIN}" {
            description = "Admin description here"
        }
        "${Roles.CUSTOMER" {
            description = "Customer description here"
        }
    }
}
This is a little uglier than simply hard-coding the role names, but buys you the spelling error protection.

Registration and Login

Registration

The plugin needs an approach that will allow the developer to customize the registration data required. Typical registration data requires (minimally) :

  • username
  • password
  • email address
  • first name
  • last name

Default Behaviour

I've taken an 'opinionated' approach to provide this functionality in Authorize while still giving you the freedom to customize the registration process if needed. So Authorize provides a default implementation as follows:

  • Authorize provides a RegisterController which will do a default registration requesting the following registration details.
    • 'username'
    • 'password'
    • 'passwordagain'
    • 'email'
    • 'firstname' (optional)
    • 'lastname' (optional)
  • As part of a successful registration, the RegisterController will:
    • create an AuthUser object
    • send an email to the registered email address requesting confirmation at a URL with the same domain as the application, and with a confirmation hash appended. The template for this email is in "/register/emails/_confirm.gsp". You can provide your own confirmation template by providing a _confirm.gsp in your own application views directory at "/views/register/emails/_confirm.gsp". If a custom confirm.gsp template is provided, be sure to specify a "confirmationUrl" variable somewhere in your template. This link is provided in the model to the template by the RegisterController.
  • Upon successful registration, the RegisterController will redirect the request to the root URI for the application using a 'redirect(uri:'/') and indicate the successful registration in the flash namespace.
  • If you would prefer a different success target, you can specify the following configuration parameters:
The RegisterController will redirect to your target and supply the AuthUser object as 'user' in the model.
  • A request initiated by a user clicking on the confirmation link will be received by RegisterController and their AuthUser account will be finalized. This account is then ready for login.
  • If there is a problem with the users registration, they can communicate with the system administrator who has the option of activating their account via the user details view in the account management views that come with the Authorize plugin.

Customizing The Registration

If your application requires additional form parameters to the default inputs outlined above, then it is best for you to handle the registration yourself. Initially I tried to incorporate custom registration details into Authorize by using controller action chaining, but the transaction management just became too messy. Nevertheless, handling the registration process yourself is really quite easy. You will basically need to do the following:

  • create an AuthUser object
  • handle the 'request confirmation' and 'registration confirmed' emails and
You can use the Authorize mail service, AuthEmailService, to send the confirmation email by declaring an AuthEmailService authEmailService reference in your registration controller. To send a mail you need to invoke the sendMail method in your code as follows:
class MyRegistrationController {
    AuthEmailService authEmailService
    def doRegister = {
        …
        authEmailService.sendMail(
        try {
            def emailContent = g.render(template: "/emails/registration", model: [user: user])
            emailService.sendMail("register@mydomain.com", user.email, "Please confirm your Acme registration", emailContent)
        } catch (Exception e) {
            // handle error here - probably return to input view
        }
    }
}
In your confirmation email, include a link for the user to click on to confirm their registration. This link should have the following format:
<domain name>/<context path>/register/confirm?uid=<user.hash>

e.g. http://www.acme.com/foo/register/confirm?uid=hYaJd3RnDT2SDCmhBDghfaMPWmY%3D

In the above example, I did have to URL-encode the hash first, using user.hash.encodeAsURL(). This is necessary because the hash is currently in Base64, so some of the characters don't work in a URL.

In the default Authorize registration code, I actually enclose the AuthUser creation and email sending code in an AuthUser.withTransaction block so that the user object is not created if the confirmation email can not be sent. You might want to follow this approach for sound transaction management of the registration process.

Login

Once a user is registered, they can then login. The Authorize plugin provides the following resources to manage the login process:

  • a LoginController to render a login view, to perform the actual login, and also a forgotPassword feature.
  • Custom tags to simplify and automate the work required in setting up your application for login purposes (more on that shortly)
The intention here is to provide similar functionality to what I saw (and liked) about the .NET User controls, which include a registration control, logged in user control, logged out user control and forgot password control. We've already covered the registration control - let's look at what would be needed for the other controls.

LoginController

The LoginController receives a request to login and renders a login view. The resulting login is then authenticated and the user is forwarded to, either the destination the user intended, or a 'login.success' view as indicated in the authorize configuration block. If no login.success view is provided, LoginController will redirect to the application main URL.

authorize {
    …
    login.success = [controller:'main', action:'index']
}

Custom Tags

<g:loginForm/> - This will render a form to be used for login that will target the LoginController's doLogin action. Alternative you can specify your own form but you must include 'username' and 'password' attributes. The form will also include a link for "Forgot My Password", which will take you to a form that allows you to enter your email address.

<g:loggedInUser/> - This will render a small outlined block that indicates "Welcome <username>. +Log Out+"

<g:loggedOutUser/> - This will render a small outlined block that indicates "You are not currently logged in. +Log In+"

&nbsp;<g:forgotPassword/> - This will render a form that allows the user to input their email address. When they click on Submit, the LoginController will then send them a reminder email with their password.

Association between Authorize's 'AuthUser' domain class and existing system user classes

Some systems may already have a 'User' class. So in order not to clash with this class, Authorize firstly names its user class "AuthUser" and then simply adds the AuthUser object to the session as 'session.authenticated'. So once a user has logged in, an application can retrieve the AuthUser object from the session if they need to. It should in most cases not be necessary since most of the work that would require access to the AuthUser object is already provided by Authorize.

Bringing It All Together

So, given all of that, this is an example of what an authorize configuration block *could* look like:

authorize {
    roles {
        "admin" {
            description = "Admin description here"
        }
        "customer" {
            description = "Customer description here"
        }
    }
    register.proxy = [controller:'account', action:'register']
    login.success = [controller:'main', action:'index']
}

Feedback

Okay, so this the first draft of the approach to this plugin. I may need to improve on the details here. Maybe some information is confusing. Maybe this plugin is not even interesting beyond my own application. At any rate, I'd appreciate any feedback, suggestions or criticism. Would this plugin be of use to anybody else? Please let me know.