Introduction

The Grails ReCaptcha plugin adds Google ReCaptcha support to Grails applications, protecting forms from spam and abuse while letting real people through with ease.

The plugin provides two things:

  • a tag library, under the recaptcha namespace, that renders the captcha widget and the script tags it needs, and

  • a service, RecaptchaService, that verifies the answer the user submitted against the ReCaptcha service.

To use the plugin you need a ReCaptcha account, available from google.com/recaptcha.

License

This plugin is released under the Apache License, Version 2.0

Acknowledgments

The plugin was originally written by Chad Johnston at Megatome Technologies, and builds on the ideas of recaptcha4j.

Following are the plugin contributors:

Source code

The full source code for this plugin can be found on GitHub.

For issues, improvements or new features go to the plugin’s GitHub issues.

Usage

Installation

Add the plugin to the dependencies block of your build.gradle:

dependencies {
    implementation 'io.github.gpc:grails-recaptcha:8.0.0-RC1'
}

Requirements:

  • Grails 8.0.0 or higher

  • Java 21 or higher

Previous versions

io.github.gpc:grails-recaptcha is a new artifact. Earlier releases were published as org.grails.plugins:recaptcha through the Grails plugin repository rather than Maven Central: use version 7.0.0 for Grails 7, 5.0.0 for Grails 5, 4.0.0 for Grails 4, and 3.2.0 for Grails 3.

Configuration

The plugin reads its configuration from the recaptcha block of your application’s application.yml. The values match those used by the ReCaptcha service:

recaptcha:
    publicKey: "your site key"
    privateKey: "your secret key"
    includeScript: true
    includeNoScript: true

publicKey and privateKey are required — the plugin throws an IllegalArgumentException when either is missing or empty.

includeScript controls whether the <script> tag ReCaptcha needs is emitted together with the widget; see the tag library reference for overriding it per tag. includeNoScript controls whether the <noscript> fallback markup is emitted.

Per-environment configuration

Any of the settings can be given per environment. Disabling the captcha during development is the most common case:

environments:
    development:
        recaptcha:
            enabled: false
    production:
        recaptcha:
            enabled: true

When enabled is false the tag library renders nothing and verifyAnswer returns true without contacting Google, so no controller changes are needed to turn the captcha off.

Proxy server configuration

If your server reaches the ReCaptcha service through a proxy, configure it under recaptcha.proxy:

recaptcha:
    proxy:
        server: ""    # IP or hostname of the proxy server
        port: ""      # Proxy server port, defaults to 80
        username: ""  # Optional, only if the proxy requires authentication
        password: ""  # Optional, only if the proxy requires authentication

Only server is required. port defaults to 80, and username and password are needed only for an authenticating proxy.

Timeout configuration

If verification requests to Google are slow, the network timeouts can be changed independently of each other:

recaptcha:
    timeoutConfig:
        connectTimeout: 10000  # Timeout for establishing the connection, in ms. Defaults to 10000
        readTimeout: 1000      # Timeout for waiting on the response, in ms. Defaults to 1000

Externalized configuration

See the Grails documentation for using externalized configuration files. As an alternative to application.yml, the plugin falls back to a RecaptchaConfig Groovy script on the classpath:

recaptcha {
    publicKey = ""
    privateKey = ""
    includeNoScript = true
    includeScript = true
}

environments {
    development {
        recaptcha {
            enabled = false
        }
    }
    production {
        recaptcha {
            enabled = true
        }
    }
}

Tag library

All tags live in the recaptcha namespace.

<recaptcha:recaptcha>

Generates the HTML that displays the captcha. Supports the following attributes:

Attribute Description

theme

dark or light. Defaults to light.

size

compact or normal. Defaults to normal.

lang

Any of the supported ReCaptcha language codes.

tabindex

Optional tabindex of the widget.

type

image or audio, for when the checkbox is not sufficient. Defaults to image.

successCallback

Optional JavaScript function called when the user submits a successful response.

expiredCallback

Optional JavaScript function called when the successful response has expired.

includeScript

Set to false to leave out the <script> tag ReCaptcha requires, overriding the global configuration value. <recaptcha:script> is then also required.

See the ReCaptcha client guide for more details.

<recaptcha:script>

Renders the required <script> tag on its own. Combine it with includeScript=false, set either globally or on the tag, to place the script elsewhere in your markup. Supports the lang attribute.

This tag does not work in the <head> section of the page.

<recaptcha:recaptchaExplicit>

Generates the HTML for explicit display and rendering of the captcha. Supports the following attributes:

Attribute Description

lang

Any of the supported ReCaptcha language codes.

loadCallback

The JavaScript function called once all dependencies have loaded. This function is normally responsible for rendering the captcha. Required.

For more about explicit mode, see the ReCaptcha documentation.

<recaptcha:renderParameters>

Generates the JSON-like string used as the argument to grecaptcha.render(). Supports theme, size, tabindex, type, successCallback and expiredCallback, with the same meanings as on <recaptcha:recaptcha>.

<recaptcha:ifEnabled> / <recaptcha:ifDisabled>

Render their body when the captcha is, respectively, enabled or disabled in the configuration.

<recaptcha:ifFailed>

Renders its body when the previous verification failed. Useful for showing your own error message.

Verifying the answer

Inject RecaptchaService into your controller and call verifyAnswer:

recaptchaService.verifyAnswer(session, request.getRemoteAddr(), params)

It returns true or false. It also returns true when the plugin is disabled in the configuration, which means you do not have to change your controller to turn the captcha off.

After a successful verification, call cleanUp(session) to release the resources the plugin associated with the session. This is not strictly required, but skipping it leaves the ReCaptcha state in memory until the session expires.

Examples

A runnable example application lives under examples/app1 in the plugin’s repository. The snippets below are taken from it.

Automatic rendering

The most common scenario. In your GSP:

<recaptcha:ifEnabled>
    <recaptcha:recaptcha theme="dark"/>
</recaptcha:ifEnabled>

Leaving out theme defaults the captcha to the light theme.

Explicit rendering

<script type="text/javascript">
    var onloadCallback = function () {
        grecaptcha.render('html_element', <recaptcha:renderParameters theme="dark" type="audio" tabindex="2"/>);
    };
</script>
<g:form action="myAction" method="post">
    <recaptcha:ifEnabled>
        <recaptcha:recaptchaExplicit loadCallback="onloadCallback"/>
        <div id="html_element"></div>
    </recaptcha:ifEnabled>
    <g:submitButton name="submit"/>
</g:form>

For more about explicit mode, see the ReCaptcha documentation.

Separate script tag

Set includeScript to false, either on the tag as below or globally in the configuration:

<body>
    <g:form action="validateNormal" method="post">
        <recaptcha:ifEnabled>
            <recaptcha:recaptcha includeScript="false"/>
        </recaptcha:ifEnabled>
        <g:submitButton name="submit"/>
    </g:form>
    <recaptcha:script/>
</body>

The <script src="https://www.google.com/recaptcha/api.js?" async defer></script> tag is then emitted at the bottom of the document instead of immediately before the <div> holding the captcha.

Customizing the language

Set lang on <recaptcha:recaptcha>, <recaptcha:recaptchaExplicit> or <recaptcha:script>. See ReCaptcha language codes for the available values.

Verifying user input

An abbreviated controller that verifies the captcha when a new user is saved:

import com.megatome.grails.RecaptchaService

class UserController {

    RecaptchaService recaptchaService

    def save() {
        def user = new User(params)
        // ...other validation...
        def recaptchaOK = recaptchaService.verifyAnswer(session, request.getRemoteAddr(), params)
        if (!user.hasErrors() && recaptchaOK && user.save()) {
            recaptchaService.cleanUp(session)
            // ...other account creation activities...
            render(view: 'showConfirmation', model: [user: user])
        } else {
            render(view: 'create', model: [user: user])
        }
    }
}

Testing

Google publishes test keys that render a captcha which always validates. Configuring them in your test environment lets integration tests exercise the plugin without a real ReCaptcha account:

environments:
    test:
        recaptcha:
            publicKey: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'
            privateKey: '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe'

To assert on the rendered markup without any network access at all, call the service directly:

def html = recaptchaService.createCaptcha(theme: 'dark')

expect:
html.contains('class="g-recaptcha"')
html.contains('data-theme="dark"')

Alternatively, disable the captcha entirely in the test environment — verifyAnswer then returns true without contacting Google.

Release Notes

  • 8.0.0

    • Support for Grails 8, Spring Boot 4 and Java 21

    • Migrated to the grails-plugin-template build structure

    • Published as io.github.gpc:grails-recaptcha, replacing org.grails.plugins:recaptcha

  • 7.0.0 - Support for Grails 7

  • 5.0.0 - Support for Grails 5

  • 4.0.0 - Support for Grails 4

  • 3.2.0 - Configurable network timeouts for captcha verification

  • 3.1.0 - Support for Grails 3.2

  • 3.0.1 - Removed Mailhide support; size attribute supported in render parameters

  • 3.0.0 - Support for Grails 3