0

I was recently trying to implement the Laravel Sanctum authentication with Nuxt 3, but have come across a problem with the initial CSRF cookie handshake. The thing is that after I make a request to the http://localhost:8000/sanctum/csrf-cookie path browser does not set the XSRF-TOKEN cookie. I am stuck on this problem for a week already and have not yet found a solution anywhere on the internet. I tried using both Axios and Fetch API to set XSRF-TOKEN, yet with no success. I am using http://localhost:8000 on the backend and http://localhost:3000 on the front end. Laravel Sanctum itself works fine since I receive set-cookie headers when tested on Postman, but the browser does not receive them. I have set the following properties in the .env file:

    FRONTEND_URL=http://localhost:3000
    SESSION_DOMAIN=localhost:3000
    SESSION_DRIVER=cookie

I did everything to overcome the limitations of the CORS requests on front end. My fetch function looks as follows:

      window.fetch('http://localhost:8000/sanctum/csrf-cookie', {
        credentials: 'include',
      }).then((response) => {
        console.log(…response.headers)
      })

I have read that setting credentials to 'include' solves the problem, but even while doing so, I am unable to set XSRF-TOKEN. I tried to set credentials to 'same-origin', but that doesn't work either. Does anybody know how this problem could be solved?

Monish Khatri
  • 820
  • 1
  • 7
  • 24
John Baker
  • 73
  • 10

2 Answers2

1

I understand your frustration with this, @John Baker. Most people seem scared of tutoring properly about the CORS issues faced when trying to get Nuxt 3 with Laravel as an API (Breeze + Sanctum API) to work properly. I'm having battles myself with it now and then, but referring back to my notes-to-self, as shared here, helps me out most of the time.

To try "hitting the mark" of your question about "getting the csrf-cookie set". Here is a typical script snippet to "set" the csrf-cookie" in a typical login page. Note the credentials: "include", parameter used in both Methods. If not used you'll get a green 204 response, but the Application cookies would not get "set":

login.vue

<script lang="ts" setup>
  definePageMeta({
    // layout: "centered", // Use a layout-centered.vue file for this.
  });

  interface LoginPayload {
    email: string;
    password: string;
  }

  /* All of this will change later as you refactor these steps with custom Composables */
  const form = ref({
    email: "webmaster@abcd.com", // At this stage, Empty Values here will give you the 422 Status error.
    password: "SomEP@s$word",
  });
  
  async function handleLogin(payload: LoginPayload) {
    // Apply the LoginPayload
    await useFetch("http://localhost:8000/sanctum/csrf-cookie", {
      credentials: "include",
    });

    const token = useCookie("XSRF-TOKEN");

    await useFetch("http://localhost:8000/login", {
      credentials: "include",
      method: "POST",
      body: payload, // Apply the LoginPayload
      watch: false,
      headers: {
        "X-XSRF-TOKEN": token.value as string,
      },
    });
    console.log(payload); 
  }
</script>

However, stopping with just setting the application cookies would only create additional errors, so I'm including the rest of the "CORS Onion layers".

As a starting point, it must be remembered what CORS is in comparison to CSRF. In short, CORS is a protection mechanism AGAINST CSRF. The "guard" against the "attacker". Referencing the Laravel Documentation on CSRF Protection, and reading carefully through it will reveal that CORS is almost like an onion. It has different "layers" of protection that get applied via the CORS configuration files and entries of the Laravel API. cors.php, and the .env file are two files that are used as an entry-point to get the "guard" equipped with instructions on HOW the resource sharing should and shouldn't be implemented.

When we need to apply CORS to a SPA using Laravel as an API, we need to go a bit deeper and refer to the dedicated SPA Authentication documentation and apply specific CORS "layers" for establishing protection against CSRF.

Currently, I can suggest two resources I came across, that are showing in detail the "Why" and "What", not just the typical "How". Without asking "Why, What, Where, When", then any "How" is useless. If people can't give you a reason why they are doing things and/or when and where to do them, it typically means they don't understand the topic very well. This is my opinion and based on experience, so don't take it as gospel.

Now for the parts that you might find very helpful: Visit Constantin Druc's YouTube Channel. The man does a very decent job of explaining this extremely common frustration in two, fairly RECENT, tutorials (mid-2023).

  1. Reference: Laravel SPA Authentication - setup and common mistakes

