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

alexleonard's avatar

Latest v5 laravel/framework CsrfMiddleware changes broke codeception functional tests

After composer updating laravel/framework today and merging in the various changes to laravel/laravel over the past couple of days, I managed to get everything back up and running except for my codecept tests.

I can browse the site fine and POST/PUT forms without triggering a CSRF mismatch, but when I run my codecept functional tests, any that are testing POST/PUT functionality all fail with

Illuminate\Session\TokenMismatchException: 

With the new inclusion of App\Http\Middleware\CsrfMiddleware in App\Providers\AppServiceProvider:$stack could this be causing an issue?

Anyone else come across this issue with the latest dev L5?

0 likes
21 replies
alexleonard's avatar

Hmm, further mysteries:

So I updated my test to do this just after the route with the form is loaded:

$token = $I->grabValueFrom("input[name='_token']");
dd(json_encode(['form-token' => $token, 'system-token' => csrf_token()]));

And the tokens match

Trying to edit a profile (ProfileManagementCept) string(115) "{"form-token":"m1Hn617cl3o3wn7c0zhIZHo6xWVkRoDCtr9RscQ4","system-token":"m1Hn617cl3o3wn7c0zhIZHo6xWVkRoDCtr9RscQ4"}"
alexleonard's avatar

Definitely something funny going on with CsrfMiddleware when tests are being run.

If I change App\Http\Middleware\CsrfMiddleware to be:

protected function tokensMatch($request)
{
    return true;
    return $request->session()->token() == $request->input('_token');
}

