Let's talk about cookie storage…

dakur
Member | 384
+
+3
-

I'm experimenting with cookie user storage in our Nette app and it looks really promising (solving some issues with serialization and invalidating), but I'm a bit confused/dissatisfied about how it works.

Life-cycle problem

When I call $storage->saveAuthentication($identity) and then $storage->getState()[1], I would expect to obtain previously stored identity. However, as cookie storage works on “read from request, write to response” basis, it doesn't return the identity as there is still nothing in the request. On next request, it works well. For this, I made a PR which caches the identity in memory, but as I was testing it, another problem occurred..

Hard-coded SimpleIdentity

The aforementioned two main methods have following signature:

public function saveAuthentication(IIdentity $identity): void;
public function getState(): array{bool, ?IIdentity, ?int};

That works well unless you start using something else than SimpleIdentity. With the caching mechanism I've introduced above, it became even more visible. I've stored my own StorageIdentity (which implements IIdentity and even extends SimpleIdentity). On getState() in the same request, it correctly obtained StorageIdentity (from memory cache), but on next request, it obtained SimpleIdentity as that's how CookieStorage#getState() is implemented – hard-coded instantiating of SimpleIdentity.

I think I understand the objective why it was made this way – getState() doesn't know what identity object user means to use. But this behavior is just confusing. CookieStorage pretends to support IIdentity when in fact it requires SimpleIdentity.

What can I do?

  • avoid passing anything else than SimpleIdentity to saveAuthentication() method – input/output is symmetric, thus more predictable
  • hard-code new SimpleIdentity() in caching mechanism the same way as it is in the current non-caching behavior and count with the implicit asymmetry – in: IIdentity, out: SimpleIdentity

Both options seem to be work-arounds to a problem that CookieStorage lies about its input/output. 🙂

Thrown away roles/data

This is related issue to see the full picture. When identity is stored with CookieStorage, roles and data are thrown away. When getState() is called then, there's only ID from the originally stored identity. With SessionStorage, what you store is what you get.

Sum up

SessionStorage and CookieStorage have technically common interface, but they seem to be two different solutions of preserving authentication data across requests with different required inputs and different produced outputs.

Question

Should they really have common interface? I'd like to hear some opinions on this.

To be honest, I don't know what would be the impact of un-doing the interface on Nette ecosystem, we use UserStorage standalone, pass its instances manually and we deregistered User class from our app. But as Nette claims itself as framework of standalone packages, I believe that such low-level/standalone usages should be supported.

Last edited by dakur (2022-10-06 08:07)

David Grudl
Nette Core | 7827
+
0
-

SessionStorage and CookieStorage are just parts of a larger system where IdentityHandler also plays an important role.

Yes, CookieStorage can only store a special type of identity. This is a feature of it. You could create some EncryptedCookieStorage that would store more information in the cookie in encrypted form. To be able to use CookieStorage with any custom identity class, there is just IdentityHandler. And it can also preserve roles and data, for example.

(This brings me to the idea of creating a CookieIdentity class that would be the only one supported in CookieStorage, to make it clear that nothing else can be used.)

The fact that CookieStorage::getState() does not take into account the previous saveAuthentication() can of course be changed.

Marek Bartoš
Nette Blogger | 827
+
0
-

You could create some EncryptedCookieStorage that would store more information in the cookie in encrypted form

Cookies have very limited storage space. Some applications dump all user data into identity and would reach limit for all cokies quite quickly.

Also it would need a checksum because it is possible to change data without decrypting them


Imho it is okay to require IdentityHandler to be used with CookieStorage. But it should be probably noted here https://blog.nette.org/…security-3-1 and here https://doc.nette.org/…thentication#… as the behavior is not that obvious for newcomers

David Grudl
Nette Core | 7827
+
0
-

@MarekBartoš If you have an idea what to improve in the documentation, feel free to do so.

dakur
Member | 384
+
0
-

Thanks for the response!

Yes, CookieStorage can only store a special type of identity. This is a feature of it. You could create some EncryptedCookieStorage that would store more information in the cookie in encrypted form.

It still seems odd to me that we have an interface to require from DI, but then we have to be aware of implementation specifics to use it. Isn't point of interface to be independent of implementation specifics?

To be able to use CookieStorage with any custom identity class, there is just IdentityHandler. And it can also preserve roles and data, for example.

With IdentityHandler, there's another problem I wrote about here. In short, you can't use it when you use User but don't use Authenticator.

(This brings me to the idea of creating a CookieIdentity class that would be the only one supported in CookieStorage, to make it clear that nothing else can be used.)

Yes, that would improve the experience. 👍

David Grudl
Nette Core | 7827
+
0
-

In short, you can't use it when you use User but don't use Authenticator.

Sure, go ahead and send a PR.

dakur
Member | 384
+
0
-

@DavidGrudl By the way, is there some reason why CookieStorage#cookieExpiration: string type doesn't adhere to the underlying layer – IResponse#setCookie(string|int|\DateTimeInterface $expire)?

dakur
Member | 384
+
0
-

@DavidGrudl ?

David Grudl
Nette Core | 7827
+
0
-

I don't know, I guess not.

dakur
Member | 384
+
0
-

Ok, I found out that it just wasn't adapted/widened to what Response supports after cookie storage has been introduced as an alternative for session storage. I've made PRs:

dakur
Member | 384
+
0
-

David Grudl wrote:

In short, you can't use it when you use User but don't use Authenticator.

Sure, go ahead and send a PR.

Not so easy today, I'm already out of context a bit as I wrote that issue 4 months ago. Will see if I can find some time to dig around again.