As partly mentioned by Dipo Ahmed, the key thing about basic CORS we need to keep in mind, is that all three parts of an URL MUST be identical.

The domain host/name localhost is NOT equal to the IP address 127.0.0.1. They get interpreted differently. If I'm correct, HTTP started off with IP addresses only, but soon it became clear that people would struggle to remember which IP address to use to be able to land on the desired website, so DNS was developed. It's like a "cross-referencing index" where the DNS knows both the IP address and the corresponding Domain Host or Name, which humans can relate to and the IP to which routing & networking devices and servers can relate to.

  • The URL Scheme or Protocol whether it be http:// or https://, or www., or https://www.
  • The URL Domain Host (Name) eg. somedomain.com or an IP Address 123.456.001
  • The URL PORT Number i.e. 8000, 3000, or 8080

All three URL elements have to be exactly the same for same-origin resource sharing to occur seamlessly. This is easy to achieve in a situation where you might only use Laravel, acting as both the Front and Backend of your application. One Scheme, one Domain Host, and one PORT (assuming we use SQLite as a DB and not an external DB server, for example).

Although in this particular case, the question is about not being able to "set CSRF cookies", and CORS is directly involved, meaning we need to configure this "guard" to monitor the sharing of data between different URLs and do so securely. No valid ID-card = No entry. Enter csrf-cookies. But, it's not only the csrf-cookies that are needed. The instructions or headers in the cookie need to be clear and explicitly instruct the CORS guard about What, Who, When, then How, and How long to do its "duties". That is the session information given through. So, look at the API's kernel.php file's The application's route middleware groups. and the sanctum.php file's return [] array. Then cross-reference it with the built-in Nuxt and custom composables used in the SPA's layout, pages, and components files.

Most probably, additional XSRF-TOKEN cookies and X-XSRF-TOKEN headers are needed. Think of it as a "deeper layer" of the onion mentioned earlier.

Note: CSRF does not function the same way for a typical GET request vs a POST request. Posting data from the SPA domain to the API domain is normally relatively easy. Getting the same information ( i.e. login credentials) back from the DB domain via the API domain to "check" a login attempt, is normally the culprit causing frustration.

Additionally, things may become more confusing in reference to CSRF protection, when you are also using X-CSRF-Tokens and/or X-XSRF-Tokens as applied to URLs and URIs. Refer to CSRF Protection in Laravel's documentation about protecting URLs and URNs. It's NOT the same as SPA CSRF Protection.

Both URI elements: URLs and URNs are used in a typical API-SPA scenario, and both are protected by CORS in a Laravel App or API. A suitable reference found here on SO, which could be applicable to this particular question as well, discusses the difference in reasonable detail. Also see RFC 3986 1.1.2, of Berners-Lee et al..

These different CSRF Protection tokens look very alike, and I have wasted hours to try and figure out why my routes simply will not go through, meanwhile, it was the use of an incorrect TOKEN name that is hard to truly "see" even if it slaps you in the face. Look again:

[X-CSRF-TOKEN + X-XSRF-TOKEN] vs [XSRF-TOKEN + X-XSRF-TOKEN]

To summarise the core factors of applying CORS to obtain effective CSRF protection, the Laravel documentation should be read carefully and in context to gain full insight into the "Why" and "Where/When" first, before moving on to "How". It is imperative to know why you need it and where you want it in YOUR application and then move on to implementing its "layers" to suit the particular requirements of YOUR scenario. It's not a one-shoe-fits-all in every case.

  1. Reference: Laravel & Nuxt 3 Authentication Guide

The second tutorial I recommend watching is focused on setting-up practical and suitable authentication routes between the SPA and API, using Nuxt composables, interfaces and middleware. (Axios is NOT involved in the example. Going the Axios path is a bit different, as you would need to install and configure Axios, and also create an axiosPlugin.ts file).

Note: Following Druc's tutorial, you might need to adjust your SPA's login.vue,register.vue, etc. files a bit when it comes to applying the useFetch() Composable vs. the $fetch() library, depending on your particular use-case. There are also differences between using Vue's Options vs. Composition API, so apply the relevant methods to suit your situation.

Both the tutorials focus on: A) understanding exactly What to do, Why you need to do it and then How CORS works in general as well as more specifically in the Nuxt-Laravel-Sanctum context, and B) how to use the XCSRF + X-XCSRF Tokens & Headers as mentioned in the Laravel SPA Authentication documentation.

