Published 1/16/2024 · 3 min read
Tags: vue , middleware , authentication , routing , typescript
The auth middleware checks if a user is authenticated before allowing access to protected routes. If authentication fails, the user is redirected to the login page.
The Auth Middleware
Create src/middleware/auth.ts:
import type { MiddlewareContext } from "./types";
import { useAuthStore } from "@/stores/auth";
export default async function auth({ to, next }: MiddlewareContext) {
const authStore = useAuthStore();
// If we already have a user, allow access
if (authStore.user) {
return next();
}
// Try to fetch the user from the API
await authStore.fetchUser();
// Check again after API call
if (authStore.user) {
return next();
}
// Not authenticated, redirect to login with return URL
return next({
name: "login",
query: { redirect: to.fullPath },
});
}
Let’s break down what this middleware does:
-
Access Pinia store - We get the auth store instance using
useAuthStore() -
Check existing user - If there’s already a user in state, immediately allow access by calling
next() -
Fetch from API - If no user exists in state, we call
fetchUser()to check with the Laravel API whether there’s a valid session -
Verify result - After the API call, if we now have a user, allow access
-
Redirect if unauthenticated - If still no user, redirect to the login page. The
query.redirectparameter saves the original destination so we can send them there after login
Using the Middleware
Import and add to any route that requires authentication:
import auth from "@/middleware/auth";
const routes = [
{
path: "/dashboard",
name: "dashboard",
component: () => import("@/views/DashboardView.vue"),
meta: { middleware: [auth] },
},
{
path: "/settings",
name: "settings",
component: () => import("@/views/SettingsView.vue"),
meta: { middleware: [auth] },
},
];
Handling the Redirect After Login
In your login component, check for the redirect query parameter:
<script setup lang="ts">
import { ref } from "vue";
import { useRouter, useRoute } from "vue-router";
import { useAuthStore } from "@/stores/auth";
const router = useRouter();
const route = useRoute();
const auth = useAuthStore();
const form = ref({
email: "",
password: "",
});
async function handleLogin() {
await auth.login(form.value);
// Redirect to original destination or dashboard
const redirect = route.query.redirect as string;
router.push(redirect || { name: "dashboard" });
}
</script>
Alternative: Synchronous Check
If you want to avoid the async/await pattern and handle loading states differently:
import type { MiddlewareContext } from "./types";
import { useAuthStore } from "@/stores/auth";
export default function auth({ to, next }: MiddlewareContext) {
const authStore = useAuthStore();
const loginQuery = {
name: "login",
query: { redirect: to.fullPath },
};
// If no user and not currently loading, try to fetch
if (!authStore.user && !authStore.isLoading) {
authStore.fetchUser().then(() => {
if (!authStore.user) {
next(loginQuery);
} else {
next();
}
});
return;
}
// User exists, allow access
if (authStore.user) {
return next();
}
// No user and not loading, redirect to login
return next(loginQuery);
}
Next up: Building the guest middleware for login pages.
Related Articles
- Routing and Pages
Master SvelteKit's file-based routing system. Learn dynamic routes, route groups, optional parameters, and layout patterns.
- Form Handling: Moving from Vue to Svelte
A practical guide to translating Vue form patterns to Svelte, covering two-way binding, validation, async submission, and what actually works better in each framework.
- Building a Modal: Vue vs Svelte
A side-by-side comparison of building a modal component in Vue 3 and Svelte 5, exploring the differences in reactivity, props, and component patterns.