Open Source CSRFHelper Login Login

CSRFHelper can protect your login forms from the Login package against CSRF attacks.

Adding the token to the Login form

First, we need to define a custom chunk to use as the login form if you haven't already done that:

[[!Login?
    &tplType=`modChunk` 
    &loginTpl=`myLoginTpl`
]]

Edit the myLoginTpl chunk. Based on the default lgnLoginTpl chunk, that might look like this:

<div class="loginForm">
    <div class="loginMessage">[[+errors]]</div>
    <div class="loginLogin">
        <form class="loginLoginForm" action="[[~[[*id]]]]" method="post">                
            <fieldset class="loginLoginFieldset">
                <legend class="loginLegend">[[+actionMsg]]</legend>
                <label class="loginUsernameLabel">[[%login.username]]
                    <input class="loginUsername" type="text" name="username" />
                </label>
                
                <label class="loginPasswordLabel">[[%login.password]]
                    <input class="loginPassword" type="password" name="password" />
                </label>
                <input class="returnUrl" type="hidden" name="returnUrl" value="[[+request_uri]]" />

                [[+login.recaptcha_html]]
                
                <input class="loginLoginValue" type="hidden" name="service" value="login" />
                <span class="loginLoginButton"><input type="submit" name="Login" value="[[+actionMsg]]" /></span>
            </fieldset>
        </form>
    </div>
</div> 

Inside the <form>, add a hidden field like so:

<input type="hidden" name="csrf_token" value="[[!csrfhelper? &key=`login` &singleUse=`1`]]">

Note that we're adding &singleUse here. Logging in a sensitive action, so we only want each token to be used once. We're also setting the &key to login, which we'll need to also set in the Login snippet in a minute.

While for Formit we need to separately add an error placeholder to show when the security token isn't valid, that's not needed for Login. The [[+errors]] placeholder will show the error automatically.

The full example myLoginTpl chunk, including the csrf_token hidden field, now looks like this:

<div class="loginForm">
    <div class="loginMessage">[[+errors]]</div>
    <div class="loginLogin">
        <form class="loginLoginForm" action="[[~[[*id]]]]" method="post">
            <input type="hidden" name="csrf_token" value="[[!csrfhelper? &key=`login` &singleUse=`1`]]">
                
            <fieldset class="loginLoginFieldset">
                <legend class="loginLegend">[[+actionMsg]]</legend>
                <label class="loginUsernameLabel">[[%login.username]]
                    <input class="loginUsername" type="text" name="username" />
                </label>
                
                <label class="loginPasswordLabel">[[%login.password]]
                    <input class="loginPassword" type="password" name="password" />
                </label>
                <input class="returnUrl" type="hidden" name="returnUrl" value="[[+request_uri]]" />

                [[+login.recaptcha_html]]
                
                <input class="loginLoginValue" type="hidden" name="service" value="login" />
                <span class="loginLoginButton"><input type="submit" name="Login" value="[[+actionMsg]]" /></span>
            </fieldset>
        </form>
    </div>
</div>

Validating the token with a hook

Now that we're submitting the token, we should also validate it. We do this with the csrfhelper_login pre-hook.

In the Login snippet call, add the csrfhelper_login to your &preHooks property.

Also add the &csrfKey property with the key for the CSRF token; this should be unique for each unique form and match the &key in the csrfhelper snippet call. In the example above, this was set to login.

All things combined, our Login snippet now looks like this:

[[!Login?
    &tplType=`modChunk` 
    &loginTpl=`myLoginTpl`
    &preHooks=`csrfhelper_login`
    &csrfKey=`login`
]]