a black and white photo of a njoguamos in a suit and with a serious look on his face

Preparing a Laravel API backend for SPA authentication using Sanctum.

Created 08 Feb 2024 | Updated 10 Feb 2024

LaravelSPA

Introduction

At times, your project may require you to develop an API backend that can be consumed by both a single-page application (SPA) and a mobile application. Laravel Sanctum comes in handy. Laravel Sanctum is a first-party authentication package that provides a lightweight authentication system for single-page applications (SPAs), mobile applications, and simple token-based APIs. When using token-based authentication, Sanctum eliminates the complexity of OAuth without compromising security.

For SPAs, Sanctum uses Laravel's built-in cookie-based session authentication services, which utilises Laravel's web authentication. The mechanism provides the benefits of Cross-Site Request Forgery (CSRF) protection and session authentication, as well as protects against leakage of the authentication credentials via Cross-site scripting (XSS).

In this article, I will show you how to set up authentication for your Laravel backend application with a Single Page Application (SPA). By following the instructions, you should be able to prevent most of the common front-end errors, including those related to Cross-Origin Resource Sharing (CORS). The configuration should work with any SPA framework either Qwik, VueJS, React, SolidJs, Slvete and AnguarJs, among others. I will not cover the frontend implementation in this article.

Note: I will assume that you have a basic understanding of Laravel PHP framework.

Install Sanctum

Start by creating a new laravel application, skip if you have an existing application.

composer create-project laravel/laravel laravel-api

Install laravel/sanctum if it is not already included in composer.json.

composer require laravel/sanctum

Publish sanctum configuration and migration files if does not exists already.

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

Open app/Http/Kernel.php and ensure EnsureFrontendRequestsAreStateful is added to api middleware group.

'api' => [
	\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
	\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
	\Illuminate\Routing\Middleware\SubstituteBindings::class,
],

You will not be able to authenticate your SPA if you don't enable the Sanctum middleware.

Set your preferred database credentials in the .env file, and then run the migrate command.

php artisan migrate

Update Stateful domain(s)

Configure which domain(s) your SPA will be making requests from. These domains allows Laravel authentication users using session cookies.

SANCTUM_STATEFUL_DOMAINS=localhost:5173
# SANCTUM_STATEFUL_DOMAINS=example.com -> production

Add authentication endpoints

To make adding an authentication endpoint easier, Laravel Breeze can automatically scaffold the application with the necessary routes, controllers, and views for user registration and authentication. We don't need to reinvent the wheel. Use the command below to install Laravel Breeze. When prompted, select the Api Only option.

composer require laravel/breeze --dev
 
php artisan breeze install

Verify installation by running the feature test

php artisan test
a screenshot of cloudflare sign up page

You can display the endpoints by running

php artisan route:list
a screenshot of cloudflare sign up page

As shown in the image above, we already have authentication endpoints for logging in, registering, and logging out.

Setup sessions

Session name

Session name refers to the name of the cookie that stores the identifier for the Laravel session. You can set the cookie name by updating the SESSION_COOKIE value in the environment variables.

Update .env file with the your preferred session name.

SESSION_COOKIE=laravel_api_session

You will need to pass the session cookie bearing the SESSION_COOKIE alongside X-XSRF-TOKEN when communicating with the Laravel backend.

Session domain

In order to authenticate, your SPA and API must share the same top-level domain. What this mean that you cannot have have your API running on https://api.domain-a.com and your SPA running on https://domain-b.com. A typical set up is having API on https://api.example.com and SPA on https://example.com. However, a session domain can have a leading . (dot).

Update .env file with the correct session domain and app url.

APP_URL=http://localhost:8000
SESSION_DOMAIN=localhost
# SESSION_DOMAIN=.example.test -> valet or Herd
# SESSION_DOMAIN=.example.com -> production

Note: SESSION_DOMAIN should not include a schema, a port or a trailing slash e.g. the following session domains are invalid: https://example.com, example.com, https://app.example.com:8080, example.com/ and https://example.com.

Setup CORS

Cross-Origin Resource Sharing (CORS) is a security feature in web browsers that restricts resource requests from different origins. Start by indicating the domain which your SPA is running.

FRONTEND_URL=http://localhost:5173
# FRONTEND_URL=https://example.com -> production
# FRONTEND_URL=https://staging.example.com -> staging

Then open config.cors and update as follows

 
return [
	// Paths that should be subject to CORS restrictions
	'paths' => ['api/*', 'telescope/*','login', 'register', 'logout' 'sanctum/csrf-cookie'],
	'allowed_methods' => ['*'],
	// domains that are allowed to make CORS requests
	'allowed_origins' => [env(key: 'FRONTEND_URL')],
	'allowed_origins_patterns' => [],
	'allowed_headers' => ['*'],
	'exposed_headers' => [],
	'max_age' => 0,
	// whether the browser should include credentials
	'supports_credentials' => true,
];

Note: If you don't update the path and supports_credentials properly, it can cause CORS errors in the browser. It is important to ensure that these settings are configured correctly to avoid any issues.

Create a test user

Open DatabaseSeeder and ensure you seed a test user.

# ... other code
 
class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        # ... other code
 
         \App\Models\User::factory()->create([
             'name' => 'Test User',
             'email' => '[email protected]',
         ]);
    }
}

Note: The user [email protected] will have a password as the password

Then don't forget to seed the database

php artisan db:seed

Install Laravel Telescope (optional)

Consider installing Laravel to enable easier debugging of API requests without Laravel Telescope. Laravel Telescope is not required to consume Laravel Sanctum API.

composer require laravel/telescope
 
php artisan telescope:install
 
php artisan migrate

Refer to the Laravel documentation for detailed installation instructions.

Start application

Start Laravel application.

php artisan serve

The application should be running on http://localhost:8000. Due to CORS, the path on config/cors.php will not be accessible via http://127.0.0.1:8000.

You backend application is ready for SPA consumption

Common errors

  1. Access to fetch at 'http://localhost:8000/sanctum/csrf-cookie' from origin 'http://localhost:5173' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '' when the request's credentials mode is 'include'. Solution: Got to laravel cors.php and enable 'supports_credentials' => true,
  2. Access to fetch at 'http://localhost:8000/login' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. Solution: Ensure your cors.php path include login

What next

You can start by checking the source code from GitHub. Learn how to integrate a specific frontend framework with the help of the guides I have prepared.

  1. Authenticate Qwik App with Laravel Sanctum.
  2. More integration guides will be coming soon 😊.

Reach out

Let’s talk about working together.

Available for new opportunities