Authors avatar image Tim Harrison (Tim) Published on 18/10/2020
Abstract

Steps needed to install Cauldstanes ReCaptcha.Mvc Nuget Package

These are the steps needed to install the Cauldstanes ReCaptcha.Mvc Nuget Package

    1. Setup your Google Account to track the ReCaptcha
    2. Install nuget package from Cauldstanes Nuget Server
    3. Configure the ReCaptcha.json file, installed in the root directory of your applicaiton, with the SiteKey and Secret keys from your Googl Account.
    4. Add the code to the web page using the Html Helpers.

Set Up Google Account

Logon on to your Google Developers account and move to the admin page for your ReCaptcha sites.  See the link to Google Recaptcha Admin in the references.

Install ReCaptcha Nuget Package

Within VS, right click the project and select "Manage Nuget Packages".  Swap the source of packages to "Cauldstanes" and perform the Nuget Package Installation.

Configure ReCaptcha

Open the ReCaptchConfig.json file in the root folder of the site and compete the settings:

{
  "Settings": {
    "SiteKey": "6LfcsuMUAAAAAJ9MQSkMSj_yNp1hOM05iWphB0k8",
    "SecretKey": "6LfcsuMUAAAAAEydRqGFo0-gefgJ4HPROk-grlvt",
    "CaptchaApiAddress": "https://www.google.com/recaptcha/api/siteverify",
    "CaptchaScript": "https://www.google.com/recaptcha/api.js?render=",
    "DefaultThreshold": 0.5,
    "AllowNoAction": false,
    "InLineScript": false,
    "ScriptFileName": "/Scripts/reCaptcha/jquery.reCaptcha.min.js"
  },
  "Actions": [
    {
      "Action": "homepage",
      "ScoreLimit": 0.5
    },
    {
      "Action": "login",
      "ScoreLimit": 0.7
    },
    {
      "Action": "register",
      "ScoreLimit": 0.9
    },
    {
      "Action": "contactform",
      "ScoreLimit": 0.5
    },
    {
      "Action": "feedbackform",
      "ScoreLimit": 0.5
    },
    {
      "Action": "blogeditform",
      "ScoreLimit": 0.7
    }
  ]
}

Reference the Google ReCaptcha documentation for a description of the settings possible for configuration.  The above is configured for this site, as is in use when editing these blogs.

different actions can be specified with an associated threshold setting, so that different degress can be applied depending on the page content and functionality.

The ScriptFileName setting allows the javascript module that should be loaded for the page.  This example uses the basic implementation of ReCaptcha, coded as a jQuery plugin.

/// <reference path="../jquery-3.4.1.js" />

/**
 * Script as a jQuery Plugin to provide a ReCaptcha that can be attached to a form explicitly
 *  Minizse (online) using https://html-css-js.com/js/compressor/
 */
