RFC: Rework Flash Message implementation

As you might now, I’m at war with server-side Sessions. Thus (and for a couple of other reasons) I’m uneasy with our current Flash Message implementation.

TL;DR Instead of automagically starting a session as soon as one packages uses Flash Messages, I suggest to make the implementation configurable and I created an alternative implementation to show that this works in fact

Current implementation

Currently Flow (and any other Framework I checked) uses a very simple
approach to Flash Messages:

  1. A string (or sometimes a more complex object including title, categorization
    etc) is stored into the session before a HTTP redirect is triggered.
  2. Upon displaying of the target action, any Flash Message is read and
    removed from the session and rendered to the client

Motivation to change this

  1. Most importantly the current implementation defeats caching[¹]
  2. Server-side Sessions are not easy to scale
  3. Storing objects in the session is error prone because the object’s
    implementation can change
  4. The FlashMessage container is filled up with old messages if they are
    not rendered
  5. An exception is thrown if Flash Messages are used in a context that
    doesn’t support sessions (e.g. from the CLI)

Suggestion

I suggest to make the Flash Message implementation a configurable strategy.
By default we could use the current, session-based, implementation because
it requires no further adjustment by the user.
But it should be possible to change that globally – or maybe even per request
pattern (for example in a CLI request it could be rendered to the console
immediately)

A simple implementation we could “ship” by default (in addition to the session-based
one) is one that simply uses a cookie to send the Flash Messages so they can be read
via JavaScript.
I created a simple example of that and it works like a charm:

Other possible implementations

The possibilities to pass data to a successive request are limited, but
there a a few possible other approaches I can think of supporting the idea
to make this a configurable strategy:

Query argument

The Flash Message(s) could be passed to the target URL via query arguments
on the Location HTTP header.
But as that’s a possible XSS attack vector, these arguments would have to
be signed (i.e. using a JWT with an async, see example using RS256)

Web Notifications

The Notifications API,
possibly together with the Push API
could be used to render messages in the user’s browser.

Further considerations

Some (more technical) thoughts on this:

Security

Using a cookie to store the Flash Message content as plaintext might pose
a risk, because that cookie could be altered by the client.
I added some lines to the packages README
outlining why I don’t consider this an issue.

Changing the PHP API

Not the scope of this RFC but maybe worth mentioning:
In my opinion one should not deal with Flash Messages from within the
MVC code as controllers should act on a different level of domain logic.
Instead I could imagine to have some kind of basic Rules Engine that
configures Flash Messages based on Domain Events.
This would also allow to re-use the concept outside of MVC.


[¹]: By default the Flash Message is displayed on redirect which is a GET (i.e. safe) request and should be cacheable according to the HTTP specification.
That’s the reason why we can’t use Flash Messages in the Neos Frontend today unless we mark the whole Fusion prototype uncached

3 Likes

Yes. Yes. Yes. The current FlashMessages are a broken design for the reasons given.

Some considerations as to the reasons:

  1. The main reason for it defeating caching is that the flash messages are (supposed to be) rendered directly inside the templates, not that they are stored in session. This could also be solved by making fetching flash messages an own (uncached) request.

  2. This is true, but I think scalability of sessions is something that is solved by 3rd party solutions

  3. Good point, though that probably happens once an Aeon and even then only affects open sessions with stored flash messages. A counter-measure could be an explicit serialization/deserialization API that doesn’t depend on PHP’s methods. The deserialization API could then be made b/c.

Anyway, I’m full in on making the FlashMessage implementation configurable and I like the suggested cookie-storage solution. The only question I have is, if it makes sense to make the actual FlashMessageContainer configurable or just provide a storage layer that is configurable. In any way I could imagine making either one an Interface and enabling the configuration through Objects.yaml as a simple solution - though that would not provide a solution for per-request (CLI/Web).

Also, the configurable Implementation most likely needs an explicit hook on the redirect and be able to potentially enrich the redirect URL/response, as seen in your cookie and also in the Query Argument solution.

I am unsure though if the Web Notifications API is the right tool for flashMessages. FlashMessages are supposed to raise very generic application level notifications, like for validation errors or generic feedback on application state-altering requests. In those cases, it is not viable to push those messages through an platform-wide notification system, which also needs to be granted permission first.

1 Like

Thanks for your feedback!

Is that really something you suggest and, if so, could you elaborate how you envision that, an extra AJAX request for every GET?

Right. The reason is that they are stored on the server-side, thus need an uncached request to transfer them to the client.

I could imagine a custom web applications that uses this channel for flash message feedback but I added this example merely as theoretical possibility.

Direct interaction with $this->flashMessageContainer is discouraged in favor of $this->addFlashMessage(). I would suggest to hook in there (and possibly keep the FlashMessageContainer injected with deprecation annotation for greater b/c).

Yes, I was thinking of a signal in redirectToUri() that passes request and response to any configured slot.

To make this configurable per Request (the Neos Backend could use a different implementation for example) we could re-use the RequestPatterns that exist already…

I’ll prepare a WIP Pull-Request to see how this works out in practice

This could also be solved by making fetching flash messages an own (uncached) request.

