Open Source Extras 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`
]]