Building Secure Authentication with Laravel Sanctum and React: A Complete Guide
Introduction
Authentication is the backbone of modern web applications, and choosing the right authentication system can make or break your application's security. Laravel Sanctum provides a lightweight authentication system for SPAs, mobile applications, and simple token-based APIs. In this comprehensive guide, we'll implement a secure authentication system using Laravel Sanctum with a React frontend.
What is Laravel Sanctum?
Laravel Sanctum offers a featherweight authentication system that provides both session-based authentication for SPAs and token-based authentication for mobile applications. Unlike Laravel Passport, Sanctum doesn't use OAuth2, making it simpler for applications that don't need the complexity of a full OAuth2 implementation.
Key Features
- SPA Authentication using Laravel's built-in cookie-based session authentication
- Mobile API Token authentication
- CSRF protection for SPAs
- Token abilities (scopes)
- Token expiration
Setting Up Laravel Sanctum
First, let's install and configure Sanctum in our Laravel application.
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrateNext, add Sanctum's middleware to your api middleware group in app/Http/Kernel.php:
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],Configure your config/sanctum.php file to specify which domains can make requests:
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
))),Creating Authentication Endpoints
Let's create the necessary authentication endpoints in our Laravel API:
// routes/api.php
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
Route::post('/logout', [AuthController::class, 'logout'])->middleware('auth:sanctum');
Route::get('/user', [AuthController::class, 'user'])->middleware('auth:sanctum');Now, let's implement the AuthController:
validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8|confirmed',
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
$token = $user->createToken('auth-token')->plainTextToken;
return response()->json([
'user' => $user,
'token' => $token,
], 201);
}
public function login(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required',
]);
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
// Revoke existing tokens
$user->tokens()->delete();
$token = $user->createToken('auth-token')->plainTextToken;
return response()->json([
'user' => $user,
'token' => $token,
]);
}
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => 'Logged out successfully']);
}
public function user(Request $request)
{
return response()->json($request->user());
}
}Setting Up React Authentication
Now let's create a React authentication system that works seamlessly with our Laravel Sanctum backend:
// src/contexts/AuthContext.js
import React, { createContext, useContext, useState, useEffect } from 'react';
import axios from 'axios';
const AuthContext = createContext();
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [token, setToken] = useState(localStorage.getItem('auth-token'));
// Configure axios defaults
axios.defaults.baseURL = 'http://localhost:8000/api';
axios.defaults.headers.common['Accept'] = 'application/json';
if (token) {
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
}
const login = async (email, password) => {
try {
const response = await axios.post('/login', { email, password });
const { user, token } = response.data;
setUser(user);
setToken(token);
localStorage.setItem('auth-token', token);
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
return { success: true };
} catch (error) {
return {
success: false,
message: error.response?.data?.message || 'Login failed'
};
}
};
const register = async (name, email, password, passwordConfirmation) => {
try {
const response = await axios.post('/register', {
name,
email,
password,
password_confirmation: passwordConfirmation,
});
const { user, token } = response.data;
setUser(user);
setToken(token);
localStorage.setItem('auth-token', token);
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
return { success: true };
} catch (error) {
return {
success: false,
message: error.response?.data?.message || 'Registration failed',
errors: error.response?.data?.errors
};
}
};
const logout = async () => {
try {
await axios.post('/logout');
} catch (error) {
console.error('Logout error:', error);
} finally {
setUser(null);
setToken(null);
localStorage.removeItem('auth-token');
delete axios.defaults.headers.common['Authorization'];
}
};
useEffect(() => {
const fetchUser = async () => {
if (token) {
try {
const response = await axios.get('/user');
setUser(response.data);
} catch (error) {
console.error('Failed to fetch user:', error);
logout();
}
}
setLoading(false);
};
fetchUser();
}, [token]);
const value = {
user,
token,
login,
register,
logout,
loading,
isAuthenticated: !!user,
};
return (
{children}
);
};Best Practices and Security Considerations
When implementing Sanctum authentication, keep these security best practices in mind:
Token Management
- Token Rotation: Implement token rotation for enhanced security
- Token Expiration: Set appropriate token expiration times
- Secure Storage: Store tokens securely on the client side
CORS Configuration
Ensure your CORS configuration is properly set up in config/cors.php:
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,Conclusion
Laravel Sanctum provides a robust and simple authentication solution for modern web applications. By following this guide, you've implemented a secure authentication system that handles user registration, login, logout, and token management. Remember to always validate user input, use HTTPS in production, and implement proper error handling throughout your application.
The combination of Laravel Sanctum's backend security with React's frontend flexibility creates a powerful foundation for building secure, modern web applications.
Related Posts
Building Lightning-Fast APIs with Go and Fiber: A Practical Guide
Learn how to build high-performance REST APIs using Go and the Fiber framework with practical examples and best practices.
Building a Complete Authentication System with Laravel 11 and JWT
Learn to implement secure JWT authentication in Laravel 11 with refresh tokens, role-based access, and best practices for API security.
Implementing Rate Limiting in Node.js APIs: Protect Your Backend from Abuse
Learn how to implement effective rate limiting strategies in Node.js to protect your APIs from abuse and ensure optimal performance.