Laravel Daily's

Authentication Patterns in Laravel + Inertia 3.x + Vue: From Sanctum SPA to Multi-Guard Setups

hero image

Modern web development demands security without friction. Laravel has long provided the gold standard for this balance. With the arrival of Inertia 3.x, the landscape of Single Page Application (SPA) authentication has shifted toward native web standards.

Building a robust authentication system requires more than a simple login form. You need to handle stateful sessions, cross-site request forgery (CSRF) protection, and complex multi-guard configurations. As a leading php web framework, Laravel provides the tools to build these systems rapidly.

The Sanctum SPA Flow

Laravel Sanctum is the default choice for SPA authentication. It provides a featherweight authentication system for SPAs and simple APIs. Unlike token-based systems that require manual storage in localStorage, Sanctum uses Laravel's built-in cookie-based session authentication services.

This approach offers significant security benefits. Cookies marked as HttpOnly are inaccessible to client-side JavaScript. This mitigates many Cross-Site Scripting (XSS) risks.

In the latest php developer tools, the initial handshake remains critical. Before a user attempts to log in, your frontend must request a CSRF cookie. This initializes the session and provides protection against CSRF attacks.

Handling CSRF with Native Fetch

Inertia 3.x has moved away from Axios in favor of the native Fetch API. This change simplifies the internal dependency tree but requires a more intentional approach to CSRF tokens. While Axios handled token synchronization automatically, Fetch requires you to be explicit.

To ensure every request is secure, your Laravel backend must share the CSRF token. You can do this in the HandleInertiaRequests middleware:

public function share(Request $request): array
{
    return array_merge(parent::share($request), [
        'csrf_token' => csrf_token(),
    ]);
}

On the frontend, you can then attach this token to your mutating requests. This ensures your SPA remains stateful and secure across all visits.

Pre-built authentication and profile management in Laravel

Multi-Guard Authentication Patterns

Complex applications often require different types of users. You might have standard customers, administrative staff, and third-party partners. Each group needs its own authentication logic and data structure.

Configuring Multiple Guards

Laravel's authentication system is guard-based. You can define multiple guards in your config/auth.php file. Each guard can use a different Eloquent provider.

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'admin' => [
        'driver' => 'session',
        'provider' => 'admins',
    ],
],

This setup allows you to isolate sessions. An admin logged into the /admin portal won't necessarily be logged into the main /dashboard. This separation is vital for building a secure rest api with php alongside your Inertia frontend.

Multi-guard authentication visualization

The useAuth Composable

In a Vue-powered Inertia application, you need a clean way to access the current user's state. Creating a useAuth composable is the most elegant pattern for this. It abstracts the usePage helper and provides a reactive interface for your components.

import { computed } from 'vue'
import { usePage } from '@inertiajs/vue3'

export function useAuth() {
    const page = usePage()

    const user = computed(() => page.props.auth?.user)
    const isAdmin = computed(() => !!page.props.auth?.admin)
    const isAuthenticated = computed(() => !!user.value || !!isAdmin.value)

    return { user, isAdmin, isAuthenticated }
}

This composable makes your templates cleaner. You can simply call const { user } = useAuth() in any script setup block. This keeps your UI logic focused on presentation rather than data fetching.

Protecting Deferred Props

Inertia 3.x introduces deferred props. This feature allows you to load heavy data in the background after the initial page load. It is a massive win for performance. However, deferred data often contains sensitive information that must be protected.

If a user's session expires while a deferred prop is loading, you need a way to handle that failure gracefully.

Data packets and deferred loading illustration

Using Rescue Slots

The Deferred component in Inertia 3.x includes a rescue slot. This slot acts as a fallback UI if the data load fails due to an authentication error or network issue.

<Deferred data="sensitiveStats">
    <template #default="{ sensitiveStats }">
        <StatsOverview :data="sensitiveStats" />
    </template>

    <template #fallback>
        <LoadingSpinner />
    </template>

    <template #rescue>
        <div class="alert">
            Session expired. Please <Link href="/login">login again</Link>.
        </div>
    </template>
</Deferred>

This pattern ensures your application doesn't crash if a background request fails. It provides a direct path for the user to rectify their authentication state.

Role-Based Access Control (RBAC)

Authentication confirms who the user is. Authorization determines what they can do. For most Laravel applications, middleware is the most efficient place to enforce these rules.

Middleware at the Edge

You can create custom middleware to check for roles or permissions. This keeps your controllers thin and your security logic centralized.

public function handle(Request $request, Closure $next, string $role)
{
    if (! $request->user()?->hasRole($role)) {
        abort(403, 'Unauthorized action.');
    }

    return $next($request);
}

Apply this in your routes to gate specific sections of your application:

Route::middleware(['auth', 'role:admin'])->group(function () {
    Route::get('/admin/reports', [ReportController::class, 'index']);
});

This approach works seamlessly with Inertia. When a user hits a restricted route, Laravel returns a 403 response, which Inertia handles by showing your custom error page.

Laravel and Vue.js administrative dashboard

Handling 401 and 403 Responses Gracefully

User experience doesn't stop at the happy path. You must handle expired sessions and unauthorized access attempts without frustrating the user.

By default, Laravel's authentication middleware redirects unauthenticated users to the /login route. For Inertia apps, this is usually exactly what you want. However, for 403 Forbidden errors, you should provide a clear explanation.

In your Vue components, you can use the global onUnauthenticated or onError hooks provided by the Inertia router to intercept these events. This allows you to show toast notifications or trigger modal logins instead of full-page redirects.

Session vs Token-Based Auth

When should you use Sanctum's session-based auth versus its token-based auth?

  • Use Session-Based Auth: For first-party SPAs (like Inertia) where the frontend and backend share the same top-level domain. It is easier to secure and requires no manual token management.
  • Use Token-Based Auth: For mobile applications or third-party integrations where cookies are not a viable option.

Laravel makes it easy to support both. You can keep your Inertia app on the web guard while exposing a sanctum guarded API for your mobile app.

Conclusion

Authentication in the Laravel and Inertia ecosystem has never been more powerful. By leveraging Sanctum's stateful security and Inertia 3.x's modern features like deferred props and rescue slots, you can build applications that are both fast and secure.

The shift to native Fetch in Inertia 3.x encourages us to embrace web standards while Laravel's elegant syntax keeps the developer experience joyful. Whether you are building a simple startup MVP or a complex enterprise dashboard, these patterns will ensure your user data remains protected.

We’d love to hear how you are implementing multi-guard setups in your latest projects. Join the conversation on our community forums and share your patterns.

Previous
Build a RAG Pipeline with Laravel AI SDK: From PDFs to Intelligent Answers