I have an app using Laravel 11 (Sanctum) as the web API and React as the frontend. Authentication works with HttpOnly cookies, but after login, /user returns 401 Unauthorized.
Axios is set with withCredentials: true CORS is configured with supports_credentials: true CSRF cookie is fetched before login (/sanctum/csrf-cookie) Here’s my code: [Include relevant snippets]
How can I ensure the session cookie is sent and recognized correctly?
import axios from "axios";
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_URL,
withCredentials: true,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
});
apiClient.interceptors.request.use(config => {
const token = document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*=\s*([^;]*).*$)|^.*$/, '$1');
if (token) {
config.headers['X-XSRF-TOKEN'] = token;
}
return config;
});
export default apiClient;
import axios from 'axios';
import apiClient from './apiClient';
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
withCredentials: true,
headers: {
'Accept': 'application/json',
}
});
export const fetchUser = async () => {
try {
const response = await apiClient.get('/user');
if (response.status === 200) {
return response.data;
}
} catch (error) {
if (error.response?.status === 401) {
throw new Error('Authentication failed');
}
throw error;
}
};
Login.js
const handleLogin = async (e) => {
e.preventDefault();
try { await apiClient.get('/sanctum/csrf-cookie');
const loginResponse = await apiClient.post('/login', {
email,
password,
ip
});
const userData = await fetchUser();
if (userData) {
setAuth(true);
navigate('/dashboard');
}
}
} catch (error) {
setError(error.response?.data?.message || "Login failed. Please try again.");
}
};
AuthController.php
public function login(Request $request): JsonResponse
{
try {
$validated = $request->validate([
'email' => ['required', 'string', 'email'],
'password' => ['required', 'string'],
]);
$user = User::where('email', $validated['email'])->first();
if (!$user || !Hash::check($validated['password'], $user->password)) {
return response()->json(['message' => 'Invalid credentials.'], 401);
}
// Generate token
$token = $user->createToken('auth_token')->plainTextToken;
$cookie = cookie(
'auth_token',
$token,
60,
'/',
null, // domain (keep null for localhost)
false, // secure (false for HTTP)
true, // httpOnly
false,
'Lax' // sameSite (use Lax/Strict for HTTP)
);
return response()->json([
'message' => 'Login successful.',
'user' => $user->only(['id', 'name', 'email'])
])->cookie($cookie);
} catch (\Exception $e) {
return response()->json(['message' => 'Internal server error'], 500);
}
}