Simple Password Protection for Craft CMS 5 Pages (No Plugin Required)
Simple password protection for individual Craft CMS 5 pages using native session management—no plugins required. Perfect for keeping casual visitors out of exclusive content while giving content managers complete control.
Need to protect specific pages in your Craft CMS 5 site from casual visitors? While Craft's built-in user system is perfect for complex permissions, sometimes you just need a simple password gate to keep looky-loos out. Here's how to build a lightweight, plugin-free solution using Craft 5's native session management.
The Challenge
Content managers often need to restrict access to certain pages—maybe it's confidential research, internal documentation, or exclusive content. Setting up full user accounts feels like overkill when you just need a simple password prompt.
Our Approach
Building on Noah Read's excellent tutorial for Craft 3, we'll create an updated system for Craft 5 that:
- Lets content managers set passwords on individual entries
- Uses Craft 5's session management for security
- Requires zero plugins
- Is simple for editors to use
Step 1: Add a Password Field
First, add a plain text field to your entry type called secretWord
(or whatever you prefer). When editors want to protect a page, they simply enter a password in this field.
Step 2: Create the Login Page Template
Create a template at /templates/loginPage.twig
:
{% extends "_layouts/basic" %}
{% set returnPageId = craft.entries.id(craft.app.request.getParam("id")).one() %}
{% block content %}
{% if craft.app.request.getBodyParam('access-code') %}
{% if returnPageId.secretWord == craft.app.request.getBodyParam('access-code') %}
{# Store authentication in session #}
{% do craft.app.session.set('authenticated_page_' ~ returnPageId.id, true) %}
{% redirect "/" ~ returnPageId.uri %}
{% else %}
<p>Your access code was incorrect</p>
<form action="/loginPage?id={{ returnPageId.id }}" method="POST">
<input type="text" name="access-code" id="access-code" />
<input type="submit" value="Submit" />
</form>
{% endif %}
{% else %}
<h2>You need to login!</h2>
<form id="check-code" action="/loginPage?id={{ returnPageId.id }}" method="POST">
<input type="text" name="access-code" id="access-code" />
<input type="submit" value="Submit" class="submit" />
</form>
{% endif %}
{% endblock %}
Step 3: Update Your Entry Template
Add the password check logic to your entry template:
{# Check if page requires authentication and user isn't authenticated #}
{% if entry.secretWord != "" and not craft.app.session.get('authenticated_page_' ~ entry.id) %}
{% redirect "/loginPage?id=" ~ entry.id %}
{% else %}
{# Your protected content here #}
<article class="container flow">
{{ entry.longformContent }}
{# Rest of your content #}
</article>
{% endif %}
How It Works
- Content Manager Sets Password: When creating/editing an entry, if the
secretWord
field has a value, the page becomes protected. - Visitor Encounters Protection: When someone visits a protected page, they're redirected to
/loginPage?id=[entry-id]
. - Authentication Check: The login page validates the submitted password against the entry's
secretWord
field. - Session Storage: On successful authentication, we store
authenticated_page_[entry-id]
in the user's session. - Future Access: Subsequent visits to the protected page check for this session variable instead of prompting for the password again.
Why Sessions Over Cookies?
While cookies might seem simpler, sessions offer several advantages:
- Server-side security: Authentication data is stored on the server, not in the browser
- Built-in expiration: Sessions automatically timeout with user inactivity
- Harder to bypass: Users can't simply create their own authentication cookie
- Craft integration: Uses Craft's native session handling
Craft 3 to Craft 5 Migration Note
If you're upgrading from Craft 3 (like Noah's original tutorial), note that craft.request.getPost()
has been replaced with craft.app.request.getBodyParam()
in Craft 5. All code examples in this post are updated for Craft 5 compatibility.
Optional: Adding Logout Functionality
Want to let users log out? Add this to your protected pages:
{% if entry.secretWord != "" and craft.app.session.get('authenticated_page_' ~ entry.id) %}
<a href="/logout?id={{ entry.id }}">Logout</a>
{% endif %}
And create a simple logout template:
{% set pageId = craft.app.request.getParam("id") %}
{% do craft.app.session.remove('authenticated_page_' ~ pageId) %}
{% set entry = craft.entries.id(pageId).one() %}
{% redirect "/" ~ entry.uri %}
Security Considerations
This approach is perfect for keeping casual visitors out, but remember:
- Passwords are stored in plain text in your Craft database
- Not suitable for truly sensitive content that requires robust security
- Sessions can be hijacked in theory (though unlikely for most use cases)
For higher security needs, consider Craft's built-in user system or a dedicated authentication plugin.
Wrapping Up
This lightweight solution gives content managers the flexibility to protect pages as needed without the overhead of user accounts or plugins. It's perfect for internal documentation, exclusive content, or any scenario where you need to keep the curious at bay.
The beauty of this approach is its simplicity—both for developers to implement and for content managers to use. Just add a password to the field, and the page is protected. Remove it, and the page becomes public again.
For more Craft CMS tutorials and tips, follow along on this here blog, YouTube channel, or connect with me on Mastodon.