Conclusion Note: I am using API as the Root folder of the Laravel API, so remember to apply any folder names appropriate to your scenario. The same applies to the SPA.

A typical Nuxt-Laravel Dev Repo might look as follows:

**Main Project Directory**
   | [API] folder
   | [SPA] folder
   | .env
   | .git
   | <other default files>
   

You might be well aware, but I put it in here for people that might forget about "small-mods-done-some-time-ago" turning into frustration and thinking patterns that it's CORS causing all the crap again:)

Keep the following in the back of your mind while working with the API:

  • Configure and refer to the Cors.php file when in doubt - stick to the defaults, unless you know exactly why you are changing paths in it.
  • Configure and refer to the RouteServiceProvider.php file and apply the same strategy as for Cors.php above.
  • Keep all the configuration files in API/app/Http/Controllers/Auth/*.php as they are, again only modify guard and Redirect Routes if you are familiar with Why you need to change them and What implications doing so has.
  • The same goes for the API\app\Http\Middleware directory files and changes you may want to do there.
  • The same goes for the files in the API's Root: API/config/ and API/routes/ folders.
  • While working with the SPA, various sessions and Database exchanges will take place on the API. If some behaviour seems off, unexpected 422 responses or other strange behaviour is evident (password or user-name changes - they happen ), refer to the API/storage/framework/sessions/ and API/storage/framework/cache/data/ folders. The data and session files you will find in them sometimes contain invalid session or cached data information due to fundamental changes made in either the API or SPA.
  • They can safely be deleted while in developer mode, BUT delete [the session files only].

For example: Session files like Au8etAwHyVuUK0o9wl1QSWSCJ9dfgYVCWRemAH7Q

  • And also ONLY the [sub-directories] residing inside the ~/data/ directory.

For example: Sub-folders like cc or c7, etc.

  • DO NOT delete the session or data folders themselves, nor the .gitignore or other files in them.

I hope it helps, and good luck!

Andre
  • 11
  • 3
  • any affiliation to that channel? [/help/promotion](/help/promotion) – starball Jul 09 '23 at 17:56
  • No. There is no affiliation. – Andre Jul 10 '23 at 18:35
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jul 11 '23 at 10:32
-1

The problem is that browser will not accept cookies from different ports.

Since you back-end is running on 8000 and front-end on 3000 The cookie that back-end provides will not be set on front-end.

So A workaround is to just disable the CSRF protection for API endpoints. You can do this in your VerifyCsrfToken middleware.

add this line in you $except array and you will be good to go.

'/api/*'

Dipo Ahmed
  • 327
  • 2
  • 6
  • No you dont understand, this is the point of using Laravel Sanctum. It provides authentication benefiting from the Laravel web guard, which enables CSRF protection and session authentication. CSRF protection is a really crucial part of authentication, hence, it cannot be turned off for the route I am using. – John Baker Mar 23 '23 at 07:06
  • 1
    Yes that would be true if you were building a monolithic application but in you case you back-end is just an API provider and there will be no session in between requests. So there is no need for CSRF protection but if you really need it you could take a look into proxy server which I don't have a good understanding of. – Dipo Ahmed Mar 23 '23 at 07:13
  • It is not only working as an API, but also authenticates users. In SSR applications there is still a need to use CSRF protection, which is precisely what Laravel Sanctum does. You can read more here: https://laravel.com/docs/9.x/sanctum#spa-authentication. I just need to figure out the way of receiving and actually setting CSRF token cookie. – John Baker Mar 23 '23 at 07:18
  • But Why are you authenticating users with session? Since you front end is separate Authenticate the users like you would authenticate a mobile user with a bearer token. Then you won't need need any kind of session authentication – Dipo Ahmed Mar 23 '23 at 07:22
  • I am using Sanctum because its lightweight and robust, and certainly more modern approach. – John Baker Mar 23 '23 at 07:39
  • Now I think I will still use Sanctum, but to authenticate with API tokens. I just thought having different ports is not a problem for session authentication. – John Baker Mar 23 '23 at 07:54
  • @DipoAhmed you might need to checkout why session authentication is used with API. Check this out, https://laravel.com/docs/10.x/sanctum#spa-authentication. With sanctum can either authenticate your API with token or session cookie, session cookie protects from cross site request forgery. – Joseph Ajibodu Aug 23 '23 at 16:51