
React + Laravel Sanctum: Solving CSRF and Token Issues in SPA Authentication
React + Laravel Sanctum: Solving CSRF and Token Issues in SPA Authentication
Learn how to implement secure authentication in a React SPA using Laravel Sanctum with CSRF protection and cookie-based tokens. A complete guide with examples and edge cases.
React + Laravel Sanctum: Solving CSRF and Token Issues in SPA Authentication
In this post, I'll walk you through how to make authentication between a React SPA and a Laravel API work cleanly using Laravel Sanctum, with cookie-based sessions and proper CSRF handling.
We’ll cover:
- How Laravel Sanctum works
- How to configure your Laravel backend for SPA auth
- How to make React send cookies securely
- How to handle CSRF token exchange
- How to troubleshoot common issues
- Special cases you may encounter in real-world apps
🔒 Why Use Laravel Sanctum?
Laravel Sanctum is a lightweight authentication system built specifically for SPAs, mobile apps, and simple token-based APIs.
It supports two main modes:
- API Token mode (for mobile and third-party apps)
- Cookie-based session mode (ideal for SPAs like React)
For SPAs, Sanctum uses session cookies and CSRF tokens to protect your users. Unlike bearer tokens or JWTs, this method automatically works with Laravel’s built-in session and authentication systems—making things more secure and easier to manage.
🧠 How Sanctum Auth Works (SPA Mode)
Here’s the high-level flow for cookie-based authentication in Laravel Sanctum:
- SPA sends a request to
/sanctum/csrf-cookie
- Laravel responds with a CSRF token in a cookie
- SPA sends a login request (
/login
) with credentials and CSRF token in headers - Laravel sets a session cookie upon success
- All subsequent requests include the session cookie automatically
💡 No need to store tokens manually in localStorage or deal with token refreshes.
🛠️ Setting Up Laravel Sanctum
1. Install Sanctum
composer require laravel/sanctum php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider" php artisan migrate
2. Middleware Setup
Make sure EnsureFrontendRequestsAreStateful
middleware is enabled in app/Http/Kernel.php
under the api
middleware group:
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful; 'api' => [ EnsureFrontendRequestsAreStateful::class, 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ],
3. Configure sanctum.php
Check your config/sanctum.php
:
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost:3000,127.0.0.1:3000')),
Set this to include your front-end domain(s). In production:
SANCTUM_STATEFUL_DOMAINS=yourdomain.com,subdomain.yourdomain.com
4. CORS Configuration
In config/cors.php
:
'paths' => ['api/*', 'sanctum/csrf-cookie'], 'allowed_methods' => ['*'], 'allowed_origins' => ['http://localhost:3000'], // or your front-end URL 'allowed_headers' => ['*'], 'supports_credentials' => true,
Make sure 'supports_credentials' => true
.
⚛️ Configuring React for Sanctum
1. Axios Setup
npm install axios
Create a axios.js
helper:
import axios from 'axios'; axios.defaults.withCredentials = true; axios.defaults.baseURL = 'http://localhost:8000'; // your Laravel API base export default axios;
2. Fetch CSRF Cookie First
Before any POST request, get the CSRF cookie:
import axios from './axios'; export const getCsrfToken = async () => { await axios.get('/sanctum/csrf-cookie'); };
3. Login Function
export const login = async (email, password) => { await getCsrfToken(); // Get CSRF cookie first const response = await axios.post('/login', { email, password }); return response.data; };
After a successful login, the session cookie is set. No need to manually store tokens!
🔐 Protecting Routes in Laravel
Use the built-in auth:sanctum
middleware in your routes:
Route::middleware('auth:sanctum')->get('/user', function (Request $request) { return $request->user(); });
🧪 Logging Out
In your React app:
export const logout = async () => { await axios.post('/logout'); };
Laravel will invalidate the session and remove the cookie.
💡 Real-World Special Cases & Pitfalls
1. Cookies Not Being Sent
- Double-check that
axios.defaults.withCredentials = true
is set. - Your Laravel server must send the session cookie with
SameSite=None
andSecure
flags if using HTTPS. - Make sure your front-end and back-end share the same root domain, or are included in
SANCTUM_STATEFUL_DOMAINS
.
2. CSRF Token Mismatch
- Always call
/sanctum/csrf-cookie
before login or any protected POST request. - React must send the CSRF token automatically. Axios does this behind the scenes after getting the cookie.
3. CORS Issues
Ensure Laravel’s cors.php
config allows:
- Your front-end origin
- Credentials (
supports_credentials: true
) - Access to
sanctum/csrf-cookie
4. Dev vs Production Domains
In development:
FRONTEND: http://localhost:3000 BACKEND: http://localhost:8000
In production:
- Use proper
SANCTUM_STATEFUL_DOMAINS
, CORS, and HTTPS configurations. - Avoid mixing
http
andhttps
in production—cookies won’t be sent.
✅ Final Checklist
Before you go live:
- Set up
.env
properly for both environments - Configure session domain (
SESSION_DOMAIN
) and secure cookies (SESSION_SECURE_COOKIE=true
) - Enable HTTPS in production
- Confirm that cookies and CSRF tokens are being sent correctly
🔚 Conclusion
By using Laravel Sanctum’s cookie-based authentication, you can avoid common issues like token leaks, poor security, or overengineering. It integrates beautifully with Laravel’s built-in session system and gives your SPA the seamless experience users expect.
Setting up Sanctum properly takes a few steps, but once in place, it’s reliable, secure, and easy to manage.
If you found this guide helpful or ran into specific edge cases, I’d love to hear about them. You can also explore other posts on kaisalhusrom.com for more hands-on articles like this.