0

I spend hours trying to debug googling arround but with no good result. I am using Laravel 8 with Sanctum package and latest Nuxt as frond-end. I setup couple endpoints and made them work - GET requests only. When I got to the forms section, where I need to retrieve information about form and then to be able to send this form data back to Laravel via POST request, I am not able within my set of middleware.

If I am using Api route, I end up at admin login page because of the \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class and if I use Web route, I end up at same login page because of the '\App\Http\Middleware\EncryptCookies::class,' if in both cases I comment them, my POST request goes through. To be exact, both work with Postman and I get no redirect. I get this behaviour only from Nuxt fetch.

This is example of my Nuxt code:

async function sendForm(){
    if(form.value.email_honeypot == ''){
        form.value.email_honeypot = props.data.honey;

        await useFetch('/sanctum/csrf-cookie', { credentials: 'include' });
        const token = useCookie('XSRF-TOKEN')
        form.value._token = token.value;

        formData.value.errors = {};
        const { data: responseData, error, status } = await useCustomFetch('/api/forms-plugin/1/submit', { method: "POST", watch: false, body: form.value, headers: { Accept: 'application/json', 'X-XSRF-TOKEN': token.value }})
        formData.value.response = responseData;

        if(responseData.value.errors) {
            for (const key in responseData.value.errors) {
                const parts = key.split('.');
                const fieldId = parseInt(parts[1]);
                formData.value.errors[fieldId] = responseData.value.errors[key][0];
            }
        }

        if(responseData.value.message) {
            formData.value.success = true;
        }

        form.value.email_honeypot = '';
    }
}

Fetching XSRF-TOKEN from sanctum works okay, I get those cookies to my Application cookie right. Could anyone help me with this?

Usually when post requests failed as I mentioned, my terminal gives me only this: Closed without sending a request; it was probably just an unused speculative preconnection

cors.php

    'paths' => ['*'],

    'allowed_methods' => ['*'],

    'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => [],

    'max_age' => 0,

    'supports_credentials' => true,

.env

APP_URL=http://localhost:8000
FRONTEND_URL=http://localhost:3000

SESSION_DOMAIN=.localhost
SESSION_SECURE_COOKIE=false

SANCTUM_STATEFUL_DOMAINS=127.0.0.1:8000,localhost:3000,localhost

nuxt.config.ts

  routeRules: {
    '/api/**': {
      proxy: { to: "http://localhost:8000/api/**" },
    },

    '/sanctum/**': {
      proxy: { to: "http://localhost:8000/sanctum/**" },
    }
  }

useCustomFetch.ts

import type { UseFetchOptions } from 'nuxt/app'
import { defu } from 'defu'

export function useCustomFetch<T> (url: string, options: UseFetchOptions<T> = {}) {
    const config = useRuntimeConfig()

    const defaults: UseFetchOptions<T> = {
        baseURL: config.public.baseUrl,
        // cache request - causes trouble with url
        // key: url,

        // set user token if connected
        headers: {
            Authorization: `Bearer ${config.public.accessToken}`,
            Accept: 'application/json',
        },

        credentials: 'include',

        onResponse (_ctx) {
            // _ctx.response._data = new myBusinessResponse(_ctx.response._data)
        },

        onResponseError (_ctx) {
            // throw new myBusinessError()
        }
    }

    // for nice deep defaults, please use unjs/defu
    const params = defu(options, defaults)

    return useFetch(url, params)
}

I tried commenting those two middlewares and it starts to work. So I guess I need to make it work with Sanctum EnsureFrontendRequestsAreStateful::class. But I do not know how.

  • Have you carefully compared your requests (url, method, headers, cookies) between Postman and your browser ? – JulBeg Aug 21 '23 at 16:07
  • I tried many times to match those, but even than with no better result. Right now I notices sometimes I get body sent as params to url. – Daniel Kužela Aug 21 '23 at 18:50
  • Can you share your useCustomFetch() function ? – JulBeg Aug 21 '23 at 19:12
  • Added to the question – Daniel Kužela Aug 22 '23 at 05:16
  • When you debug your params, just before useFetch, in your useCustomFetch, is everything ok ? – JulBeg Aug 22 '23 at 07:29
  • With parameters I believe so. I get this request reported by nuxt { "baseURL": "http://localhost:3000", "headers": { "Authorization": "Bearer 1|XDMKyEfHYxxvoS9PnUYnW7Myr06vVdzrf3ES1ujK", "Accept": "application/json", "X-XSRF-TOKEN": "r5aQ0WppJggheoqBVyFGCzd0unOzL3PatTfnx9Uk", "X-CSRF-TOKEN": "r5aQ0WppJggheoqBVyFGCzd0unOzL3PatTfnx9Uk" }, "credentials": "include", "method": "POST", "watch": false, "body": {} } where body is empty because I have not filled any input and this usefetch works with authorization token as get request goes through sanctum – Daniel Kužela Aug 22 '23 at 09:44
  • if I comment \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class this post request works – Daniel Kužela Aug 22 '23 at 09:49
  • I just found out it strictly csrf problem and it looks like session token does not match token in cookie and it looks like some is encrypted and other is not. – Daniel Kužela Aug 22 '23 at 10:40

1 Answers1

1

It looks like I solved the issue.

#1 do not put multiple tokens in request, use only X-XSRF-TOKEN in headers as the cookie token you get from /sanctum/csrf-token is encrypted and it does not match session csrf token by itself, so it needs to decrypt via this method in CsrfVerify middleware

protected function getTokenFromRequest($request)
{
    $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');

    if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
        try {
            $token = CookieValuePrefix::remove($this->encrypter->decrypt($header, static::serialized()));
        } catch (DecryptException $e) {
            $token = '';
        }
    }

    return $token;
}

when putting either X-CSRF-TOKEN to header or _token to inputs, it does not decrypt and they would not match

#2 they did match anyway and I found out that the issue was, that my middleware was not first in api middlewares - if there is EncryptCookies::class before, it breaks the session csrf token value and they wont match

\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,

took me more than 8 hours and finally with ChatGPT I debug this via logs in middlewares