GrkmEldeniz
GrkmEldeniz

Reputation: 1

Next.js 15 middleware JWT route protection issue (works locally, fails in production)

I'm facing a weird issue with my Next.js App Router (v15) middleware for JWT-based route protection. I'm using jose for token verification because it's Edge-compatible.

The issue is that everything works fine locally, but fails in production. Middleware keeps redirecting users unnecessarily as if the JWT verification fails. I suspect it's either a problem with TextEncoder, environment variables, or Edge runtime behavior.

My setup:

Here’s my middleware file:

export async function middleware(request: NextRequest) {
    const { pathname } = request.nextUrl;

    const isProtectedRoute = protectedPaths.includes(pathname);
    const isAuthRoute = authPaths.includes(pathname);

    // Get tokens
    const accessToken = request.cookies.get(ACCESS_TOKEN_NAME)?.value;
    const refreshToken = request.cookies.get(REFRESH_TOKEN_NAME)?.value;

    // If the route is protected, check if the user is authenticated
    if (isProtectedRoute) {
        const redirectUrl = new URL("/sign-in", request.url);
        redirectUrl.searchParams.set("redirect", pathname + request.nextUrl.search);

        if (!accessToken) {
            return NextResponse.redirect(redirectUrl);
        }

        try {
            // Verify access token
            const { payload: accessPayload } = await jose.jwtVerify(
                accessToken,
                new TextEncoder().encode(process.env.JWT_ACCESS_SECRET || "")
            );

            // If access token is valid, continue
            if (accessPayload && typeof accessPayload.userId === "number") {
                return NextResponse.next();
            }
        } catch (error) {
            // Access token is invalid, try refresh token
            console.error("Access token verification error:", error);

            if (!refreshToken) {
                return NextResponse.redirect(redirectUrl);
            }

            try {
                // Verify refresh token
                const { payload: refreshPayload } = await jose.jwtVerify(
                    refreshToken,
                    new TextEncoder().encode(process.env.JWT_REFRESH_SECRET || "")
                );

                if (!refreshPayload || typeof refreshPayload.userId !== "number") {
                    return NextResponse.redirect(redirectUrl);
                }

                // Generate new access token
                const userId = refreshPayload.userId as number;
                const newAccessToken = await generateToken(userId, "ACCESS");

                const response = NextResponse.next();

                // Set the new access token as a cookie in the response
                response.cookies.set(ACCESS_TOKEN_NAME, newAccessToken, {
                    ...COOKIE_CONFIG,
                    maxAge: ACCESS_TOKEN_MAX_AGE,
                });

                return response;
            } catch (error) {
                // Refresh token is invalid, redirect to login
                console.error("Refresh token verification error:", error);
                return NextResponse.redirect(redirectUrl);
            }
        }
    }

    if (isAuthRoute && accessToken) {
        try {
            // Verify the access token is valid before redirecting
            await jose.jwtVerify(
                accessToken,
                new TextEncoder().encode(process.env.JWT_ACCESS_SECRET || "")
            );

            // redirect where it came from
            const redirectUrl = request.nextUrl.searchParams.get("redirect");
            if (redirectUrl) {
                return NextResponse.redirect(new URL(redirectUrl, request.url));
            }
            return NextResponse.redirect(new URL("/", request.url));
        } catch (error) {
            // Token is invalid, let them stay on the auth route
            console.error("Auth route token verification error:", error);
            return NextResponse.next();
        }
    }

    return NextResponse.next();
}

What I've checked so far:

Possible causes I'm considering:

Upvotes: 0

Views: 33

Answers (0)

Related Questions