Authentication Plugin
Dependency :
compile ":authentication:2.0.1"
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
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
- 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>/authentication3. 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
}
}
}
}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
}
}
}
}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>
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 validonValidateLogin:{ loginID -> true }onValidatePassword: { password -> true }onEncodePassword: { password -> password?.encodeAsMD5Hex() }onFindByLogin:{ loginID -> AuthenticationUser.findByLogin(loginID) }onNewUserObject: { loginID -> def obj = AuthenticationUser.newInstance(); obj.login = loginID; return obj }onSaveUser: { user -> user.save() }onLoggedIn: { AuthenticatedUser login -> }onLoggedOut: { AuthenticatedUser login -> }onSignup: { params -> }onDelete: { user -> user.delete() }onConfirmAccount: { user -> }onCheckAuthorized: { params -> true }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.groovyCodecs
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
<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>
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
}
}
}
}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
String login String password String email int status // must be set to AuthenticationService.STATUS_NEW at init
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 = trueApplying 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.WebApplicationContextUtilsclass BootStrap { def init = { servletContext -> // init auth events def appCtx = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext) appCtx.authenticationService.events.onValidatePassword = { password -> return !appCtx.myDictionaryService.containsWord(password) } } }
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:- Make onConfirmAccount return true for the user (which may be conditional on other information you have)
- Make onSignup trigger whatever confirmation process it is you require
- Once your confirmation process has confirmed the user, call authenticationService.confirmUser(loginID)
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>
<auth:ifNotLoggedIn>You need to log in man!</auth:ifNotLoggedIn>
<auth:ifUnconfirmed>Please check your inbox for the confirmation mail!</auth:ifUnconfirmed>Hello, <auth:user/> <!-- outputs the user login -->
Your email address is currently set to: <auth:user property="email"/><auth:form authAction="login" success="[controller:'portal']" error="[controller:'userProfile', action:'loginError']"> ...fields here… </auth:form>
<auth:logoutLink success="[controller:'home', action:'newUser']" error="[controller:'userProfile', action:'error']">Log out</auth:logoutLink>