Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

jshannon's avatar

Why would a cloned request cause downstream issues?

<?php

namespace App\Http\Middleware;

use Closure;

class CloneRequest
{
    public function handle($request, Closure $next)
    {
        $clone = $request->duplicate();

    // act on cloned request here

        return $next($clone);
    }

}

The above middleware is a simple Laravel "before" middleware for testing purposes. It actually does nothing of importance. For purposes of this question, assume that I would like to treat the request object as immutable. In such a case I would clone the request, take some actions on it, and then pass it along. Seems to work fine until it needs authentication and hits the other middlewares. Any ideas?

0 likes
9 replies
Talinon's avatar

When you say it works fine until the authentication middleware, what happens exactly? An error? or do you mean that the object passed into the authentication middleware is the original Request Object and not your clone?

In the case of the latter, have you made any changes to the authentication middleware that refers to the request() helper instead of the passed object?

jshannon's avatar

Enabling this exact test middleware on the boilerplate 5.5 install with (make:auth) results in the following after login... "The page has expired due to inactivity. Please refresh and try again."

So I am in error calling it an authentication issue. More likely CSRF.

No changes anywhere, except installing and enabling this middleware. If you were to take a fresh Laravel install, make:auth, create a user, install this middleware and login, you will see the error.

martinbean's avatar

@jshannon Well I don’t know what will exactly be happening without diving into the code, but I imagine a new request will manipulate the session in some way that will make it invalid.

I’m not sure why you’d need to “clone” a request, so just use it as is. The very nature of middleware is to manipulate a HTTP request and then pass that request to the next handler in the stack, and so on. You don’t need to clone the request.

jshannon's avatar

Running var_dump($request->hasSession()); just before duplicate returns false so there is no session store in the original request at the time the test middleware runs.

@martinbean I realize it is unnecessary to clone the request. Reasoning behind treating the request as immutable is a topic for another day. I'm afraid trying to explain might confuse the thread.

jshannon's avatar

@Talinon the cloned request object does become the main request object. As confirmed by dumping the request object in the web.php file within the '/' route.

Snapey's avatar

whilst you have passed along a copy of the original request object, isnt the original bound to the container?

1 like
jshannon's avatar

Yes... I have dumped the container request from within a route and I get the new request object.

Just a couple more notes....

  1. comment out the line where we clone the request and return the original request and it works.

  2. Move the middleware registration to a point after the startsession middleware and it works.

I just can't seem to get past the possibility that the original and clone differ in some way.... but, maybe I am missing something very obvious.

Talinon's avatar
Talinon
Best Answer
Level 51

I think the problem is something along these lines:

CloneRequest gets ran as Before Middleware before anything else. When you run $request->duplicate(), at this moment, there is no session store on the request object. (I think even if there was, this still would fail, because diving into the code shows that the session is not cloned using the duplicate() method).

You then pass the $clone into the next() method which makes its way thru the middleware stack. When it reaches StartSession, it sets a singleton session store on your clone, but the "original" Request object doesn't get set.

Then when you hit your login form, the session $errors object is referenced within login.blade.php, which doesn't exist because you have no session (because it is set on your clone), and everything blows up.

This test confirms my theory:

class CloneRequest
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {

        $clone = $request->duplicate();

        \App::bind('test', function() use ($clone) { return $clone; });

        return $next($clone);
    }
}

Then within login.blade.php:

Original Request: {{ dump(\Request::hasSession()) }}
Clone Request: {{ dd(resolve('test')->hasSession() ) }}

// results in

// Original Request:
// false
// Clone Request:
// true


If I remove the dd() and allow it to proceed, when it reaches the $errors->has('email'), it throws the "page has expired" message. Turning on debug, we can see that the underlying error is "Session store not set on request"

1 like
jshannon's avatar

Bingo! @Talinon you nailed it. I must have totally misread the object id when I dumped the container request object from the route. Not sure how that happened.

Now I know what is needed. I appreciate the effort. Thanks!

BTW.... @Snapey you were on to something also.

Please or to participate in this conversation.