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

// 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

Next Steps

Now that you understand authentication with Laravel, check out: