Invisible reCAPTCHA

I’ve previously used the Google one click reCAPTCHA in sites but as the new invisible version is out I figured I’d have a go at getting it to work.

Previously I had the one click reCAPTCHA working as below where the result populated an invisible required field validated using Bootstrap validator which then allowed the form to be submitted.


@section head {
    <script src='https://www.google.com/recaptcha/api.js'></script>
}

@{
    ViewData["Title"] = "Contact";
}

@{
    string contactMessage = (string)ViewData["ContactMessage"];
}

@model ContactForm

<div class="row">
    <div class="col-lg-4 col-md-6 col-xs-12">
        <h2>@ViewData["Title"].</h2>
        @if (!string.IsNullOrEmpty(contactMessage))
        {
            if (contactMessage == "Message sent :)")
            {
                <div class="alert alert-success" role="alert">Message sent :)</div>
            }
            else
            {
                <div class="alert alert-danger" role="alert">contactMessage</div>
            }
        }
        <form asp-controller="Home" asp-action="Contact" method="post" id="ContactForm" data-toggle="validator" role="form">
            <fieldset>
                <div class="item form-group required field">
                    <div class="row">
                        <div class="col-md-4">
                            <label asp-for="FromEmail" class="control-label"></label>
                        </div>
                        <div class="col-md-8">
                            <input asp-for="FromEmail" class="form-control" required />
                            <div class="help-block with-errors"></div>
                        </div>
                    </div>
                </div>
                <div class="item form-group required field">
                    <div class="row">
                        <div class="col-md-4">
                            <label asp-for="Message" class="control-label"></label>
                        </div>
                        <div class="col-md-8">
                            <textarea asp-for="Message" class="form-control" required rows="10"></textarea>
                            <div class="help-block with-errors"></div>
                        </div>
                    </div>
                </div>
				<div class="form-group">
					<div class="captchaDiv">
						<label class="control-label">Before reqistering, please tick the box below to show you are not a robot and select the relevant pictures if asked.</label>
						<div class="g-recaptcha" data-sitekey="<YOUR_KEY>" data-callback="captcha_onclick"></div>
						<input type="text" name="recaptcha" value="" required id="RecaptchaValidator" pattern="1" style="visibility: hidden">
					</div>
				</div>
                <div class="form-group">
                    <button type="button" class="btn btn-primary" id="submit">Send</button>
                </div>
            </fieldset>
        </form>
    </div>
</div>

@section scripts {
    <script src="~/lib/bootstrap-validator/dist/validator.js"></script>
    <script type="text/javascript">
        function captcha_onclick() {
            $('#RecaptchaValidator').val(1);
            $('#ContactForm').validator('validate');
        }
    </script>
}

The new invisible reCAPTCHA isn’t validated before the specified button is clicked, once it is clicked the reCAPTCHA does its stuff and the user is flagged as being human or not, if they are human then the associated javascript function is triggered. The triggered javascript function then needs to POST the form to the controller action and load the returned view using AJAX.

Index.cshtml

@section head {
    <script src='https://www.google.com/recaptcha/api.js'></script>
}

@{
    ViewData["Title"] = "Contact";
}

@{
    string contactMessage = (string)ViewData["ContactMessage"];
}

@model ContactForm

<div class="row">
    <div class="col-lg-4 col-md-6 col-xs-12">
        <h2>@ViewData["Title"].</h2>
        @if (!string.IsNullOrEmpty(contactMessage))
        {
            if (contactMessage == "Message sent :)")
            {
                <div class="alert alert-success" role="alert">Message sent :)</div>
            }
            else
            {
                <div class="alert alert-danger" role="alert">contactMessage</div>
            }
        }
        <form asp-controller="Home" asp-action="Contact" method="post" id="ContactForm" data-toggle="validator" role="form">
            <fieldset>
                <div class="item form-group required field">
                    <div class="row">
                        <div class="col-md-4">
                            <label asp-for="FromEmail" class="control-label"></label>
                        </div>
                        <div class="col-md-8">
                            <input asp-for="FromEmail" class="form-control" required />
                            <div class="help-block with-errors"></div>
                        </div>
                    </div>
                </div>
                <div class="item form-group required field">
                    <div class="row">
                        <div class="col-md-4">
                            <label asp-for="Message" class="control-label"></label>
                        </div>
                        <div class="col-md-8">
                            <textarea asp-for="Message" class="form-control" required rows="10"></textarea>
                            <div class="help-block with-errors"></div>
                        </div>
                    </div>
                </div>
                <div class="form-group">
                    <button type="button" class="g-recaptcha btn btn-primary" id="submit" data-sitekey="<YOUR_KEY>" data-callback="submit_form">Send</button>
                </div>
            </fieldset>
        </form>
    </div>
</div>

@section scripts {
    <script src="~/lib/bootstrap-validator/dist/validator.js"></script>
    <script type="text/javascript">
        function submit_form(response) {
            if ($('#ContactForm').validator('validate')) {
				$.ajax({
					url: '@Url.Action("ValidateCaptcha", "Home")?response=' + response,
					type: 'GET',
					headers: { 'RequestVerificationToken': getAntiForgeryToken({}) },
					success: function () {
						var formdata = new FormData($('#ContactForm').get(0));

						$.ajax({
							url: '@Url.Action("Contact", "Home")',
							type: 'POST',
							data: formdata,
							processData: false,
							contentType: false,
							success: function(data) {
								$("body").html(data);
								}
						});
					},
					error: function () {
						grecaptcha.reset();
					}
				});
            } else {
                grecaptcha.reset();
            }
        }
    </script>
}

You should also validate the response returned from the ReCaptcha service to ensure that it is a valid response, this is done by the AJAX call to the ValidateCaptcha action which is a server side call to the ReCaptcha service using your secret and the returned response.

HomeController.cs

public IActionResult ValidateCaptcha(string response)
{
	if (ReCaptcha.ValidateAsync(response).Result)
	{
		return new StatusCodeResult(200);
	}
	else
	{
		return new StatusCodeResult(500);
	}
}
ReCaptcha.cs

public class ReCaptcha
{
	public static async Task<bool> ValidateAsync(string response)
	{
		bool valid = false;

		using (HttpClient client = new HttpClient())
		{
			HttpResponseMessage result = await client.PostAsync("https://www.google.com/recaptcha/api/siteverify?secret=<YOUR SECRET>&response=" + response, null);
			ReCaptchaResponse reCaptchaResponse = JsonConvert.DeserializeObject<ReCaptchaResponse>(await result.Content.ReadAsStringAsync());
			valid = reCaptchaResponse.Success;
		}

		return valid;
	}
}

public class ReCaptchaRequest
{
	[JsonProperty("secret")]
	public string Secret { get; set; }

	[JsonProperty("response ")]
	public string Response { get; set; }

	[JsonProperty("remoteip")]
	public string RemoteIp { get; set; }
}

public class ReCaptchaResponse
{
	[JsonProperty("success")]
	public bool Success { get; set; }

	[JsonProperty("challenge_ts")]
	public DateTime ChallengeTimeStamp { get; set; }

	[JsonProperty("hostname")]
	public string Hostname { get; set; }

	[JsonProperty("error-codes")]
	public List<string> ErrorCodes { get; set; }
}

This works for forms created using tag helpers in ASP.NET MVC 6 but should work in all forms.


Leave a Reply

Your email address will not be published. Required fields are marked *