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

scott_mcmullan's avatar

Cookies are missing from request when feature testing in Laravel

My project is using a modified implementation of Laravel Passport for authentication.

I have a route, /auth/login, which I use for authentication. POST is for logging in, PUT is for refreshing tokens, and DELETE is for logging out. Here's the update method of my controller, which is mapped to PUT /auth/login. It is supposed to find the refresh token in the cookie and use it in the performProxyRequest method. However, when I add the cookie to a request in one of my feature tests, $token is always null.

/**
 * Refresh an access token.
 *
 * @param Request $request
 * @return Response
 * @throws JsonException
 */
public function update(Request $request): Response
{
    $token = Cookie::get(Passport::cookie());

    if (is_null($token)) {
        return response([
            'message' => 'Refresh token not found.',
        ], SymfonyResponse::HTTP_UNAUTHORIZED);
    }

    $request->request->add([
        'refresh_token' => $token
    ]);

    return $this->performProxyRequest($request, 'refresh_token');
}

Here is my current feature test with the different attempts I've tried to add the cookie in.

No matter what I try, the $token is always null when I run my feature test. I've tried putting break points to insepct the request, dding it, and a few other things. But there cookie is always missing from the request.

I've check the middleware for the request, it's only throttle:login and auth:api, so I don't think there's anything weird going on there.

I've tested this flow using plain cURL requests, the PhpStorm HTTP client, and in my local dev with my frontend application. In all of those cases, using the cookie works. It just doesn't when I'm writing the feature test.

So, I'm not sure what else I can do to get this feature test working.

My Laravel version is 11.0, Passport is 12.0.

0 likes
4 replies
LaryAI's avatar
Level 58

This is a common issue when feature testing cookies in Laravel, especially with Passport or Sanctum, due to how Laravel encrypts and handles cookies in tests.

The root cause:
When you use Cookie::get() in your controller, it tries to read the cookie from the request. In feature tests, cookies are encrypted unless you specifically set them as unencrypted. If your app expects an encrypted cookie (the default), but your test sends it unencrypted (or vice versa), Cookie::get() will return null.

Solution

1. Use withUnencryptedCookie in your test

Since in your controller you are using Cookie::get(), which expects the cookie to be encrypted (as Laravel does by default), you should use withUnencryptedCookie() in your test. However, you must ensure the value you pass is unencrypted (i.e., the raw value), and Laravel will encrypt it for the request.

But, if you get the cookie from the response ($cookie = $response->getCookie(...)), that cookie is already encrypted. Passing it to withUnencryptedCookie() will result in double encryption, and Cookie::get() won't be able to decrypt it.

Instead, use withCookie() with the raw value:

$cookie = $this->postJson(route('auth.login'), $this->credentials)
    ->getCookie(Passport::cookie());

$response = $this->withCookie(
    $cookie->getName(),
    $cookie->getValue()
)->putJson(route('auth.refresh-tokens'));

But if this still doesn't work, try decrypting the cookie value before passing it to withUnencryptedCookie():

use Illuminate\Support\Facades\Crypt;

$cookie = $this->postJson(route('auth.login'), $this->credentials)
    ->getCookie(Passport::cookie());

$decryptedValue = Crypt::decrypt($cookie->getValue());

$response = $this->withUnencryptedCookie(
    $cookie->getName(),
    $decryptedValue
)->putJson(route('auth.refresh-tokens'));

2. Ensure you are using the correct cookie name

Double-check that Passport::cookie() returns the same name in both your controller and your test.

3. If you are using HttpOnly or Secure cookies

Laravel's test client can handle these, but if you have custom logic around these flags, ensure they're not interfering with your test environment.


Summary

  • If you use withCookie(), pass the value as-is (encrypted).
  • If you use withUnencryptedCookie(), decrypt the value first.
  • Prefer withUnencryptedCookie() with the decrypted value for Passport refresh tokens.

Example:

use Illuminate\Support\Facades\Crypt;

$cookie = $this->postJson(route('auth.login'), $this->credentials)
    ->getCookie(Passport::cookie());

$decryptedValue = Crypt::decrypt($cookie->getValue());

$response = $this->withUnencryptedCookie(
    $cookie->getName(),
    $decryptedValue
)->putJson(route('auth.refresh-tokens'));

$response->assertOk();

This should ensure that $token in your controller is populated as expected.


References:

Let me know if this resolves your issue!

martinbean's avatar

@scott_mcmullan You should not be trying to do multiple requests in a single test case, as the framework isn’t reset between multiple requests, and you get weird state-related issues like you‘re describing. Especially with cookies since there’s no browser to “pass” cookies to, in order to send back with subsequent requests.

You also shouldn’t be modifying Passport given it’s an OAuth server implementation, and provides helpers for testing routes protected by OAuth tokens.

2 likes
martinbean's avatar

@scott_mcmullan Sorry if telling you how to avoid foot guns in your tests (as confirmed by a core member of the Laravel team: https://github.com/laravel/framework/issues/38267#issuecomment-895087635) or that pointing out you’re using a package incorrectly and against the OAuth spec, and therefore is the source of your problems, is “unhelpful”. But sure, insult me, continue to stick your head in the sand, and see how far that takes you…

I mean, I literally explained why the cookie had an empty value:

there’s no browser to “pass” cookies to, in order to send back with subsequent requests.

But yeah, that was completely unhelpful of me. Sorry.

1 like

Please or to participate in this conversation.