I'm trying to build an authentication feature for my web app following this online tutorial.
I created the middleware.ts for my web app, and saw that there is a const isLoggedIn
so i thought i can use this boolean in every other pages to constantly authenticate if user is logged in.
so i abstracted it out like this
export const checkIsLoggedIn = (req: any): boolean => {
return !!req.auth;
};
since this is the middleware which is server action, and my components are all client actions, i decided to create an api such that checkisloggedin will be passed from middleware.ts -> check-auth.ts (the api) -> main-nav.tsx (the component i want to use to check if user is logged in).
but when i console.log to see if my api is working, my browser's inspect element console returned this error
[main-nav-tsx][checkAuthStatus]: Error fetching auth status
SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
window.console.error @ app-index.js:33
Apparently it meant that my api route wasnt found.
The issue: Im not sure how else i can pass checkisloggedin boolean to my components as every method i tried to check if user is logged in isn't working. all my directories seemed correct too.
Expected outcome: I just need a way to check if user is logged in, and show the signout button as needed.
Im also open to new solutions involving sessions and tokens, but i have no idea how to use any of them.
These are my codes. Since NextJS is particular about file directories, I commented where each file is stored at, at the very top.
// src/middleware.ts
// import { auth } from "./auth"
import authConfig from "./auth.config"
import NextAuth from "next-auth"
import {
DEFAULT_LOGIN_REDIRECT,
apiAuthPrefix,
authRoutes,
publicRoutes,
} from "@/routes"
import { NextResponse } from "next/server";
const { auth } = NextAuth(authConfig);
export const checkIsLoggedIn = (req: any): boolean => {
return !!req.auth;
};
export default auth((req) => {
const { nextUrl } = req;
const isLoggedIn = !!req.auth;
console.log('[middleware-ts][checkisloggedin]: ', checkIsLoggedIn(req));
console.log('[middleware-ts][isloggedin]: ', isLoggedIn);
const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix);
const isPublicRoute = publicRoutes.includes(nextUrl.pathname);
const isAuthRoute = authRoutes.includes(nextUrl.pathname);
if (isApiAuthRoute) {
return NextResponse.next();
}
if (isAuthRoute) {
if (isLoggedIn) {
return NextResponse.redirect(new URL(DEFAULT_LOGIN_REDIRECT, nextUrl))
}
return NextResponse.next();
}
if (!isLoggedIn && !isPublicRoute) {
return NextResponse.redirect(new URL("/auth/login", nextUrl));
}
return NextResponse.next();
})
export const config = {
// matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
// matcher: ["/auth/login", "/auth/register"],
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
}
// src/components/main-nav.tsx
"use client"
import * as React from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { docsConfig } from "@/config/docs"
import { siteConfig } from "@/config/site"
import { cn } from "@/lib/utils"
import { Icons } from "@/components/icons"
import { signOut } from "@/auth"
export function MainNav() {
const pathname = usePathname()
const [isLoggedIn, setIsLoggedIn] = React.useState(false);
React.useEffect(() => {
const checkAuthStatus = async () => {
console.log('[main-nav-tsx][checkAuthStatus]: Checking auth status...');
try {
const response = await fetch('/app/api/authentication/check-auth');
if (response.ok) {
const data = await response.json();
console.log('[main-nav-tsx][checkAuthStatus]: Auth status response:', data);
setIsLoggedIn(data.isLoggedIn);
} else {
console.error('[main-nav-tsx][checkAuthStatus]: Error fetching auth status', response.status);
}
} catch (error) {
console.error('[main-nav-tsx][checkAuthStatus]: Error fetching auth status', error);
}
}
checkAuthStatus();
}, []);
/**
* handleSignOut is a server side action to check for log in
* main-nav is a client side action
* even though the method is post, we cannot use server action for checking if logged in is true or false
* hence a POST api is needed
*/
const handleSignOut = async () => {
const response = await fetch('/app/api/authentication/signout', {
method: 'POST',
});
if (response.ok) {
// Handle successful sign-out, e.g., redirect or update state
setIsLoggedIn(false);
} else {
// Handle error
console.error('Error signing out');
}
};
return (
<div className="mr-4 hidden md:flex">
<Link href="/" className="mr-6 flex items-center space-x-2">
<Icons.logo className="h-6 w-6" />
<span className="hidden font-bold sm:inline-block">
{siteConfig.name}
</span>
</Link>
<nav className="flex items-center gap-4 text-sm lg:gap-6">
{docsConfig.mainNav?.map(item => (
item.href && (
<Link
key={item.href}
href={item.href}
className={cn(
"transition-colors hover:text-foreground/80",
pathname === item.href ? "text-foreground" : "text-foreground/60"
)}
>
{item.title}
</Link>
)
))}
{/* change this button else where */}
{isLoggedIn && (
<button onClick={handleSignOut}>Sign Out</button>
)}
</nav>
</div>
)
}
// src/app/api/authentication/check-auth.ts
import { NextApiRequest, NextApiResponse } from 'next'
import { checkIsLoggedIn } from '../../../middleware'
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const isLoggedIn = checkIsLoggedIn(req);
// console.log("[check-auth-ts]: ", isLoggedIn)
res.status(200).json({ isLoggedIn });
}
The tutorial you provided utilizes 3 party libraries for authentication, more specifically next-auth. So if you want to access session data, all you need is just to use useSession hook. It provides status field, which may be one of following values: "loading" | "authenticated" | "unauthenticated"
.
However, you are also asking how to pass data from middleware to pages, which is a bit more complicated. As for the current version (14.2.4) Next.js uses Edge runtime for middlewares and api routes, while using Node for rendering by default. So I doubt it's even possible to do so. Well, you may opt-in to edge runtime, but Next.js still does not provide any direct way to access req
object, if you use App router. See discussions here.
As for possible solutions for passing data from middleware, I may suggest a few solutions:
Move middleware logic to template.tsx
or layout.tsx
component. There you can access you have access to headers and cookies and retrieve necessary data from them. Then you may put it in state management solution you're comfortable with (Redux, React Context, etc). Something like this:
export const Layout = ({ children }: LayoutProps) => {
const userData = getUserData();
return (
<StateProvider userData={userData}>
{children}
</StateProvider>
)
}
This way it would be more convenient to pass more user data if needed - user info, role, etc. Also, that's a good place to make a network request, if data retrieved from it is relevant for the whole application.
If that's not an option for you due to some constraints, you can make a pretty hacky solution, but working nonetheless. You can create a custom header, set it in middleware and retrieve it from your page.
// middleware.ts
const myMiddleware = (req: Request, res: Response, next: NextFunction) => {
const userIsAuthenticated = checkAuthentication(req);
req.headers['x-user-authenticated'] = userIsAuthenticated;
return next();
};
// page.ts
const MyPage = () => {
const userIsAuthenticated = headers().get('x-user-authenticated');
// rest of the logic
};
However, I would abstain from this solution, because usually in the context of authentication, you need more than a single field. Well, passing each individual field in a separate header is cumbersome.
next-auth
library (or something similar), it provides an api to retrieve session data within pages - useSession
hook for instance