I’ve done this a few times before and this is how I usually get it done.
Set Per Entry Password
Within Craft create a new Plain Text field for the entry you would like password protected. The password for the entry can be a phrase, random string of numbers and letters, whatever.
Create a Login Template
In Craft, set up a route to the login template including the slug of the page you are password protecting. For example, blog/{slug}/login.
Within your login template create a form that:
- Contains a field for your password
- Posts to itself
It should look something like this:
<form id="check-code" action="/{{ craft.request. getPath() }}" method="POST">
<input type="text" name="access-code" id="access-code" />
<input type="submit" value="Submit" class="submit" />
</form>
Check Form Submission against Entry’s Password
Within the login template wrap the form in a conditional that checks for the password field’s POST value when the page is reloaded following successful form submission (I use Parsley for pre-submission form validation).
{% if craft.request.getPost('access-code') %}
{% else %}
<form action="/{{ craft.request. getPath() }}" method="POST">
…
</form>
{% endif %}
Now, select the entry using craft.request.getSegment() and compare its password field against the post data from our form submission. If they don’t match then create a duplicate of our form from below with some kind of error message (I’m sure there’s a way to include the forms, without repeating markup using a macro or something). This allows the user to try again if they got it wrong.
{% if craft.request.getPost('access-code') %}
{% set entry = craft.entries.section({entrySection}).slug(craft.request.getSegment(2)).first() %}
{% if entry.passwordField == craft.request.getPost('access-code') %}
{% else %}
<form action="/{{ craft.request. getPath() }}" method="POST">
<p>Your access code was incorrect</p>
<input type="text" name="access-code" id="access-code" />
<input type="submit" value="Submit" />
</form>
{% endif %}
{% else %}
<form action="/{{ craft.request. getPath() }}" method="POST">
…
</form>
{% endif %}
Now, if the password and the post data do much we need to set a cookie and redirect the user to the entry’s template. In order to set the cookie, I’m using Andrew Welch’s Cookies plugin. In this case I’m setting the cookie to expire with the current browser session, but you can set it to be whatever you want following PHP’s set cookie parameters.
{% if craft.request.getPost('access-code') %}
{% set entry = craft.entries.section({entrySection}).slug(craft.request.getSegment(2)).first() %}
{% if entry.passwordField == craft.request.getPost('access-code') %}
{{ setCookie( craft.request.getSegment(2), entry.passwordField, 0, "/") }}
{% redirect "blog/"~craft.request.getSegment(2) %}
{% else %}
<form action="/{{ craft.request. getPath() }}" method="POST">
…
</form>
{% endif %}
{% else %}
<form action="/{{ craft.request. getPath() }}" method="POST">
…
</form>
{% endif %}
Check for Cookie in Entry’s template
At the top of your entry template check if the entry has a password set and if the browser does not have a cookie set, whose name matches the entry’s slug and whose value matches the entry’s password. If those conditions are met then the user has not logged in and will be redirected to the login page. Otherwise, the template will continue loading and the user will see the entry template.
{% if (entry.passwordField != "") and (getCookie( entry.slug ) != entry.passwordField) %}
{% redirect "blog/"~entry.slug~"/login" %}
{% endif %}
Bonus
Once you’ve tested out that this process is working you, or other credentialed site users, may not want to have to enter in the password each time. Simply add another conditional to the entry template’s password/cookie check.
{% if (entry.passwordField != "") and (getCookie( entry.slug ) != entry.passwordField) and (currentUser == false) %}
{% redirect "blog/"~entry.slug~"/login" %}
{% endif %}
Now, if the user is logged in to the site the conditional will be bypassed and they will be able to see the content without getting redirected to the login page.
This is pulled from a blog post I wrote a while ago.