Then the tests move on to the next step (although now they're failing for not seeing notifications being passed back ->withNotifications('blah')

I can't see how that _token mismatch is occurring though with the die and dump showing two matching tokens.

alexleonard's avatar

I can only think that there's definitely something up with the way tests are seeing Session data. Considering both _token mismatch is fired and when I force the Csrf to be true regardless, then it's not seeing Session::has('notification') either.

However this all works fine if I'm manually testing things in the browser - Csrf is validated and ->withNotifications($notification) is picked up by my _flash partial which uses if(Session::has('notification')

This is my /tests/functional.suite.yml file

class_name: FunctionalTester
modules:
    enabled: [Filesystem, FunctionalHelper, Laravel4, Db]

And it was all working prior to pulling in the last two days of L5 commits.

I'm going to have to move on without functional tests for the rest of today and fingers crossed some more updates fix things tomorrow. I'm a little lost on how to track down what's going wrong.

Thorsten_Daners's avatar

Yes, I do, but with behat. The problem is that there is no session in which the token can be stored. So it is regenerated for each request.

I fixed the problem temporary in CsrfMiddleware.php via User-Agent detection.

public function handle($request, Closure $next)
{
 if ($request->header('user-agent') == 'Symfony2 BrowserKit' || $request->method() == 'GET' || $this->tokensMatch($request)) {
  return $next($request);
 }

 throw new TokenMismatchException;
}
alexleonard's avatar

Thanks so much Schnoop! I'm on another project for the next few hours but very excited to test that out and see if it's working. Awesome!

JanHenkG's avatar

I encountered the same problem, and the solution provided by Schnoop works. But I adapted his solution a little, instead of using User-Agent detection I make use of a call to App::runningUnitTests():

if ($request->method() == 'GET' || $this->tokensMatch($request) || App::runningUnitTests())
{
   return $next($request);
}

I also reported this issue on Laravel's GitHub issue tracker: https://github.com/laravel/framework/issues/6138.

2 likes
Thorsten_Daners's avatar

That's definitely a better solution Jan. I'm unsure if this is the final fix for our problem, so my fix has been temporary, and not that elegant like yours.

Thorsten_Daners's avatar

In generel the whole session component is not working, so you're also not able to use things like:

redirect(action('Foo\BarController@showCreateForm', array('model' => $model)))
                ->withErrors(array('Args'));
JanHenkG's avatar

You're right, this is not a final fix and all things session related do not work. I ran into that problem when I wanted to test error messages for a form.

JanHenkG's avatar
Level 20

I actually found a better solution that fixes all the problems with the sessions. I will first explain the problem and then provide my solution.

The problem arises due to the fact that the session driver used for tests is the Array driver, and this driver uses a NullSessionHandler. Because of this the session data is not saved between requests. The code that creates the array session driver is follows:

// Illuminate/Session/SessionManager.php

/**
  * Create an instance of the "array" session driver.
  *
  * @return \Illuminate\Session\Store
  */
protected function createArrayDriver()
{
    return new Store($this->app['config']['session.cookie'], new NullSessionHandler);
}

So my thought was to change the driver used in the testing environment to the native driver, but this didn't work. I found this strange, and after some testing I found out that setting the session driver option was always overridden to the array driver. The code that does this is in the SessionServiceProvider file:

// Illuminate/Session/SessionServiceProvider.php

/**
  * Setup the default session driver for the application.
  *
  * @return void
  */
protected function setupDefaultDriver()
{
    if ($this->app->runningInConsole())
    {
        $this->app['config']['session.driver'] = 'array';
    }
}

Because tests are run through the console, the session driver will always be set to array, even if you specify something else in the config file for the testing environment.

So my solution is to create a TestingServiceProvider myself, that sets the correct session driver:

// App/Providers/TestingServiceProvider.php

<?php namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class TestingServiceProvider extends ServiceProvider {

     /**
      * Register the service provider.
      *
      * @return void
      */
    public function register()
    {
        if ($this->app->environment() == 'testing')
        {
            $this->app['config']['session.driver'] = 'native';
        }
    }

}

Do make sure you register this service provider after the SessionServiceProvider in config/app.php, because otherwise the change will be overridden again.

This will work nicely until some other fix will be implemented.

2 likes
fenos's avatar

@JanHenkG Your solution is pretty good thanks!! It's work, maybe you can just decorate the service provider like so

public function register()
 {
  if ($this->app->environment() == 'testing')
  {
   $this->app['config']['session.driver'] = 'native';
  }
 }
1 like
JanHenkG's avatar

@fenos that is something I meant to do but forgot, I will edit it into my post. Thanks.

1 like
MatthewLilley's avatar

I've also had problems since this update, with POST requests from route resources. If I do not use a resource and directly reference the @POST, the POST request functions fine, but if I attempt to use a resource, I get a MethodNotFoundException, and a 405 response.

alexleonard's avatar

@JanHenkG That's brilliant. I've added a TestingSessionProvider and loaded it at the end of my service providers and Codecept tests are running again! Many thanks

alexleonard's avatar

Oh oh. Looks like Codecept is wholly broken with the latest laravel/laravel updates

[Codeception\Exception\ModuleConfig]                                                                             
    Laravel4 module is not configured!                                                                               
    Laravel start.php file not found in /path/to/bootstrap/start.php.  
    Please provide a valid path to it using 'start' config param.     
alexleonard's avatar

@JanHenkG I completely forgot about this post! But I do have that TestingServiceProvider in place and being called by config.app.providers (after the regular SessionServiceProvider).

<?php namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class TestingServiceProvider extends ServiceProvider {

         /**
            * Register the service provider.
            *
            * @return void
            */
        public function register()
        {
                if ($this->app->environment() == 'testing')
                {
                        $this->app['config']['session.driver'] = 'native';
                }
        }

}

I tried, for curiosity's sake, disabling the TestingServiceProvider, but no change.

jonsa's avatar

I'm having the same issue and been banging my head against the wall these last few days trying to get this to work.

From what I can tell it worked a week or so ago, but then I pulled in the upstream changes, updated composer and let it stew for a few day. Now when I run the tests it failes at the token check. The problem seems to be that the session is not persisted when running tests no matter what driver I choose in TestingServiceProvider.

Please or to participate in this conversation.