(function($) {

    var settings = {};

    var methods = {
        init: function(options) {
            //  Set the options to the settings variable

            var self = this; //Encapsulate the form recaptcha is protecting

            $(':submit').click(function() {
                //  Store the value (formAction) of the button clicked in an input field 
                //  so the server can determine which button was used to submit the form
                var attrValue = $(this).val();
                $('#formaction').val(attrValue);
            });

            self.submit(function(e) {
                //  Intercept the form submit process, and pause the submit to the  
                //  server code for now, we need to wait until the captcha has
                //  responded, if all is OK, call the submit from the callback.
                e.preventDefault();

                var thisForm =
                    $(this); //  needed to ensure the object is in scope in the success callback of the reCaptcha function
                //alert(thisForm.attr("id"));

                //  Retrieve the values required for ReCaptcha from the input form
                var sitekey = $('#sitekey').val();
                var action = $('#action').val();

                //  Call the recaptcha process to get the token for validation
                grecaptcha.ready(function() {
                    grecaptcha.execute(sitekey, { action: action }).then(function(token) {
                        //  Shove the token into the form.
                        var tokenElement = thisForm.find('input#captchatoken');
                        tokenElement.val(token);

                        //var thisformId = thisForm.attr("id");

                        //var inputs = thisForm.find('input[type=hidden]');
                        //var inputsCount = inputs.length;

                        //var skeyElement = thisForm.find('input#sitekey');
                        //var skeyValue = skeyElement.val();

                        //var actionElement = thisForm.find('input#action');
                        //var actionValue = actionElement.val();

                        //var tokenValue = tokenElement.val();

                        //var msg = 'Form id:' + thisformId + '\r\n' +
                            //'Inputs : ' + inputsCount + '\r\n' +
                            //'Sitekey: ' + skeyValue + '\r\n' +
                            //'Action : ' + actionValue + '\r\n' + 
                            //'Token  : ' + tokenValue;

                        //alert(msg);



                        //  Don't call the validation from here CORS will not allow it, it must be done from the server
                        //validateReCaptcha(token, 'contactform', validateReCaptcha);

                        //  OK, so now continue the submit: unbind this event and resubmit the form.
                        //$(this).unbind('submit').submit();
                        thisForm.unbind('submit').submit();

                    });
                });
            });

        }
    };

    $.fn.reCaptcha = function( method ) {

        if (methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (typeof method === 'object' || !method) {
            return methods.init.apply(this, arguments);
        } else {
            $.error('Method ' + method + ' does not exist on jQuery.reCaptcha');
        }
    }

})(jQuery);


/**
 * Doc ready function to attach the recaptcha to the forms
 * DO NOT minimize this
 */
//$(document).ready(function() {
//    $('form').reCaptcha();
//});

Add ReCaptcha elements to HTML pages

A series of Html Helper methods are provided to easily incorporate the necessary fields and settings required for the ReCaptcha to work.

Html.ReCaptchaInput should be coded only once for the form being tracked. it takes 2 parameters:

  1. action:  A string representing the action being tracked by ReCaptch, which corresponds to one of the actions in the the above configuration file
  2. formSubmitValue: a string representing the name of the submit button(s) used by the form

Htmo.ReCaptchaScripts: is used to load the scripts for controlling the ReCaptcha for the page.  It accepts a parameter for the script, which defaults to the script in the config file if no parameter is used.

The example below shows the implementation for the Blog Edit page for editing the blogs on this site:

@using ReCaptchaV3.Mvc
@model cauldstanes.uk.Areas.BackOffice.Models.CreateBlogViewModel

@{
    ViewBag.Title = "Create New Blog";

    //  TODO:   Code Sanitiser for the HTML allowed inputs for Abstract and Content
    //  See the comments on the "Edit.cshtml" page

}

@section styles {
    @* Tag Editor: https://goodies.pixabay.com/jquery/tag-editor/demo.html *@
    @Styles.Render("~/Content/themes/base/jquery-ui.min.css")
    @Styles.Render("~/Content/tagEditor/jquery.tag-editor.css")
}

@section pageheader
{
    <header class="pageheader">
        @* Icon to indicate in Edit mode: bit of a cheat to ensure the spacing at the top is correct *@
        <h2><i class="fa fa-edit"></i>Create</h2>
    </header>
}

@section leftaside
{
    <aside class="left-aside">
        @Html.Partial("_blogTreeViewPartial", Model.BlogHeaders)
    </aside>
}

@* (Html.BeginForm()) *@
@using (Html.BeginForm("Create", "Blogs", FormMethod.Post, new { id = "EditForm" }))
{
    @Html.AntiForgeryToken()

    @Html.ReCaptchaInput("createBlog", "FormAction")

    <div class="form-horizontal">

        <header>
            @*class="pageheader">*@

            <div class="form-inline">
                <label for="Title">
                    <i class="fas fa-heading"></i><span>Title</span>
                </label>
                @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" })
            </div>

        </header>

        @*<hr />
        <div id="autosaveStatus" class="invisible" role="presentation" aria-hidden="false">
            <span>
                Autosaving:
                <img src="~/Content/Images/ajax-loader-circle-25.gif" alt="autosave in progress." aria-hidden="true" />
            </span>
        </div>*@
        @*  Testing: allow the autosave to be switched on and off *@
        <button id="autosaveOn" class="btn btn-primary" value="Turn Autosave On" style="display: none">Turn autosave On</button>
        <button id="autosaveOff" class="btn btn-primary" disabled value="Turn Autosave Off" style="display: none">Turn autosave Off</button>

        @*<div id="autosaveResult" class="invisible" role="presentation" aria-hidden="false"></div>*@

        <hr />

        @Html.ValidationSummary(false, "", new { @class = "text-danger" })


        <div class="tabs">

            @* Radio button and label for #tab-content1: Blog Abstract RTE *@
            <input type="radio" name="tabs" id="tab1" checked aria-checked="True">
            <label for="tab1">
                <i class="fa fa-info"></i><span>Abstract</span>
            </label>
            @* Radio button and label for #tab-content2: Blog Content RTE *@
            <input type="radio" name="tabs" id="tab2">
            <label for="tab2">
                <i class="fa fa-book"></i><span>Content</span>
            </label>
            @* Radio button and label for #tab-content3: Blog Tags Editor *@
            <input type="radio" name="tabs" id="tab3">
            <label for="tab3">
                <i class="fa fa-tags"></i><span>Tags</span>
            </label>
            @* Radio button and label for #tab-content4: Blog References Editor *@
            <input type="radio" name="tabs" id="tab4">
            <label for="tab4">
                <i class="fa fa-link"></i><span>References</span>
            </label>

            @* Tab Contents: Abstract *@
            <div id="tab-content1" class="tab-content">
                <div class="form-group">
                    <div>
                        @Html.TextAreaFor(model => model.Abstract, new { @class = "form-control" })
                        @Html.ValidationMessageFor(model => model.Abstract, "", new { @class = "text-danger" })
                    </div>
                </div>
            </div>
            @* Tab Contents: Content *@
            <div id="tab-content2" class="tab-content">
                <div class="form-group">
                    <div>
                        @Html.TextAreaFor(model => model.Content, new { @class = "form-control" })
                        @Html.ValidationMessageFor(model => model.Content, "", new { @class = "text-danger" })
                    </div>
                </div>
            </div>
            @* Tab Contents: Tags *@
            <div id="tab-content3" class="tab-content">
                <div class="form-group">
                    <div>
                        @Html.TextAreaFor(model => model.Tags, new { @class = "form-control" })
                        @Html.ValidationMessageFor(model => model.Tags, "", new { @class = "text-danger" })
                    </div>
                </div>
            </div>
            @* Tab Contents: References *@
            <div id="tab-content4" class="tab-content">
                <div class="form-group">

                    <table class="table">
                        <tr>
                            <th>
                                @Html.DisplayNameFor(model => model.NewSource)
                            </th>
                            <th>
                                @Html.DisplayNameFor(model => model.NewUrl)
                            </th>
                            <th></th>
                        </tr>

                        @Html.Partial("_EditBlogReferencesPartial", Model.References)

                        <tr>
                            <td>
                                @Html.TextBoxFor(model => model.NewSource, null, htmlAttributes: new { @class = "form-control" })
                                @Html.ValidationMessageFor(model => model.NewSource, "", new { @class = "text-danger" })
                            </td>
                            <td>
                                @Html.TextBoxFor(model => model.NewUrl, null, new { @class = "form-control" })
                                @Html.ValidationMessageFor(model => model.NewUrl, "", new { @class = "text-danger" })
                            </td>
                            <td>
                                <button type="submit" id="addRef" value="AddRef" name="FormAction" class="btn btn-primary" role="button"><i class="fas fa-upload"></i>Add</button>
                            </td>
                        </tr>


                    </table>

                </div>
            </div>

        </div>

        <div class="form-group">
            <div class="button-bar">
                <button type="submit" id="save" value="Save" name="FormAction" class="btn btn-primary" role="button"><i class="far fa-save"></i>Save</button>
                <button type="submit" id="cancel" value="Cancel" name="FormAction" class="btn btn-secondary cancel" role="button"><i class="far fa-trash-alt"></i>Cancel</button>
                <button type="reset" id="reset" value="Reset" name="FormAction" class="btn btn-secondary" role="button"><i class="fas fa-undo"></i>Reset</button>
            </div>
        </div>

        <div id="autosaveUrl" data-value="@Url.Action("Autosave", "Blogs")"></div>

    </div>
}



@section pagefooter
{
    <footer class="pagefooter">
        <span>
            <i class="fa fa-envelope-open-text"></i>
        </span>
        @*<div id="autosaveStatus" style="display: none" role="presentation" aria-hidden="true">*@
        <div id="autosaveStatus" class="invisible" role="presentation" aria-hidden="true">
            <span>
                Autosaving:
                <img src="~/Content/Images/ajax-loader-circle-25.gif" alt="autosave in progress." aria-hidden="true" />
            </span>
        </div>

        <div id="autosaveResult" class="invisible accessibility-issue--info" role="presentation" aria-hidden="true"></div>
        @*<div id="autosaveResult" style="display: none" role="presentation" aria-hidden="true"></div>*@

    </footer>
}

@section aside
{
    <aside class="aside">
        @Html.ActionLink("Back to List", "Index")
    </aside>
}



@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")

    @Html.ReCaptchaScripts()

    @*  TinyMce documentation: => using with more that one editable area on the single page
        https://www.tiny.cloud/docs/general-configuration-guide/multiple-editors/
    *@
    <script src="~/Scripts/tinymce/tinymce.min.js"></script>

    @* Tag Editor *@
    <script src="~/Scripts/jquery-ui-1.12.1.min.js"></script>
    <script src="~/Scripts/tagEditor/jquery.caret.min.js"></script>
    <script src="~/Scripts/tagEditor/jquery.tag-editor.js"></script>
    @*<script src="~/Scripts/tagEditor/jquery.tag-editor.min.js"></script>*@

    @* AutoSave and FormData *@
    <script src="~/Scripts/AutoSave/FormData.js"></script>
    <script src="~/Scripts/AutoSave/jQuery.Autosave.js"></script>

    @* Moved to after Tag Editor as it contains configuration for all script elements on the page*@
    <script src="~/Scripts/BackOffice/BlogEdit.js"></script>

}

 

 

 

 

References
Tags

Back to List