Any site that stores or validates credentials has a responsibility and an obligation to protect those credentials with reasonable security measures. While in the backend Marketpath CMS stores and accesses passwords using security best-practices, there are also security measures that should be taken to protect those credentials from malicious access in templates. If you are interested in implementing profile authentication or registration in your templates, it is imperative that you keep these security measures in mind:
Not every request should be allowed to authenticate to your site.
In order for your credentials to remain secure, you should require the request to be sent over HTTPS. This can be accomplished simply using a quick check of request.use_ssl.
{% unless request.use_ssl %}{% redirect '/login' %}{% endunless %}.
As an additional precaution, you should not allow credentials to be sent as query string parameters. Instead, they should be submitted as part of a post request, which can be checked using request.method.
{% unless request.method == 'post' %}{% redirect '/login' %}{% endunless %}.
Authentication will practically never be meaningful if the user has disabled sessions. One potential solution is to enable sessions when the user tries to login.
{% set_client_permission %}
But it will typically make more sense and be more secure if the user cannot even submit the login form until sessions are enabled, which can be checked using session.allowed.
{% unless session.allowed %}{% redirect '/login' %}{% endunless %}
One simple security measure that you can implement is to require a valid token before authenticating a user. This prevents malicious actors from writing scripts that repeatedly submit usernames and passwords without first loading the form. Ideally tokens will be single-use randomly generated codes that are stored on the session and that expire after a predefined amount of time (eg: 20 minutes). Some sample code to generate a random token and store it on the session:
{% var token = "abcdefghijklmnopqrstuvwxyz0123456789" | rand: 16 | prepend: “tok_” %} {% var twentyMinutesFromNow = request.date | add_minutes:20 | date: "o" %} {% set_session &token:twentyMinutesFromNow %}
Some sample code to check if a token is valid
{% var tokenIsValid = false %} {% var token = request.post_params[‘token’] %} {% if token is_valid and token starts_with ‘tok_’ and session[token] is_date true %} {% var tokenExpires = session[token] | date %} {% if tokenExpires > request.date %} {% set tokenIsValid = true %} {% endif %} {% unset_session &token %} {% endif %} {% unless tokenIsValid %} {% redirect ‘/login’ %} {% endunless %}
One of the reasons to require users to allow sessions prior to logging in is so that you can track failed login and registration attempts. After too many failed attempts, you should disallow future attempts for a period of time in order to prevent brute force attacks.
In addition to locking sessions, it is a good idea to track failed login attempts against each profile. After too many failed login attempts you should lock the profile for a predetermined period of time (eg: 20 minutes after 3 failed attempts). Because of the differing requirements for locking profiles on different sites, the details of tracking when to lock the profile for how long are up to the template developer. However, you are strongly recommended to take advantage of the {% auth lock %} method for this purpose.
A discussion of security best practices would not be complete without addressing password requirements. No matter how secure you make your site, if a user sets their password to “password” or “1234” they are in danger of being compromised.
In order to accommodate the many various security needs of Marketpath CMS sites - some of which prefer simplicity over security and some of which prefer security over simplicity - we do not enforce password complexity for you. Therefore it is up to you as the template developer to define the minimum security standards required for your profiles. One of the simplest password requirements to implement is a minimum length (eg: 8 characters):
{% var pwd = request.post_params[‘password’] %} {% var len = pwd | size %} {% unless len >= 8 %} {% redirect ‘\login’ %} {% endunless %}
Another simple check is to make sure that the user does not include the string “password” or “1234” in their password:
{% var lower = pwd | downcase %} {% if lower contains “password” or lower contains “1234” %} {% redirect ‘\login’ %} {% endif %}
You may additionally implement further complexity requirements - such as requiring a combination of uppercase and lowercase letters, numbers, and symbols.
One common password requirement that it is important NOT to check in Marketpath CMS is that the new password is not any of the previous X passwords. The reason for this is that there is no way to perform this check without storing the old passwords in plain text (note: we do NOT store plain text passwords - all passwords are uniquely salted and hashed). These old passwords would not only be vulnerable if an attacker gained access to the raw user data, but they would be readily accessible by administrators of the Marketpath CMS site or in the event that anyone was able to execute liquid code on the site.
Settings and attributes are stored in plain text in Marketpath CMS. Because of this it is important not to store sensitive information in them. The example given above is storing previous passwords, but it might also include other financial, legal, or sensitive information.
While you can activate sessions during profile creation using {% auth register is_active:true %}, for most sites we recommend that you validate the profile information before activating their account. At a minimum this would require confirming that the user has access to the email address they registered with. For this purpose the user is assigned a randomly-generated 6-character activation code when they sign up. By default the activation code is valid for 1 week although you may specify a different timeframe. You may also generate a new activation code using the {% auth set_activation_code %} command, although doing so will invalidate the first code if it is still valid.
Do you have other ways that you protect your users? We’d love to hear them!
Please fill out the form below with your feedback or any questions you may have after working through the "Security Best Practices" lesson.