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).
- 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-Token
s and/or X-XSRF-Token
s 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.
- 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!