Caching despite using sessions and cookies – is it possible with any tricks?

Setup

For a Kirby site which needs a basic access restriction, I built a custom frontend authentication.

Since the site is intended for a closed group of visitors and should be as easy as possible to use, I would rather not mess around with user management. Instead, visitors are redirected to a simple login form with just a password field whenever they open any URL of the site. One password for all visitors is actually a sufficient level of security in this case. (The site is for a live event. And with this solution, I can simply print out the password and post it in each conference room.) Successful authentication is then either stored using the PHP session or using a custom cookie for permanent login – depending on the visitor’s choice.

Basically, this solution is just a prettier and simpler (because there is no username field) alternative to using .htpasswd to protect the whole site.

In combination with the custom login/logout page, I use a route:before hook in the config.php to handle redirects to the login page in case a visitor is not yet authenticated.

"hooks" => [
  "route:before" => function ($path) use (
    $permanentLoginCookieName,
    $permanentLoginCookieValue
  ) {
    //Detect the preferred language of the website visitor
    $detectedLang = kirby()->detectedLanguage();
    $redirectPath = !empty($path) ? $path : $detectedLang->code();

    // Determine the password slug based on the detected language
    $passwordSlug =
      $detectedLang && $detectedLang->code() === "de"
        ? "passwort"
        : "password";

    // Check if either the session or the permanent login cookie is set
    // to determine if the user is authenticated
    $sessionAuth = kirby()
      ->session()
      ->get("myevent-frontend-authenticated", false);
    $cookieAuth = false;
    if (Cookie::exists($permanentLoginCookieName)) {
      $cookieAuth =
        Cookie::get($permanentLoginCookieName) ===
        $permanentLoginCookieValue;
    }

    // If the user is not authenticated, redirect to the password page
    if (!$sessionAuth && !$cookieAuth) {
      // Exclusions from the password page redirect:
      // - the password page itself (to prevent infinite loops)
      // - certain filetypes in the root directory
      // - the fallback socialshare image
      if (
        $path !== $passwordSlug &&
        $path !== "de/$passwordSlug" &&
        $path !== "en/$passwordSlug" &&
        !preg_match(
          "/^[^\/]+\.(ico|png|svg|txt|webmanifest|xml)$/",
          $path
        ) &&
        !preg_match(
          "/^media\/site\/.*\.(jpg|jpeg|png|gif|webp|avif)$/",
          $path
        )
      ) {
        go(
          ($detectedLang ? $detectedLang->code() . "/" : "en/") .
            $passwordSlug .
            "?redirect_to=" .
            urlencode($redirectPath)
        );
      }
    }
  },
],

This works fine and is very convenient for website visitors.

Problem

The solution has one downside: The pages cache is not available because of the use of sessions and cookies in the route:before hook. Even trying to trick Kirby by caching pages manually using a custom cache in a route:after hook didn’t work.

Since I don’t use sessions and cookies for any kind of personalization, and all website visitors are served the exact same content after they successfully authenticated, caching the content “after the access gate” would make sense. It is not absolutely necessary because Kirby is fast enough, even without the cache. But at least to be prepared for future bigger events, caching would be a good idea.

Question

Is there any way to bypass Kirby’s automatic cache deactivation triggered by the use of sessions and cookies without having to implement crazy hacks?

Not sure if this will be possible with Kirby on its own. There’s an Issue on Github where a new solution to Kirbys caching mechanisms regarding sessions is discussed.

You can read @lukasbestle answer outlining the new implementation that was introduced with 3.7.0 (?). The merged PR can be found here.

Maybe a solution could be in the .htaccess file. For all routes requiring authentication, check if the session cookie with the expected value is set. If yes deliver the static html file from cache (assuming staticache is used). If it’s not set redirect to a page with the password form for that page and set the session cookie after password validation.

# Check before if the request requires authentication 
<If "%{HTTP_COOKIE} =~ /(^|; )myevent-frontend-authenticated\s*=\s*password-value(;|$)/>
  # Deliver the static html file from cache. See how the Kirby Staticache plugin does this.
</If>

This is just a very very rough outline and will most likely require some more .htaccess rules / php code for redirecting between the password form page and the actual requested content.

Absolutely not tested : )

Thanks a lot! I will check this.

The idea of handling the authentication check in the .htaccess instead of using the route:before hook, might help. I will play around with that.

instead of using the full pages cache you could wrap the performance critical parts in a „partial cache“ (storing data or full html-output whatever is more suiteable for your usecase). you can read a bit more about those in my caching introduction post Remember this - Caching in Kirby

I’d also go for the .htaccess route. You could actually flip it around: Serve the login page if the cookie is not set yet. On the login page, set the cookie and reload the page. Then .htaccess will let the request pass through to the site, which can use caching as normal as long as you don’t access the cookie from your templates.

1 Like

I will try that next week. Some not yet finished templates have higher priority today. :wink:

I just have to see which web server will be used. I guess nginx, which I have no experience with. But perhaps I will simply use PHP for the “redirect to password page” logic. That is not as performant as using .htaccess or the nginx configuration. But in combination with staticache it should be absolutely enough for this project.

Taking the authentication check out of Kirby did the trick. Access gate works and Kirby cache works.

1 Like