Authentication Plugin

  • Authors: Marc Palmer
5 votes
Dependency:
compile ":authentication:2.0.1"

 Source  Issues

Summary

Description

Authentication Plugin

Overview

This authentication plugin provides extensible mechanisms that are configured to work out of the box.

This plugin includes support for account signup and for account confirmation (checking of email address etc).

Authentication is done many different ways in different applications, for example:

  • Some sites have a user id separate from email address
  • Some sites require extra fields during registration, such as opt-ins and so on
  • Some sites don't allow signup (account creation) and instead read from an existing user database/LDAP
  • Some sites permit immediate login after signup, others don't
This plugin is flexible enough to support all of these, but out of the box with no configuration it provides a development-mode example login/signup form ready to use, and encodes passwords with MD5 hashing in the database.

The AuthenticationController supplies an /index action only in development modes. In production it will NOT include this. This is because you do not typically use the supplied view or the authentication controller to show your login page, you embed the auth tags in other views of your site to provide login and signup forms.

It uses events to customize behaviour, supports signup including two-phase signup where you integrate with email address confirmation services, as well as being domain-neutral so you can use the default domain class for users, or your own - or something that is not a Grails domain class at all, i.e. LDAP etc.

Password encoding and password strength hooks are supplied - nothing is fixed. Password reset/reminder hooks are also provided.

Taglibs are supplied to make it easier to render authentication forms, without using the HTML page to determine the full target url for success or failure, to avoid XSS attacks and abuse. There are also tags for rendering user login status and information, and conditional information based on login status.

Commercial Support

Commercial support is available for this and other Grailsrocks plugins.

Using it to add simple signup and alter behaviour based on whether a user is logged in

  1. Install the Authentication plugin
grails install-plugin authentication

If you are using Grails 1.0.2 you will have to manually copy the files from plugins/authentication-1.0/grails-app/views/authentication to your application's grails-app/views/authentication folder. This is due to a bug in Grails 1.0.2. This step is not required if you do not intend to use the default views, which are for example only.

2. Run grails run-app and browser to http://localhost:8080/<yourapp>/authentication

3. Use the sample (development-mode only) sign up form there to create an account and you will be logged in.

4. Use the AuthenticationService.isLoggedIn(request) method to see if the user has a valid login:

