Search code examples
laravelcookiesmiddleware

Update cookie inside laravel middleware


I have a project set up with laravel Sanctum that uses an access token and a refresh token.

I store the token data in a cookie.

I am trying to create a middleware that does the following: 1- Read the cookie containing the token data. 2- Check if the access token has expired. 3- Make a call to my API to refresh it. 4- Store the new value in the cookie (overwriting the old one). 5- Continue the process and the controller will receive the new access token value.

But I have a problem with step 4 and 5, I am finding it impossible to update the new value of the access token in the cookie and get it to the controller.

I have tried everything but I still can't get it, I leave you a piece of code:

$newAuthTokens = $this->adminRepository->refreshAccessToken(
                   new AccessToken($accessTokenData['token']),
                   new RefreshToken($refreshTokenData['token'])
                 );

$cookie = cookie(
                AdminConstants::COOKIE_NAME,
                Crypt::encryptString($newAuthTokens->concatenate()),
Carbon::instance(new DateTime($newAuthTokens->refreshTokenExpiredAt->value()))->diffInMinutes(Carbon::now())
                    );

                    return $next($request)
                        ->withCookie(
                            cookie()->make(
                                AdminConstants::COOKIE_NAME,
                                Crypt::encryptString($newAuthTokens->concatenate()),
                                Carbon::instance(new DateTime($newAuthTokens->refreshTokenExpiredAt->value()))->diffInMinutes(Carbon::now())
                            )
                        );

In other words, I do this:

  1. User enters email and password.
  2. Call API and return the tokens with their expiration times.
  3. The LoginController returns the response with the cookie.

That's the initial thing. Now imagine that the access token that is stored in the cookie has expired and the user clicks on a link that takes him to see, for example, his list of friends, then this happens:

  1. A middleware is executed that checks if the tokens are in the cookie and if they are not expired. As the access token is expired, it calls the API with the refresh token to retrieve a new one.
  2. The middleware receives the response from API with the new data and updates the previous cookie with the new access token and the new refresh token.
  3. The request or whatever arrives at the controller. This collects the access token from the cookie and makes a call to API to retrieve the resource that the user requested, in this case the list of forms (to access the resource, the access token has to be added as a bearer token in the API call, that's why I need it).
  4. The controller receives the data from the API and returns the view with all the user.

What happens between step 2 and 3, the middleware does not update the access token, so when the process reaches step 3 and retrieves the access token, the one it retrieves is the old value that is expired.

I need the cookie to be updated before it reaches the controller so I don't use it: $response = $next($request);


Solution

  • Your application receives an old value because withCookie() method is being executed after $next($request) is resolved to Illuminate\Http\Response, so after the application has already processed the request.

    In general, you are adding a cookie to the response, not a request.

    See Middleware docs for more context.

    On the other hand, I'm not a fan of the refreshing token on the fly in the middleware. You should have made a route to refresh the token which will be called if the client gets HTTP 401 Unauthorized or after a certain amount of time (expiry time).

    If you really what to go that way - you can try to set a cookie on the $request and then return $next($request).

    I don't know which Laravel version you are using (those things have changed over time), so I'm not sure which method you should use. It would look like this:

    public function handle(Request $request, Closure $next): Response
    {
        // Refresh token
        // ...
    
        $request = $request->method_which_will_add_cookie();
    
        return $next($request);
    }
    

    If for some reason cookies are not accessible, you can use ReflectionClass (docs).