Steps to use Livewire with Acorn 4.0

Hej,

since roots/acorn:4.0 we have official support forlivewire/livewire (Enhanced Router Compatibility (Including Livewire Support) And Filters For Early Service Container Overrides by broskees · Pull Request #291 · roots/acorn · GitHub).

I tried to follow the mentioned steps in the related PR and found some needed changes (for me). But unfortunately, although it first seems to work, I ran into an issue related to the session / nonce / CSRF.

Currently the @livewireScripts or the ESM version @livewireScriptConfig won’t set a CSRF token, which will break on events like wire:click or wire:submit.

Has anyone a hint, why the csrf_token() will return null? Am I missing something?

Best,
Daniel

1 Like

… I tried to debug this and am quite not sure if I miss something fundamental.
It seems that the session will not be persistent between different requests. Secondly, at every point in code, the function csrf_token() will return null.

However, at the moment, I’m applying the following workaround to ensure that the Livewire components function properly:

In the layout file, I add:

<meta name="csrf-token" content="{{ Str::random(40) }}">

This will provide the Livewire JS code the required token.

1 Like

I’ve encountered same issue in the past where the CSRF token wasn’t generated on pages managed by WordPress itself.

I guess it’s due to WordPress pages aren’t managed by the Laravel router, so doesn’t trigger a middleware that initiates the session which generates the token.

This is my current workaround:

/**
 * Start Laravel Session after WordPress loaded.
 */
add_action('wp_loaded', fn () => app('session')->isStarted() || app('session')->start());
3 Likes

How did you both manage to handle the multiple instances of livewire being loaded?

Whenever I use wire:navigate this is what happens

I assumed you followed this guide,

https://github.com/iniznet/csrf-token-workaround/commits/main

Then, this link should help you,
https://livewire.laravel.com/docs/installation#manually-bundling-livewire-and-alpine

Thanks, I was already loading everything through the livewireconfigs:

collect([‘wp_head’ => ‘@livewireStyles’,
‘wp_footer’ => ‘@livewireScriptConfig’,
])->each(fn ($directive, $hook) =>
add_action($hook, function () use ($directive) {
echo Blade::render($directive);
})
);

My issue is that using wire:navigate keeps re-initiating everything whenever I press on a link.
grafik

Here is a working solution that I use:

This is an Acorn bootloader as a WP Must Use plugin:

<?php

use Roots\Acorn\Application;
use Sentry\Laravel\Integration;
use Roots\Acorn\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;

/**
 * Plugin Name:  Acorn Bootloader
 * Plugin URI:   https://gitlab.com/rehashx/rehash-wp
 * Description:  Acorn must be booted in order to use it. This plugin handles Acorn booting.
 * Version:      1.0.0
 * Author:       Rehash
 * License:      MIT License
 */
if (! function_exists('\Roots\bootloader')) {
    wp_die(
        __('You need to install Acorn to use this site.', 'domain'),
        '',
        [
            'link_url' => 'https://roots.io/acorn/docs/installation/',
            'link_text' => __('Acorn Docs: Installation', 'domain'),
        ]
    );
}

function acorn_bootloader(): void
{
    $app_builder = Application::configure();
    $app_builder->withExceptions(function (Exceptions $exceptions) {
        Integration::handles($exceptions);
    });
    $app_builder->withMiddleware(function (Middleware $middleware) {
        $middleware->web(append: [
            ValidateCsrfToken::class,
        ]);
    });
    $app_builder->boot();
}

add_action('after_setup_theme', 'acorn_bootloader');

function acorn_bootloader_start_session(): void
{
    $session = app('session');

    if (!$session->isStarted()) {
        if (!empty($_COOKIE[$session->getName()])) {
            $session->setId($_COOKIE[$session->getName()]);
        }
        $session->start();
    }
}

add_action('init', 'acorn_bootloader_start_session');

function acorn_bootloader_save_session(): void
{
    $session = app('session');

    if ($session->isStarted()) {
        $session->save();
    }
}

add_action('shutdown', 'acorn_bootloader_save_session');
1 Like

Thank you it works !

I build manually livewire and alpine (with some plugin) with radicle, and that was the missing part to work nicely :wink:

For information i just have a LivewireServiceProvider with

public function register(): void
    {
        add_filter('wp_head', function () {
            echo Blade::render('@livewireStyles');
        });

        add_filter('wp_footer', function () {
            echo Blade::render('@livewireScriptConfig');
        });

        add_action('wp_loaded', fn () => app('session')->isStarted() || app('session')->start());
    }

and app.ts is

// We add Livewire/Alpinejs manually to be more flexible
// @see https://livewire.laravel.com/docs/alpine#manually-bundling-alpine-in-your-javascript-build
import {Livewire, Alpine} from '../../vendor/livewire/livewire/dist/livewire.esm';
import resize from '@alpinejs/resize'

Alpine.plugin(resize)

Livewire.start()

import.meta.webpackHot?.accept(console.error);

cheers.

You are missing:

add_action('shutdown', 'acorn_bootloader_save_session');

shutdown is the last WP action and therefore I am using it to save the session to disk.

1 Like

Thank you; this fixed a lot of things. However, two issues still remain unfixed for me:

  1. The error related to the bootloader part:
    Method Roots\Acorn\Application::configure does not exist

  2. The Session::token() remains the same only when I send a Livewire XHR request.
    Cookies aren’t generated on a normal page request (considering that I’m using local server).

The error related to the bootloader part

Their code is for the Acorn v5 beta: Release v5.0.0-beta.1 · roots/acorn · GitHub

1 Like

Oh, I thought this thread was only for Acorn 4.0.
Thank you. :rose:

I’ve implemented the solutions provided in this thread and it works perfectly:

<?php

declare(strict_types=1);

namespace App\Providers;

use Illuminate\Session\SessionManager;
use Illuminate\Support\ServiceProvider;

class SessionServiceProvider extends ServiceProvider
{
	public function register(): void
	{
		add_action('init', $this->startSessionManually(...));
		add_action('shutdown', $this->saveSessionManually(...));
	}

	/**
	 * Starts the session manually during the WordPress 'init' hook.
	 *
	 * This method ensures that a Laravel session is initialized within
	 * WordPress, allowing for session-based functionality across
	 * the application. If a session is not already active and a session
	 * cookie is present, it sets the session ID and starts the session.
	 */
	public function startSessionManually(): void
	{
		$session = app('session');

		if (! $session instanceof SessionManager || $session->isStarted()) {
			return;
		}

		if (empty($_COOKIE[$session->getName()])) {
			return;
		}

		$session->setId($_COOKIE[$session->getName()]);
		$session->start();
		$session->regenerate(false); // Force reloading session data.
		session('user')?->setSessionManager($session);
	}

	/**
	 * Saves the session manually during the WordPress 'shutdown' hook.
	 *
	 * This method ensures that any changes to the session are properly
	 * saved before the script ends. It checks if the session is active
	 * and saves it, preserving session data across requests.
	 */
	public function saveSessionManually(): void
	{
		$session = app('session');

		if (! $session instanceof SessionManager || ! $session->isStarted()) {
			return;
		}

		$session->save();
	}
}

But the Illuminate Validator class of which the MessageBag is passed to the front-end does not work anymore:

if ($validator->fails()) {
	return redirect('/inloggen')
		->withErrors($validator)
		->withInput();
}

When I disable the process of startin the session manually:

// add_action('init', $this->startSessionManually(...));

The errors from the Validator MessageBag are shown again.
I’m not sure yet about what the cause could be but being stuck for a few hours now I hope someone can help me.

EDIT:
Using this ugly solution it is possible to display the errors:

if ($validator->fails()) {
	session()->flash('errors', $validator->errors());

	return redirect()->back()->withInput();

	// Does not work because of session mismanagement?
	// return redirect('/inloggen')
	// 	->withErrors($validator)
	// 	->withInput();
}
1 Like