class YourController {
   def authenticationService

def onlyLoggedInUsers = { if (!authenticationService.isLoggedIn(request)) { // Redirect or return Forbidden response.sendError(403) } else { … do something } } } }

You can also do this from filters or interceptors or other code e.g:
class YourFilters {
    static nonAuthenticatedActions = [
        [controller:'authentication', action:'*']
    ]

def filters = { accessFilter(controller:'*', action:'*') { before = { boolean needsAuth = !nonAuthenticatedActions.find { (it.controller == controllerName) && ((it.action == '*') || (it.action == actionName)) } if (needsAuth) { return applicationContext.authenticationService.filterRequest( request, response, "${request.contextPath}/authentication/index" ) } else return true } } } }

...or you can check they are logged in and authorized to access the resource using the filterRequest helper method which defers to the on onCheckAuthorized event and calls onUnauthorizedAccess (see Recipes section)

5. Use the GSP tags auth:form and auth:ifNotLoggedIn etc to create your own login and signup forms, and assign a custom event handler object, or override individual events, to change the behaviour.

By default the plugin will use GORM for the user accounts, and will store passwords using an MD5 one-way hash. The default AuthenticationUser domain class is minimal. If you want to change constraints or add fields (you may consider using a separate class instead for extra user data) you just redefine the onNewUserObject event and return your own instance of a domain class or similar wrapper around another authentication database such as LDAP

Once you have the basics working you will want to add your own forms etc to customize the behaviour.

Reference

Controlling what happens after authentication actions

The authentication controller uses redirects after performing its work, and extracts request parameters that determine the parameters to pass to redirect() when the action succeeds or fails. The easiest way to set these is to use the supplied taglibs and their success/error attributes - the values are passed as redirect(thevalues).

e.g:

<auth:form authAction="signup" success="[controller:'portal', action:'newUser']"
    error="[controller:'portal', action:'signup']">
…
</auth:form>
These are always relative to the application for security reasons, and the taglibs extract controller, action and id.

You are able to supply any redirect() parameters you like if you use the alternative method of specifying the map of values to use as flash variables authSuccessURL and authErrorURL - which need to be set in the action that returns the page containing the form, so that they are available for the next request when authentication is atempted. These flash variables will be used in preference to request parameters, and this allows redirects to absolute urls or other more exotic redirect options.

Events

The list of events currently supported and their default behaviour is:

Called to validate the user's chosen login name, i.e. is it too short or in use? Return true if valid

onValidateLogin:{ loginID -> true }
Called to validate the user's password, i.e. is it long enough/strong enough. Return true if valid
onValidatePassword: { password -> true }
Called to encode the user's password prior to saving it, i.e. return digested/XOR'd password Defaults to MD5 hash, i.e. NO clear text in the database, but also no password reminders possible!
onEncodePassword: { password -> password?.encodeAsMD5Hex() }
Called to load the user object by login id, must retun the user object or null if not found
onFindByLogin:{ loginID -> AuthenticationUser.findByLogin(loginID) }
Called when a new user object is required, object returned must have login, password, email and status properties
onNewUserObject: { loginID -> def obj = AuthenticationUser.newInstance(); obj.login = loginID; return obj }
Called when a user object has been changed and needs to be saved This impl assumes its a GORM object
onSaveUser: { user -> user.save() }
Called when a user has logged in
onLoggedIn: { AuthenticatedUser login -> }
Called when a user has logged out
onLoggedOut: { AuthenticatedUser login -> }
Called on successful signup, although email may not be confirmed yet - params are the request (form) params
onSignup: { params -> }
Called when an account has to be deleted, passing in the user object supplied by onNewUserObject
onDelete: { user -> user.delete() }
Called to see if email confirmation is required, return true if user cannot log in yet until confirmed
onConfirmAccount: { user -> }
Called to see if a logged in user can access the requested resource (called by filterRequest) params has properties: request, user, controllerName, actionName
onCheckAuthorized: { params -> true }
Called after a denied access (called by filterRequest) params has properties: request, response
onUnauthorizedAccess: { params -> params.response.sendError(403) }

Error handling

The authentication controller will put an object called authenticationFailure into flash scope in the event of a problem with an authentication action such as signup or login. This object is of type authentication.AuthenticatedUser which has several properties relating to the login attempt, as well as the "result" property which is a code relating to one of the ERROR_XXX constants or other result code on that class:

static final ERROR_NO_SUCH_LOGIN = 1
static final ERROR_INCORRECT_CREDENTIALS = 2
static final ERROR_LOGIN_NAME_NOT_AVAILABLE = 3
static final AWAITING_CONFIRMATION = 4 // indicates account is created but awaiting user confirmation

Customizing behaviour

The authenticationService exposes an "events" property which supplies the default event handlers as a map of closures. You can set a new value for any of the events in BootStrap or at runtime for example in an afterPropertiesSet() implementation.

Alternatively you can pass in a new events object - map or otherwise - in the "authenticationEvents" configuration variable in Config.groovy

Codecs

The plugin supplies a HexCodec, MD5Codec and MD5HexCodec for utility purposes, particularly for hashing passwords stored in the database.

Recipes

Creating users programmatically

For test data or otherwise, you can just create an instance of the domain class used for the user. By default this is AuthenticationUser:

assert new AuthenticationUser( login:'someone', password:'secret'.encodeAsMD5(), email:'someone@somewhere.com', 
    status:AuthenticationService.STATUS_VALID).save()

Getting the login id of the currently logged in user

Typically you need to access this information from a controller. There is a taglib (detailed later in this documentation) that you can invoke from a controller:

class MyController {
    def index = { 
       render (auth.user() == 'marc' ? "Hello Marc" : 'Who are you?') }
}

Creating a signup form

You can use any form to submit an signup request, it just needs to have at a minimum the following fields:

  • login
  • password
  • passwordConfirm
You also need to tell the controller where it should redirect to in the case of success or failure of the signup. You can do this either with flash or with hidden parameters in the form. For the sake of simplicity the <auth:form> tag is provided which handles all of this for you.
<g:if test="${flash.authenticationFailure}">
	Login failed: ${message(code:"authentication.failure."+flash.authenticationFailure.result).encodeAsHTML()}
</g:if>
<auth:form authAction="signup" success="[controller:'portal', action:'newUser']" error="[controller:'portal', action:'signup']">
    User: <g:textField name="login"/><br/>
    Password: <input type="password" name="password"/><br/>
    Confirm Password: <input type="password" name="passwordConfirm"/><br/>
    <input type="submit" value="Create account"/>
</auth:form>
You can of course add your own extra fields.

Creating a login form

Much the same as signup, you use auth:form:

<g:if test="${flash.authenticationFailure}">
	Login failed: ${message(code:"authentication.failure."+flash.authenticationFailure.result).encodeAsHTML()}
</g:if>
<auth:form authAction="login" success="[controller:'admin', action:'index']" error="[controller:'admin', action:'loginError']">
    User: <g:textField name="login"/><br/>
    Password: <input type="password" name="password"/><br/>
    <input type="submit" value="Log in"/>
</auth:form>

Restricting access using Filters

A helper method is supplied to establish whether or not the user is logged in, which can be called from a Filter:

class MyFilters {
    def filters = {
	adminFilter(uri:"/admin/**") {
		// Redirect to login if not logged in
		before = {
		    if (actionName != "login") {
		        return applicationContext.authenticationService.filterRequest( request,
		            response, "${request.contextPath}/admin/login" )
	        } else return true
	    }
	}
    }
}
The third parameter to filterRequest is the relative URL (on the same server) to redirect to if the user is not logged in.

This method will return true if the user is already logged in, and allowed access to the resource they requested.

Authorization to access the resource is granted or denied by the implementation of your onCheckAuthorized event handler, which is passed the request and user (AuthenticatedUser) objects. onUnauthorizedAccess is called if access is denied, and is passed the request and response, typically so that it can call response.sendError(403). Once onUnauthorizedAccess has been called, filterRequest will always return false.

Changing user login/password constraints / using a custom domain class or other backing store

The default AuthenticationUser domain class is simple and defers to events to validate the login and password, in terms of them being safe/valid values to be saved to the database. Usually this class will be enough and you can simply add any extra information required about your user to your own domain classes that have a property that matches the login string of the user.

To use a completely different domain class or constraints or an entirely new backing store, you simply tell the plugin which class to use in Config.groovy:

authenticationUserClass = MyLDAPUserClass
Any new class must simply ensure it has the following properties as a minimum:
String login
String password
String email
int status // must be set to AuthenticationService.STATUS_NEW at init

If this is still a Grails domain class and you have not changed the behaviour of the onSaveUser event, there is nothing more to do.

However if this is not a Grails domain class, and for example you need to talk to a directory server to save the user's details, you must supply a new implementation of onSaveUser.

Preventing signup/account creation

To prevent account creation attempts from the controller or directly through calls to the service, in Config.groovy set:

authentication.signup.disabled = true
Any groovy "true" value will disable it.

Applying password strength constraints

To customize the checking of passwords you just supply a new value for your onValidatePassword event:

import org.springframework.web.context.support.WebApplicationContextUtils

class BootStrap { def init = { servletContext -> // init auth events

def appCtx = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext)

appCtx.authenticationService.events.onValidatePassword = { password -> return !appCtx.myDictionaryService.containsWord(password) } } }

This validation is applied by the authentication service (only if you use the default domain class) prior to saving a user's details, in addition to the normal database constraints applied to the domain class.

Adding email address or other confirmation

Often you want to confirm a user's account after initial sign-up, typically by sending a confirmation email with a click-through link or a special code to enter.

Authentication plugin lets you do this any way you like, you simply have to:

  1. Make onConfirmAccount return true for the user (which may be conditional on other information you have)
  2. Make onSignup trigger whatever confirmation process it is you require
  3. Once your confirmation process has confirmed the user, call authenticationService.confirmUser(loginID)
e.g:
import org.springframework.web.context.support.WebApplicationContextUtils

...

def init = { servletContext ->

def appCtx = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext)

// this is the auth plugin bit appCtx.authenticationService.events.onConfirmAccount= { user -> // user is the domain object, AuthenticationUser unless you have changed it return true // always require confirmation no matter who it is or what their email address is } appCtx.authenticationService.events.onSignup = { params -> // params contains "user" - the domain object, and "extraParams" - all the params passed to the controller signup action myEmailConfirmationService.sendConfirmationTo(user.email) }

// this is dependent on your confirmation mechanism appCtx.myEmailConfirmationService.onConfirmation = { userToken -> appCtx.authenticationService.confirmUser(userToken) } }

Sending password reminders/reset

For this you simply need to look up the user by their login / email address - which if you use the default class is a case of AuthenticationUser.findByLogin or findByEmail - and if found email them the password (if you are not encoding it in the DB - tut tut!). Alternatively change the password property and save the domain class, and email them the new value.

Using the auth: tags

The auth:XXXX tags provide a set of useful utility functions that access the information about the logged in user in the session, or produce forms that set the parameters appropriately for authenticationController.

Executing GSP code if the user is logged in:

<auth:ifLoggedIn>You are logged in!</auth:ifLoggedIn>
Executing GSP code if the user is NOT logged in:
<auth:ifNotLoggedIn>You need to log in man!</auth:ifNotLoggedIn>
Executing GSP code if the user is registered by the account is not yet confirmed:
<auth:ifUnconfirmed>Please check your inbox for the confirmation mail!</auth:ifUnconfirmed>
Showing the current user login name or other user domain class property:
Hello, <auth:user/>  <!-- outputs the user login -->
Your email address is currently set to: <auth:user property="email"/>
Creating a form that submits to the authenticationController for login, signup, logout etc - set authAction to one of "login", "signup" or "logout":
<auth:form authAction="login" success="[controller:'portal']"
   error="[controller:'userProfile', action:'loginError']">
...fields here…
</auth:form>
Create a logout link with the success/error urls set correctly:
<auth:logoutLink success="[controller:'home', action:'newUser']"
    error="[controller:'userProfile', action:'error']">Log out</auth:logoutLink>

Using HTTP basic authentication

Not implemented yet because it sucks