Courses

How to Build Laravel 12 API From Scratch

Authentication with Laravel Sanctum and SPA

Summary of this lesson:
- Setting up Sanctum for Single Page Applications
- Configuring CORS and cookies
- Protecting API routes with Sanctum middleware

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']);
});
Previous: Authentication Overview: Sanctum, Passport or JWT?
avatar

I thought XSRF didn't apply to APIs. Should an XSRF token be fetched before each POST request?

In my Laravel API application, all routes are secured with auth:sanctum Middleware. SPA application created in React. So far my axios wasn't sending any XSRF token - I didn't configure it, but all queries were handled correctly. Now enabling withXSRFToken doesn't change anything. So I wonder if request Login validates XSRF at all.

// auth
Route::post('auth/register', Auth\RegisterController::class);
Route::post('auth/login', Auth\LoginController::class);

// validated user
Route::middleware('auth:sanctum')->group(function () {

    Route::get('secret', fn () => 'some secret');

    // profile
    Route::get('profile', [Auth\ProfileController::class, 'show']);
    Route::put('profile', [Auth\ProfileController::class, 'update']);
avatar

It depends on the situation.

Cookies

Imagine you're logged into your email provider, lets say outlook for simplicity. When you login to your email provider you can close your browser, re-open it and then head to the email provider and you won't need to login again.

You don't need to re-authenticate because your browser has stored a cookie that allows this functionality. The cookie typically stores a value that identifies the 'session'.

The identifer value should be a long randomly generated value to prevent people from guessing or bruteforcing the value. If someone was to guess the value they would be able to go to the site using a session that is potentially authenticated.

The likelyhood of guessing the value of an authenticated session increases when there is a large amount of authenticated sessions. Think youtube and how many people are logged in. If the value was only 10 digits in length, it would be insecure as you're quite likely to hit a valid session.

CSRF Attack

A CSRF Attack exploits this cookie explained above. For example, a malicious website could submit an invisible form when you visit it. This invisible form could have it's action set to one of the email providers endpoints that sends an email. Without protection, this invisible form being submitted would send a request to that endpoint and the cookies will be used to determine you are indeed authenticated. Other endpoints may be used aswell such as changing your password, changing your name etc. (There is hundreds of ways to exploit this, including using an img tag to make a get request)

What are CSRF Tokens?

When you visit a page a CSRF token is granted if you are able to perform any actions. This token must match your session and will verify that this is not a malicious cross site attack.

Why do we need CSRF Tokens Here?

It's because we're using cookie based authentication. The API's that don't require CSRF are stateless so no cookies are used and a API Token / Secret / Key is used to authenticate sessions. Your browser does not support this automatically so any requests sent there will be unauthenticated unless you manually provide a token.

avatar
You can use Markdown
avatar
You can use Markdown