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