Learn

Protecting Content

Control who can and can't see each page of content.

As of v1.7, Statamic can protect your content from unwanted visitors with the _protect variable. By setting this variable, you can allow or deny people based on their logged-in status, their member profile fields, and other things. There are four schemes used for protecting content:

  • allow will only let users in if their session matches all of the requirements you list
  • deny will only prevent users from getting in if their session matches all of the requirements you list
  • password will force users to enter a password you set before being allowed to view the content
  • ip_address (added in v1.7.1) will only allow users in based on their IP address

Whichever of these you choose, know that _protect is designed to help you out. We’ve tried to keep the syntax as simple as possible to use while at the same time allowing for a lot of flexibility. Because of this, if Statamic sees that _protect has been set but it isn’t able to parse the requirements you’ve set, all users will always be denied. In these instances, a message will be written to the log telling you that something’s gone wrong.

Of these four, allow and deny work the same except for being opposites of one another (one lets people in, one keeps people out). The password and ip_address options work a bit differently but are the easiest to explain so we’ll start with that those. Before that however, we should review the _protect add-on’s configuration.

Configuring

The protect bundle powers the content-protection features of Statamic. It’s what examines the current user, determines who is and isn’t allowed in, and handles redirection as appropriate. There are a couple of fields that need configuring to use it on your site.

The config file for the protect add-on can be found in _config/bundles/protect/protect.yaml. Inside, you’ll find two sets of settings:

Standard Settings

  • login_url is where a user will be redirected when an allow or deny scheme is set but the current user isn’t logged in
  • no_access_url is where a user will be redirected when an allow, deny, or ip_address scheme is set, for allow and deny this will be used when the current user is logged in but not allowed to view the URL they’re trying to access; for ip_address this will be used when the current user’s IP address doesn’t match the list of allowed IPs
  • password_form_url is where a user will be redirected when a password scheme is set but the user has not yet entered in a valid password

Advanced Settings Added in v1.7.3

Most of the time, you will not need to touch the following settings, but it’s worth knowing about them. By pairing these settings with a custom-written add-on and the _addon scheme described below, it’s possible to not only protect pages based on an external authentication system, but forward to an external login that can then redirect users back to wherever they were heading before needing to log in.

  • require_member — when set to true (which it is by default), the protect bundle will require that the current user be logged into Statamic before and protection rules are evaluated, if the current user isn’t logged in, they will be redirected to the defined login URL
  • return_variable — the name of the variable that will hold the URL the current user is trying to get to, return by default
  • use_full_url — when set to true (false by default), the URL set to your return_variable’s value will include your site’s configured _site_url

Each of these settings can be overridden in the _protect variable itself, but most of the time you’ll probably want to use the site-wide defaults.

Using password

There will be times when you want to password-protect one or more files, but don’t want to bother with having people create member accounts just to access a page. That is where using the password scheme comes in. This scheme does not relate to member accounts in any way, only one-off password entry. The setup for this would look something like the following:

---
title: My Secret Content
_protect:
  password:
    allowed: [ "my-password", "another-password" ]
    form_url: /password-entry
---
You can only see me if you have the password.
And since you're reading this, you obviously do.

This tells Statamic to _protect this content file with a password. You provide one or more valid allowed passwords (case-sensitive), and that it should forward people that haven’t entered the correct password to form_url (which is simply another piece of content in your _content folder).

Note: the form_url variable is optional here, if you don’t include it, _protect will use the URL configured in its password_form_url setting.

The Password Form

Next, you’ll need to provide a way for people to enter passwords for URLs. In the above example, form_url is pointing to /password-entry, but this can be anywhere on your site. As an example, let’s say that this content file looks like this:

---
title: Password Entry
_template: password
---
Please enter a valid password to view that page.

Note that in this example we’ve picked the password template for this page. Again, you can name your templates anything you’d like, password is just what we’ve used here. In our password template, we’ll use the protect tag to create a form.

<h1>{{ title }}</h1>

{{ content }}

{{ protect:password_form }}
   {{ if error }}
      <p class="error">{{ error }}</p>
   {{ endif }}

   <p>
      <label for="password">Password</label>
      <input type="password" name="password" value="" id="password">
   </p>

   <p>
      <input type="submit" value="Go">
   </p>
{{ /protect:password_form }}

