Back to Course |
How to Build Laravel 11 API From Scratch

Authentication with Laravel Sanctum and SPA

In this lesson, we will see an example of how to authenticate users using Laravel Sanctum for a SPA application.

This lesson isn't about how to create an SPA application. The Vue.js part will only be shown partly.

We will change the home page to show categories and products for authenticated users.


Laravel Sanctum Setup

Laravel Sanctum comes with the default new Laravel installation. First, you must set the stateful domain. Your application must be on the same domain, but API can be on a subdomain.

Laravel has set some default stateful domains. You can check the domain in the config/sanctum.php. But, the correct way, in my opinion, would be to set the correct APP_URL in the .env, and Laravel will set the stateful domain automatically. Another option is to set SANCTUM_STATEFUL_DOMAINS in the .env without the http part.

Next, we must add the Sanctum EnsureFrontendRequestsAreStateful Middleware. Laravel has a method called statefulApi which can be added to enable this Middleware.

bootstrap/app.php:

return Application::configure(basePath: dirname(__DIR__))
->withProviders()
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->statefulApi();
})
->withExceptions(function (Exceptions $exceptions) {
$exceptions->renderable(function (NotFoundHttpException $e) {
return response()->json(['message' => 'Object not found'], 404);
});
})->create();

And for the CORS and cookies, we need to set supports_credentials to true in the config/cors.php. Because we are using Axios to make HTTP requests in this example, we must also configure it.

resources/js/bootstrap.js:

import axios from 'axios';
window.axios = axios;
 
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
axios.defaults.withCredentials = true;
axios.defaults.withXSRFToken = true;
 
// ...

Authenticating a User

Before making a POST request to authenticate a user, we must make a GET request to the default sanctum URL /sanctum/csrf-cookie. After getting a XSRF-TOKEN token, we can make a POST request to authenticate the user.

<template>
// ...
</template>
 
<script setup>
import { onMounted, ref } from 'vue';
import { TailwindPagination } from 'laravel-vue-pagination';
 
const categories = ref({})
const products = ref({})
const user = ref(false)
 
const email = ref('')
const password = ref('')
 
// ...
 
const login = async () => {
await axios.get('/sanctum/csrf-cookie')
.then(response => {
axios.post('/login', {
email: email.value,
password: password.value
})
.then(response => {
user.value = true
getCategories()
getProducts()
})
.catch(error => console.log(error)); // credentials didn't match
})
}
 
onMounted(() => {
if (user.value) {
getCategories()
getProducts()
}
})
</script>

After successfully authenticating the user, we set the user variable to true in this simple example. In the template, we use v-show to show login form or categories with products based on the user variable.

Now, we can protect the API routes.

routes/api.php:

Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:sanctum');
 
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('categories', \App\Http\Controllers\Api\CategoryController::class);
 
Route::get('products', [\App\Http\Controllers\Api\ProductController::class, 'index']);
});