Is that really something you suggest and, if so, could you elaborate how you envision that, an extra AJAX request for every GET?

Not something we should provide as an out-of-the-box solution, but something we should maybe make easier to achieve than blindly adding <f:flashMessages /> to the templates. That is just so convenient, but this specifically breaks all (caching) hell lose :confused:

Direct interaction with $this->flashMessageContainer is discouraged in favor of $this->addFlashMessage().

So you’d like to get rid of the Container layer in order to enforce usage from Controllers only? I mean that would totally solve the above issue and make so much sense from how they should work and be used, but hell that would be breaking :slight_smile:

Yes, I was thinking of a signal in redirectToUri() that passes request and response to any configured slot.

:thumbsup:

Here’s a first PR implementing two Storage strategies (Cookie & Session) and switching the default to use a Cookie: https://github.com/neos/flow-development-collection/pull/1061

In my previous considerations I forgot that we can use the cookie on the server-side too, so this could be done completely backwards compatible. But I decided to go for a more flexible approach because this allows us to introduce “FlashMessage” namespacing (i.e. prevent Neos Backend Flashmessages to be displayed in the Frontend vice versa and allow plugins to have their own FlashMessages, …)

Hey, interesting topic and nice it got attention :slight_smile: I like the ideas mentioned, but was wondering 1 thing: would there be anything against a strategy that would use response headers?

Hi Rens and thanks for your feedback, very nice to read you :wink:
The FlashMessageCookieStorage which the PR suggests to make the default strategy actually uses a HTTP response header, namely the Set-Cookie header.
AFAIK that’s the only header (in addition to the Location header) that is accessible from redirects (see How to forward headers on HTTP redirect - Stack Overflow).
Also it’s the only one that is readable from JavaScript.

So the only other way (I can think of) to send information to the redirected page is to use query parameters on the Location header but they have to be secured as mentioned above.

Cheers

Ah, check. I didn’t think of redirects. I just thought about a simple way to add some feedback to the current response in a very lightweight way, without it being persisted in some way. Wouldn’t using the cookie still have the problem below?

The FlashMessage container is filled up with old messages if they are not rendered

Yes, but

  1. It’s not as “bad” because the data only lies in a session cookie on the browser that doesn’t cost server resources and is cleared upon browser restart anyways
  2. It’s easier to circumvent if you render the FlashMessages via JavaScript because that would usually just check for the cookie to be set whilst the current approach requires the flashMessage ViewHelper in every template that can be rendered after a redirect
1 Like

One consideration regarding the cookie usage though:

  • if the cookies will be sent back to the server (which they will unless deleted client-side), this will prevent any proxy-caching from working. A set cookie implies some sort of private/personal information.

  • the optimal case would be a one-time-delivery guarantee. So a JavaScript that reads the flashmessages from the cookie, stores them in localStorage for further/later processing and deletes the cookie immediately

Right, but that’s the case anyways. Currently we send a cookie with the session Id.

The JS part is not included, but we could add a snippet to the documentation as example.
But why would you need to store the FlashMessage(s) for later processing?
Just output them and delete the cookie. That’s what I do in the dummy implementation (Wwwision.SessionlessFlashMessage/Resources/Public/Scripts/app.js at master · bwaidelich/Wwwision.SessionlessFlashMessage · GitHub)

Right, but that’s the case anyways. Currently we send a cookie with the session Id.

Yes, but that’s something that we should improve where possible.

But why would you need to store the FlashMessage(s) for later processing?
Just output them and delete the cookie.

I was thinking more into the direction of providing a minimal JS code snippet that:

a) solves the issue of sending the cookie forth-and-back and killing proxy cachability

b) does not dictate any specific way of how to render the flash messages

Yes, that’s what I meant. Unfortunately JS doesn’t have a nice API to read and delete cookies. But when using js.cookie.js this snippet would be as simple as:

var flashMessagesCookie = Cookies.get('<YOUR_COOKIE_IDENTIFIER>');
if (!flashMessagesCookie) {
  return;
}
// RENDER THE FLASHMESSAGE HERE
Cookies.remove('<YOUR_COOKIE_IDENTIFIER>');

OK, this is maybe a bit more general-purpose

var flashMessagesCookie = document.cookie.replace(/(?:(?:^|.*;\s*)Neos_Flow_FlashMessages\s*\=\s*([^;]*).*$)|^.*$/, "$1");
if (flashMessagesCookie !== '') {
    var flashMessages = JSON.parse(decodeURIComponent(flashMessagesCookie));
    for (var i in flashMessages) {
        var flashMessage = flashMessages[i];
        Object.keys(flashMessage).map(function (key) {
            flashMessage[key] = decodeURIComponent(flashMessage[key]).replace(/\+/g, ' ')
        });

        // RENDER FlashMessage: alert(flashMessage.title + ': ' + flashMessage.renderedMessage + ' (' + flashMessage.severity + ')');

    }
    document.cookie = 'Neos_Flow_FlashMessages=;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/;';
}

With a note that Neos_Flow_FlashMessages has to be replaced if a custom cookieName was configured