Advice on authentication guards with server middleware

Hi everyone, I was hoping to get some advice on how to handle custom auth within Nuxt. (It's worth noting we're deliberately not using a package, because we have a lot of auth code already that is custom and being re-used for this app) Right now, I have four pieces set up: - Various server auth endpoints to login, set session cookie, verify session - Composable useUserSession that gets the session with a useFetch('/api/session') - Nuxt middleware that redirects to /login if not logged in / expired token - Server middleware that checks if the token is valid (for API routes that will be called from the client) The problem I have is that the server middleware is hit before any Nuxt middleware, so any request is blocked with a 401 immediately - even the login page. And I know I should not be redirecting in server middleware because then we look at an infinite loop on the middleware. I added "allowed routes" to the middleware, but then that means I'd have to add every single frontend page to the allowed routes in order for them to then be correctly redirected via nuxt middleware. I know this can't be the answer - am I missing an obvious development pattern here? Thanks very much in advance for your advice.
3 Replies
kapa.ai
kapa.ai4w ago
Hi @Ollie1700. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- You're facing a common challenge with authentication in Nuxt applications. Based on the knowledge sources, I can provide some guidance on how to structure your authentication flow more effectively. Understanding the Issue Your current setup has server middleware checking authentication before Nuxt route middleware runs, which is causing all routes (including public ones like login) to be blocked with 401 errors. This is happening because server middleware executes earlier in the request lifecycle than route middleware. Recommended Approach 1. Separate API Routes from Page Routes The key insight is that you should only protect API routes with server middleware, not page routes:
// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
// Only check authentication for API routes
if (event.path.startsWith('/api/') && !event.path.startsWith('/api/session') && !event.path.startsWith('/api/login')) {
// Your authentication logic here
// If not authenticated, throw a 401 error
throw createError({
statusCode: 401,
message: 'Unauthorized'
})
}
})
// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
// Only check authentication for API routes
if (event.path.startsWith('/api/') && !event.path.startsWith('/api/session') && !event.path.startsWith('/api/login')) {
// Your authentication logic here
// If not authenticated, throw a 401 error
throw createError({
statusCode: 401,
message: 'Unauthorized'
})
}
})
2. Use Route Middleware for Page Protection For protecting pages, use Nuxt route middleware:
// middleware/authenticated.ts
export default defineNuxtRouteMiddleware(() => {
const { loggedIn } = useUserSession()

// redirect the user to the login screen if they're not authenticated
if (!loggedIn.value) {
return navigateTo('/login')
}
})
// middleware/authenticated.ts
export default defineNuxtRouteMiddleware(() => {
const { loggedIn } = useUserSession()

// redirect the user to the login screen if they're not authenticated
if (!loggedIn.value) {
return navigateTo('/login')
}
})
Then apply this middleware to the routes you want to protect:
// In your page component
definePageMeta({
middleware: ['authenticated'],
})
// In your page component
definePageMeta({
middleware: ['authenticated'],
})
3. Handle API Authentication Errors For handling 401 errors from API calls, you can create a custom fetch instance that handles authentication errors:
// plugins/api.ts
export default defineNuxtPlugin((nuxtApp) => {
const api = $fetch.create({
// Your base URL if needed
async onResponseError({ response }) {
if (response.status === 401) {
await nuxtApp.runWithContext(() => navigateTo('/login'))
}
}
})

return {
provide: {
api
}
}
})
// plugins/api.ts
export default defineNuxtPlugin((nuxtApp) => {
const api = $fetch.create({
// Your base URL if needed
async onResponseError({ response }) {
if (response.status === 401) {
await nuxtApp.runWithContext(() => navigateTo('/login'))
}
}
})

return {
provide: {
api
}
}
})
This approach is recommended in the Custom useFetch in Nuxt documentation. Important Considerations 1. Return vs. navigateTo: When using middleware, be careful with how you handle navigation. As noted in a Stack Overflow answer, returning navigateTo() is the correct approach, not using router.push() which can cause the "glitchy" behavior you might be experiencing Stack Overflow Post. 2. Avoid infinite redirects: Make sure your middleware doesn't cause infinite redirects by returning nothing when you want to continue the navigation:
if (userIsAuthenticated) {
return // returns nothing means continue the middleware
}
return navigateTo('/login')
if (userIsAuthenticated) {
return // returns nothing means continue the middleware
}
return navigateTo('/login')
This pattern is explained in another Stack Overflow post. 3. Server-side navigation: If you need to handle redirects server-side in specific cases, you can use the approach from this Stack Overflow answer:
if (import.meta.server) {
const response = requestEvent.node.res;
if (!response.headersSent) {
response.writeHead(302, {Location: '/login'});
response.end();
}
}
if (import.meta.server) {
const response = requestEvent.node.res;
if (!response.headersSent) {
response.writeHead(302, {Location: '/login'});
response.end();
}
}
By implementing these patterns, you should be able to create a more effective authentication system that properly handles both API routes and page routes without causing infinite redirects or blocking public routes. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai4w ago
<@98366949871865856> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Ollie1700
Ollie1700OP4w ago
Ah of course, I can just distinguish by the /api prefix 🤦‍♂️ I've been so in the weeds of understanding when SSR vs Client happens I didn't consider it!

Did you find this page helpful?