0

I am building a website using React for the frontend and Laravel for the backend.

I wrote a React Context for authentication, which sends a request to the /api/user endpoint. This endpoint is protected with the auth:sanctum middleware.

However, I am constantly getting a 401 Unauthenticated response when calling /api/user.

I have followed all the recommended steps I found online, including:

Ensuring the SPA (React) and the backend are on the same domain or that the SPA domain is listed in SANCTUM_STATEFUL_DOMAINS.

Calling /sanctum/csrf-cookie before making authenticated requests.

Allowing the SPA URL in the CORS configuration.

Despite this, the problem persists.

What might I be missing? How can I resolve this and get Laravel Sanctum to recognize the session?

env

SANCTUM_STATEFUL_DOMAINS=localhost:5173
SESSION_DOMAIN=localhost
APP_FRONTEND_URL=http://localhost:5173

cors

 'paths' => ['api/*', 'sanctum/csrf-cookie'],

    'allowed_methods' => ['*'],

    'allowed_origins' => [env('APP_FRONTEND_URL', 'http://localhost:3000')],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => [],

    'max_age' => 0,

    'supports_credentials' => true,

authContext

import { createContext,useState,useEffect } from "react";
import api from "../axios";

export const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
    const [user,setUser] = useState(null);

    const fetchUser = async () => {
        try {
            const response = await api.get('/api/user');
            setUser(response.data);
        } catch (error) {
            console.error("Error fetching user data:", error);
            setUser(null);
        }
    }


const logout = async () => {
    try {
        await api.post('/api/logout');
        setUser(null);
    } catch (error) {
        console.error("Error logging out:", error);
    }
}

useEffect(() => {
    api.get('/sanctum/csrf-cookie').then(fetchUser)},[]);
    
    return (
        <AuthContext.Provider value={{ user, fetchUser, logout }}>
            {children}
        </AuthContext.Provider>
    );
}

api

import axios from 'axios'

const api = axios.create({
  baseURL: 'http://localhost:8000',
  withCredentials: true,
})

export default api

login

  const [formData, setFormData] = useState({
        email: "",
        password: ""
    });

    const handleChange = (e) => {
        const { name, value } = e.target;
        setFormData({
            ...formData,
            [name]: value
        });
    }
    const handleSubmit =  (e) => {
        e.preventDefault();
    
        api.get('/sanctum/csrf-cookie')
        .then(() => {
          return api.post('/api/login', formData); 
        })
        .then((response) => {
          console.log('Logged in successfully:', response.data);
          // Maybe redirect or fetch user
        })
        .catch((error) => {
          console.error('Login failed:', error.response?.data || error.message);
          alert("Login failed");
        });
    }      
4
  • Can you try emptying your SESSION_DOMAIN env? Simply make it SESSION_DOMAIN= Commented Jul 10 at 0:25
  • I tried that and it didn't work @Tatachiblob Commented Jul 10 at 22:02
  • Hmm, can you add your backend code for the login process? Commented Jul 11 at 1:26
  • added it @Tatachiblob Commented Jul 11 at 22:26

1 Answer 1

0

After the successful api.post('/api/login') in your Login component, you need to somehow tell AuthContext to re-fetch the user.

AuthContext.js

import { createContext, useState, useEffect, useContext } from "react";
import api from "../axios";

export const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
    const [user, setUser] = useState(null);
    const [loading, setLoading] = useState(true); // Add a loading state

    const fetchUser = async () => {
        try {
            const response = await api.get('/api/user');
            setUser(response.data);
        } catch (error) {
            console.error("Error fetching user data:", error);
            setUser(null);
        } finally {
            setLoading(false); // Set loading to false after fetch
        }
    };

    const login = async (credentials) => {
        try {
            await api.get('/sanctum/csrf-cookie'); // Get CSRF token
            const response = await api.post('/api/login', credentials);
            console.log('Logged in successfully:', response.data);
            await fetchUser(); // Fetch user immediately after successful login
            return response.data; // Return data if needed
        } catch (error) {
            console.error('Login failed:', error.response?.data || error.message);
            setUser(null);
            throw error; // Re-throw error for handling in component
        }
    };

    const logout = async () => {
        try {
            await api.post('/api/logout');

            setUser(null);
        } catch (error) {
            console.error("Error logging out:", error);
        }
    };

    useEffect(() => {
        // This initial fetch will run once on mount to check if already authenticated
        // It will likely return 401 if no prior session, which is fine.
        fetchUser();
    }, []);

    return (
        <AuthContext.Provider value={{ user, fetchUser, login, logout, loading }}>
            {children}
        </AuthContext.Provider>
    );
};

Then use login function from context

import React, { useState, useContext } from 'react';
import { AuthContext } from '../authContext'; // Adjust path if needed
import { useNavigate } from 'react-router-dom'; // Assuming you use react-router-dom

function Login() {
    const [formData, setFormData] = useState({
        email: "",
        password: ""
    });

    const { login } = useContext(AuthContext); // Get the login function from context
    const navigate = useNavigate(); // For redirection

    const handleChange = (e) => {
        const { name, value } = e.target;
        setFormData({
            ...formData,
            [name]: value
        });
    };

    const handleSubmit = async (e) => {
        e.preventDefault();

        try {
            await login(formData); // Call the login function from context
            alert("Login successful!");
            navigate('/dashboard'); // Redirect to a protected route
        } catch (error) {
            alert("Login failed: " + (error.response?.data?.message || error.message));
        }
    };

    return (
        <form onSubmit={handleSubmit}>
            <div>
                <label>Email:</label>
                <input type="email" name="email" value={formData.email} onChange={handleChange} required />
            </div>
            <div>
                <label>Password:</label>
                <input type="password" name="password" value={formData.password} onChange={handleChange} required />
            </div>
            <button type="submit">Login</button>
        </form>
    );
}

export default Login;

ensure that the laravel_session cookie is properly set and then subsequently sent with authenticated requests after a successful login,

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.