👀 Get a glimpse of Kirby 5 Learn more
Skip to content

Access forbidden

You can use Kirby’s user system to restrict access to parts of your website that should only be available to certain users, for example, a clients’ area.

In this recipe we will guide you through

  • creating a login form for the front-end
  • providing a logout link
  • protecting pages from unauthenticated users

Do not use the file cache for pages with protected content, otherwise your content might be visible to unauthenticated users.

User management

All users, including front-end users, are managed via the Panel.

Creating roles without panel access

By default, Kirby provides a single admin role with access to the Panel and without any restrictions. Front-end users usually shouldn’t have access to the Panel at all so we first need to create a user role without Panel access.

In your /site/blueprints/users folder, create a new file called client.yml with the following content:

/site/blueprints/users/client.yml
title: Client
permissions:
  access:
    panel: false

If you add a new user in the Panel and assign the client role, this user cannot login to the Panel.

You can create as many roles without Panel access as necessary to determine which part of your front-end should be accessible to which role.

The login page

For the login page, we use an unlisted page with some basic information that gets its own template. Create a /content/login folder with a login.txt text file inside it. We use the text file to store the information for the form.

By creating a content file with these fields, we can make the form more dynamic and translate form labels and error messages in a multi-language installation if required. You could also hard-code this in your template instead.

/content/login/login.txt
Title: Login

----

Alert: Invalid user or password

----

Username: User name

----

Password: Password

----

Button: Log in

The corresponding blueprint for the Panel

To make this file editable in the Panel, create a blueprint for this page:

/site/blueprints/pages/login.yml
title: Login
icon: 🔐

fields:
  alert:
    label: Alert text
    type: text
  username:
    label: Label for username
    type: text
  password:
    label: Label for password
    type: text
  button:
    label: Button text
    type: text

The login template

In the login template, create the login form and a container for the error messages:

/site/templates/login.php
<?php snippet('header') ?>

<h1><?= $page->title()->html() ?></h1>

<?php if($error): ?>
<div class="alert"><?= $page->alert()->html() ?></div>
<?php endif ?>

<form method="post" action="<?= $page->url() ?>">
  <div>
    <label for="email"><?= $page->username()->html() ?></label>
    <input type="email" id="email" name="email" value="<?= get('email') ? esc(get('email'), 'attr') : '' ?>">
  </div>
  <div>
    <label for="password"><?= $page->password()->html() ?></label>
    <input type="password" id="password" name="password" value="<?= get('password') ? esc(get('password'), 'attr') : '' ?>">
  </div>
  <div>
    <input type="submit" name="login" value="<?= $page->button()->html() ?>">
  </div>
</form>

<?php snippet('footer') ?>

The controller

To handle the form submission we create a login controller to keep the logic out of the template.

/site/controllers/login.php
<?php

return function ($kirby) {

  // don't show the login screen to already logged in users
  if ($kirby->user()) {
    go('/');
  }

  $error = false;

  // handle the form submission
  if ($kirby->request()->is('POST') && get('login')) {

    // try to log the user in with the provided credentials
    try {
      $kirby->auth()->login(get('email'), get('password'));

      // redirect to the homepage if the login was successful
      go('/');
    } catch (Exception $e) {
      $error = true;
    }

  }

  return [
    'error' => $error
  ];

};

The login will redirect the user back to the homepage if it was successful. Otherwise the error variable is returned to the template as true and the alert is displayed. If you want to redirect the user to a different page, change the path in the go() method.

The logout

For the logout we don’t need a real page. A simple URL to send logged-in users to is enough.

/site/config/config.php
return [
  'routes' => [
    [
      'pattern' => 'logout',
      'action'  => function() {

        if ($user = kirby()->user()) {
          $user->logout();
        }

        go('login');

      }
    ]
  ]
];

By adding the code above to your config file, Kirby will register a new route to http://yoursite.com/logout. When you open that URL, the action method is called and a logged-in user will be logged out. Afterwards the script will redirect the user to the login page.

As soon as the user logged in, we display a logout link in the menu or somewhere else on the page. Here is the menu from Kirby’s Starterkit with an additional li element that appears when the user has logged in.

/site/snippets/header.php
<nav id="menu" class="menu">
  <?php foreach ($site->children()->listed() as $item): ?>
    <?= $item->title()->link() ?>
  <?php endforeach ?>
  <?php if ($user = $kirby->user()): ?>
    <li>
      <a href="<?= url('logout') ?>">Logout</a>
    </li>
  <?php endif ?>
</nav>

Protecting Content

With the login and logout processes in place, we can finally protect our content.

Protecting entire pages

You can protect entire pages from unauthenticated users by adding the following line at the top of a template:

<?php if (!$kirby->user()) go('/') ?>

// rest of the template

This will redirect all unauthenticated visitors to the home page.

Instead of adding this code to each template, you can also put your logic into a route, for example, when restricting access by other criteria than the template.

Protecting parts of a page

In the same way, you can hide parts of a page from unauthenticated users:

<?php snippet('header') ?>

<h1><?= $page->title()->html() ?></h1>

<?= $page->text()->kirbytext() ?>

<?php if ($kirby->user()): ?>
Top Secret: the meaning of life is…
<?php endif ?>

<?php snippet('footer') ?>

Protecting content by role

The above examples don’t differentiate by user role but grant access to these pages to all logged-in users. If you have multiple front-end user roles and want to restrict access to certain pages or parts of pages to particular roles, you can ask for the current user’s role like this:

<?php if (($user = $kirby->user()) && $user->role()->id() === 'client'): ?>
This part of the page is only visible for
clients with the role clients
<?php endif ?>

Remarks

Note that this recipe only provides a basic example to give you an idea how to handle access restrictions. You can extend this into a powerful user area, with user sign-on, password reset, etc.

The process described here doesn’t prevent access to your assets (images, video, documents etc., which will still be accessible to anyone who guesses the URL to these files). We will deal with this in a separate recipe.

Author