The protect:password_form tag is going to wrap everything between the tags in an HTML form tag that’s pointing to the appropriate place. Note that the HTML of the form itself is up to you. The only requirements of this form are that the user is entering passwords into a field named password. Other than that you can do anything you’d like.

Redirecting Back

Most of the time, people will get to the password form page by trying to get to the content they want and then being redirected. Note that when users are redirected, the return GET variable contains where they were trying to get to. By default, the protect:password_form tag is set to send people back to whatever the return GET variable is set to. You can manually override this on the tag itself by setting the return parameter.

Note that the value of return is what is used to determine which passwords are and aren’t valid.

Invalid Passwords

If someone submits a password and it isn’t valid, Statamic will redirect the user back to the form, populating the error tag with an error message. In the example above, you can see that we’re printing it out if it’s there. Valid passwords can vary from piece of content to piece of content. This one form is smart enough to handle all password management between password-protected URLs.

Valid Passwords

A valid password is one that matches any of the passwords in the allowed list as configured on the page. This means that you can send three people three different passwords to access the same file, each having their own way in. Additionally, you could also set just one password and send that to 100 people and they can all use the same password.

It should go without saying, but for the sake of completeness here be careful in how you set and give out passwords.

Password Expiration

Each user’s passwords will expire once their session has expired. This length of time is determined by the _cookies.lifetime setting in your main settings.yaml file. To manually invalidate a password, simply remove it from the list of allowed passwords on the page. The next time a user with that password visits this page, they’ll be redirected to the password form just like everyone else.

Using ip_address

This scheme is the simplest of all of them in that people will either be allowed in or not. The setup looks like this, similar to password:

_protect:
  ip_address:
    allowed: [ 127.0.0.1 ]

We set the scheme to be ip_address, and create a list of allowed IP addresses. If the visitor’s IP address matches one of the IP addresses in this list, they will be allowed in, otherwise, they will be redirected to your no_access_url page.

Using allow and deny

These schemes let you protect content based on a member’s logged-in state, any of their profile fields (including roles), and even hook into add-ons to test values. The structure for both allow and deny are virtually identical except that instead of setting the scheme as allow for allow, you set it as deny for deny. Here’s a simple example:

_protect:
  allow
    _logged_in: true

This will only allow logged-in members to view the page.

The Redirect Flow

Any user that hits a page with an allow or deny scheme will be subject to the following flow of redirects:

  • If the user is not logged in, they will be redirected to the login page; this page is either set in the _protect variable on the page, or by default will be the value set to the login_url setting for _protect

  • If the user is logged in, the rules will be evaluated; if the scheme is set to allow and the current user matches all of the rules or the scheme is set to deny and the current user doesn’t match all of the rules, the user will be shown the protected page; if the scheme is set to allow and the current user doesn’t match all of the rules or the scheme is set to deny and the current user matches all of the rules, the user will be redirected to the no-access page; this page is either set in the _protect variable on the page, or by default will be the value set to the no_access_url setting for _protect

Rule Tests

The allow and deny schemes come with seven different tests that you can use to only let in (or keep out) the exact users that you want. Note that some of these tests let you nest other tests within them, meaning that things can get complicated quick.

The tests currently available:

Field Comparisons

The following three tests are simple field checks.

The Straight Field Comparison

With this test, you can check to see if any given field in a member’s profile is either equal to a given value, or is in a list of values that you set. This is where you’d check for roles, like this:

---
title: Members Only
_protect:
  allow:
    roles: [ member ]
---
Hello there, member.

This check will only let in members who have the member role in their list of roles. Again, you can check against any field here, for example, maybe you only want to let in people named Janet:

---
title: Janets Only
_protect:
  allow:
    first_name: [ Janet ]
---
Welcome to the Janet Society, fellow Janet!

All of the other test start with an underscore (_), so any test that doesn’t start with an underscore will be treated as a straight field comparison.

The Complex Field Comparison

This test lets you try more complex comparisons for any given field in a member’s profile. For example, let’s say you only want to let in members whose age is greater than or equal to 18:

---
title: Hi, Adult-in-the-eyes-of-the-Law
_protect:
  allow:
    _field:
      field: age
      comparison: ">="
      value: 18
---
Hmm... looks old enough to me. Welcome!
---

