Together with hchonov we found a race condition with CSRF token generation.
We can reproduce this in our test with behat and with prefetch_cache module enabled (as it does additional calls to forms). But X-Debug needs to be disabled as timing is crucial here.
What the test does is:
* Login as user
* Go to /user
* Click on edit
* Save profile
This ends up with "The form has become outdated. Copy any unsaved work in the form below and then reload this page".
After some debugging and testing we found the following race condition. If you open a form multiple times without having an form opened before, the CSRF token is generated for each form. Ending up in only a single form having a valid token.
The test with prefetch_cache module just helps here, as prefetch module tries to prefetch the edit form (first form opened) and at the same time the user is already clicking on edit (second form opened).
Now if the second call is faster then your form has token ABCD. Then the first form finishes and your session token is now EFGH. As no CSFR token was set before both set it.
When you click on on save, the form validation fails as ABCD != EFGH.
I can't create a phpunit test for this, as php is not async and the only way to test this is, if we could do two requests at similar times. In our environment this just happens as accident because of the prefetch_cache
Solution
Call \Drupal::service('csrf_token')->get(); inside user_login_finalize.