Marcin Erdmann

Groovy, Grails, Geb...

Using Remote Control plugin to log in into a Grails application with Spring Security backed authentication

In my previous post I wrote about how much I like using grey box testing as a technique for writing functional tests. One common scenario where you could use it is to log in into the application in your tests without having to go to the login page and filling the credentials every single time you need to test something that is available only to logged in users of your system.

Some people will argue that you shouldn't be logging in for every single test that needs the user to be authenticated to be performed. They will use different ways not to have to do so like stepwise Spock specifications or not clearing cookies between tests. But I don't agree with that and I'm happy to pay the price of that little bit of overhead. By starting every single tests from always the same, clear, consistent state, setting up all prerequisites and then clearing cookies and your database afterwards you avoid test bleed which is often hard to track and can lead to flakey tests.

On the other hand it doesn't mean that you shouldn't be smart about the way you're setting up your tests to save time. Not going through the frontend every single time but logging in programmatically is one way to achieve it. A thing to remember here is that because you're not testing it indirectly when using this technique you should always have a test that verifies that authenticating using the frontend works as expected.

The most popular Grails authentication plugin is Spring Security Core Plugin. All that you need to do to programmatically log in in a test into an application that is using Spring Security Core plugin is to authenticate and then drop a JSESSIONID cookie in your browser:

class AuthenticatedSpec extends GebSpec {

    RemoteControl remote = new RemoteControl()

    void setup() {
        def sessionId = remote {
            ctx.springSecurityService.reauthenticate('user', 'password')
            RequestContextHolder.requestAttributes.request.session.id
        }

        if (!driver.currentUrl.startsWith(config.baseUrl)) {
            to LoginPage
        }

        driver.manage().addCookie(new Cookie('JSESSIONID', sessionId))
    }
}

Thanks to the fact that we have access to main application context in remote control closures (via ctx) we can retrieve the springSecurityService bean and call reauthenticate() on it as suggested by Burt Beckwith in this post. Session id can be retrieved from RequestContextHolder. Finally, we make sure before dropping the cookie that the browser is pointing at the tested domain as it won't allow you to drop a cookie for a domain that it's not pointing at - this will only be the case for the first test or if a test ends up outside of the domain you're testing.