As seen in the above example, the _field test takes three parameters:

  • field is the member profile field to check
  • comparison is the comparison to use, valid values for this are:
    • = or == - equals, when either value is a simple list, this will return true of the lists intersect at all
    • !=, <>, or not - does not equal
    • < - less than
    • <= - less than or equal to
    • > - greater than
    • >= - greater than or equal to
    • has or exists - has a value, matches when the field is not empty
    • lacks or missing - does not have a value, matches when the field is empty or doesn’t exist
  • value is the value to compare against

Although the order that you specify these parameters for the test doesn’t matter code-wise, it will make the most sense to stack them this way. When you do, they can be read top to bottom: “I want to _protect this page, allowing members whose age is >= 18.”

The Logged-In Check

This test checks to see if the current user is logged in. There are other ways to check for this, but this test has been included as a fast way for site developers to quickly protect some pages.

---
title: Logged-In People Only
_protect:
  allow:
    _logged_in: true
---
I see that you're logged in. Delicious.

Anyone that is logged into the site will be able to view this page.

Multiple-Rule Grouping

The following three tests are ways to group multiple tests together into complex rule structures. Each of these point to lists of other rules that must match in certain ways to make them true, including other nested multiple-rule groupings.

Checking for Any

Check that any of a given list of rules is true. For example, let in anyone that’s an admin or whose first_name is Elroy:

---
title: Welcome Admins & Elroys
_protect:
  allow:
    _any:
      -
        roles: [ admin ]
      -
        first_name: [ Elroy ]
---
Ah yes, my good man Elroy and/or site admin...
Checking for All

Check that all of a given list of rules is true. For example, let in anyone that’s an admin and their first_name is Elroy:

---
title: Welcome Admin Elroys
_protect:
  allow:
    _all:
      -
        roles: [ admin ]
      -
        first_name: [ Elroy ]
---
Checking for None

Check that none of a given list of rules is true. For example, let in anyone that is not an admin or whose first_name is Elroy:

---
title: Welcome Members and/or People Not Named Elroy
_protect:
  allow:
    _none:
      -
        roles: [ admin ]
      -
        first_name: [ Elroy ]
---
Hello members or people not named Elroy! We've been expecting you.

Add-on–Based Tests

There is one other check, which lets you tap into bundles and add-ons via their APIs.

Add-on API Test

Check that the value returned from an add-on’s API method matches a comparison to a given value.

---
title: I See That You're Karma-Rich
_protect:
  allow:
    _addon:
      method: 'karma:get_points'
      comparison: '>='
      value: 100
---
A message for those that have done good deeds...

This test will get the value from the karma (a made-up add-on for the sake of this example) add-on’s get_points API method. Let’s say that that method will return the number of karma points for the currently logged in user. The value returned will then be compared using the >= comparison with the value 100. If that’s true, the user will be allowed through.

Mixing Rule Tests

With the _all, _any, and _none tests, you can quickly whip up complex content protection rules in no time. Here is an example of nesting rule tests mixing a bunch of the above examples:

---
title: Jumping Through Hoops
_protect:
  allow:
    _any:
      -
        roles: [ admin ]
      -
        _all:
          -
            first_name: [ Elroy, Janet ]
          -
            roles: [ member ]
      -
        _add-on:
          method: 'karma:get_points'
          comparison: '>='
          value: 100
---
You made it!

This rule says to let in anyone that matches any of the following:

  • Has the admin role
  • Has the member role and has first_name of Elroy or Janet
  • Has at least 100 karma points

The Login Form

Users that are not logged in and are trying to get to a page protected by allow or deny will be redirected to the login URL as either set in the _protect variable itself, or the configured login_url. For this redirect, the page the user was trying to reach will be included as the return GET variable.

The member login form has the ability to use this variable if it exists, but this feature is not turned on by default. To use this feature, set the allow_request_return parameter to true on the {{ member:login_form }} tag.

Read more about the member login form.

The No Access Page

You can choose to send people anywhere when they don’t have access to a page. This redirect will simply put the user at a new place on your site, no matter when you call it. If you don’t want to create a custom no-access page, you can have people forward to the site’s 404 page.

If you do want to create a custom page for no-access, you can now use the _response variable to set the HTTP response code for the page so that it’s something other than 200. For example, maybe you want to set your No Access page to be status code 403, which means “Forbidden,” you can do so like this:

---
title: No Access
_template: no-access
_response: 403
---
Sorry, you don't have access to that page.

This article was last updated on March 30th, 2016. Find an error? Please let us know!