Authentication with Laravel
Laravel Authentication Options
When building a SaaS application with Svelte frontend and Laravel backend, you have several authentication options:
Popular Laravel Authentication Methods
- Laravel Sanctum - Lightweight authentication system for SPAs, mobile apps, and token-based APIs
- Laravel Passport - Full OAuth2 server implementation with more advanced features
- Laravel Fortify - Backend authentication implementation without UI components
For most Svelte + Laravel SaaS applications, Laravel Sanctum is the recommended choice due to its simplicity and good SPA support.
Setting Up Laravel Sanctum
Backend Setup (Laravel)
1. Install Sanctum
composer require laravel/sanctum php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider" php artisan migrate
2. Configure Sanctum in Kernel.php
// app/Http/Kernel.php
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],3. Set Up CORS (for SPA usage)
// config/cors.php 'paths' => ['api/*', 'sanctum/csrf-cookie'], 'allowed_origins' => ['http://localhost:5173'], // Your Svelte app URL 'allowed_origins_patterns' => [], 'allowed_methods' => ['*'], 'allowed_headers' => ['*'], 'exposed_headers' => [], 'max_age' => 0, 'supports_credentials' => true, // Important for cookies
4. Create Login API Endpoint
// routes/api.php
Route::post('/login', [AuthController::class, 'login']);
Route::post('/logout', [AuthController::class, 'logout'])->middleware('auth:sanctum');
Route::get('/user', [AuthController::class, 'user'])->middleware('auth:sanctum');
// AuthController.php
public function login(Request $request)
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required',
]);
if (!Auth::attempt($credentials)) {
return response()->json([
'message' => 'Invalid login credentials'
], 401);
}
$user = Auth::user();
$token = $user->createToken('auth-token')->plainTextToken;
return response()->json([
'user' => $user,
'token' => $token,
]);
}Connecting Svelte to Laravel Sanctum
Frontend Authentication (Svelte)
Two Authentication Approaches:
1. Cookie-based Authentication
Uses HttpOnly cookies - better for SPA security.
// Sanctum cookie setup in Svelte
import axios from 'axios';
// Configure axios
axios.defaults.baseURL = 'http://localhost:8000';
axios.defaults.withCredentials = true; // Important for cookies
async function login(email, password) {
// Get CSRF cookie first
await axios.get('/sanctum/csrf-cookie');
// Then login
const response = await axios.post('/api/login', {
email,
password
});
return response.data;
}2. Token-based Authentication
Uses Bearer tokens in Authorization header.
// Token storage and usage in Svelte
import axios from 'axios';
// Configure axios
axios.defaults.baseURL = 'http://localhost:8000';
// Store token
function setToken(token) {
localStorage.setItem('token', token);
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
}
async function login(email, password) {
const response = await axios.post('/api/login', {
email,
password
});
const { token } = response.data;
setToken(token);
return response.data;
}Authentication Store in Svelte
Create a store to manage authentication state:
// src/stores/auth.ts
import { writable } from 'svelte/store';
import axios from 'axios';
// Initial state
const initialState = {
user: null,
token: localStorage.getItem('token'),
isAuthenticated: false,
loading: false,
error: null
};
// Create the store
const createAuthStore = () => {
const { subscribe, set, update } = writable(initialState);
return {
subscribe,
login: async (email, password) => {
update(state => ({ ...state, loading: true, error: null }));
try {
const response = await axios.post('/api/login', { email, password });
const { user, token } = response.data;
// Store token
localStorage.setItem('token', token);
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
set({ user, token, isAuthenticated: true, loading: false, error: null });
return { success: true };
} catch (error) {
update(state => ({
...state,
loading: false,
error: error.response?.data?.message || 'Login failed'
}));
return { success: false };
}
},
logout: async () => {
try {
// Call logout endpoint if needed
if (initialState.token) {
await axios.post('/api/logout');
}
} catch (e) {
console.error('Logout error:', e);
}
// Clear storage and state
localStorage.removeItem('token');
delete axios.defaults.headers.common['Authorization'];
set({ ...initialState, token: null });
},
checkAuth: async () => {
const token = localStorage.getItem('token');
if (!token) return;
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
update(state => ({ ...state, loading: true }));
try {
const response = await axios.get('/api/user');
update(state => ({
...state,
user: response.data,
isAuthenticated: true,
loading: false
}));
} catch (error) {
// Token expired or invalid
localStorage.removeItem('token');
delete axios.defaults.headers.common['Authorization'];
set({ ...initialState, token: null });
}
}
};
};
export const authStore = createAuthStore();Authentication UI Components
Login Form Example
Login Demo
// LoginForm.svelte
<script>
import { authStore } from '../stores/auth';
let email = '';
let password = '';
let loading = false;
async function handleSubmit() {
loading = true;
const result = await authStore.login(email, password);
loading = false;
if (result.success) {
// Redirect after login
window.location.href = '/dashboard';
}
}
</script>
<form on:submit|preventDefault={handleSubmit}>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" bind:value={email} required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" bind:value={password} required>
</div>
{#if $authStore.error}
<div class="error">{$authStore.error}</div>
{/if}
<button type="submit" disabled={loading}>
{loading ? 'Logging in...' : 'Log In'}
</button>
</form>Route Protection with Svelte
Protecting Routes in SvelteKit
// src/hooks.client.js
import { goto } from '$app/navigation';
import { authStore } from './stores/auth';
// Subscribe to auth store changes
authStore.subscribe(state => {
// Redirect logic can go here if needed
});
// Initialize auth state on app load
authStore.checkAuth();
// Protect routes in +layout.ts
export async function load({ url }) {
// Get the current path
const path = url.pathname;
// Array of protected routes
const protectedRoutes = ['/dashboard', '/settings', '/profile'];
// Check if the current path is protected
const isProtectedRoute = protectedRoutes.some(route =>
path.startsWith(route)
);
// Get auth state
const auth = get(authStore);
if (isProtectedRoute && !auth.isAuthenticated) {
// Redirect to login
goto('/login');
}
return {};
}Creating a Protected Layout
// src/routes/dashboard/+layout.svelte
<script>
import { authStore } from '../../stores/auth';
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
onMount(() => {
// Check if user is authenticated
if (!$authStore.isAuthenticated) {
goto('/login');
}
});
</script>
{#if $authStore.isAuthenticated}
<slot></slot>
{:else}
<p>Loading...</p>
{/if}Security Best Practices
Keeping Your SPA Secure
- Use HttpOnly Cookies when possible for token storage
- Set Proper CORS Headers on your Laravel API
- Enable CSRF Protection by getting the CSRF cookie
- Set Token Expiration to reasonably short periods
- Implement Refresh Tokens for better security
- Secure API Routes with proper middleware
- Validate API Inputs on the server side
- Use HTTPS in production