vercel / next.js

The React Framework
https://nextjs.org
MIT License
127.53k stars 27.05k forks source link

NextJs 14 middleware redirect issue: User Keeps getting redirected to /login after successful Sign-In #59218

Open anni1236012 opened 1 year ago

anni1236012 commented 1 year ago

Link to the code that reproduces this issue

https://github.com/anni1236012/nextjsMiddleware

To Reproduce

  1. yarn dev
  2. All pages are protected via middleware except home page.
  3. Click on any link other than home page and it should redirect you to /login.
  4. Now click on login and it will set the sessionToken cookie with value "loggedin". At this stage, user is authenticated and it should allow users to see the protected pages.
  5. Now click on any protected page and you will see the /login page which is not the expected behavior. It should show the protected page immediately after the successful signin. However, it does work after a few seconds and user can see the protected page. I don't know why there is a delay.

https://github.com/vercel/next.js/assets/86418669/67bb7e52-b6a3-41c0-b4ad-3eed1eff8404

Current vs. Expected behavior

Delayed navigation, looks like all requests are not reaching middleware and served from cache.

Verify canary release

Provide environment information

Operating System:
  Platform: linux
  Arch: x64
  Version: #38~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Nov  2 18:01:13 UTC 2
Binaries:
  Node: 20.5.1
  npm: 9.8.0
  Yarn: 1.22.19
  pnpm: N/A
Relevant Packages:
  next: 14.0.4-canary.37
  eslint-config-next: 14.0.3
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.3.2
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Middleware / Edge (API routes, runtime)

Additional context

No response

SuhelMakkad commented 1 year ago

@anni1236012 try setting the status code to 303. One of the reasons it might fail is because of the browser cache. By default NextResponse.redirect will do a 307, which can be cahced by the browser.

return NextResponse.redirect(new URL("/login", request.url), {
    status: 303,
});
anni1236012 commented 1 year ago

@SuhelMakkad it did not change the behavior.

SuhelMakkad commented 12 months ago

Interesting 🤔 I am using this exact setup for my app and it works fine. I tried your repo on my local machine, and after the changes it worked. Maybe you need to clear your browser cache.

yasermazlum commented 12 months ago

I have exactly the same problem, it does not give the expected behaviour for a while

anni1236012 commented 12 months ago

I fixed it with rewrite instead of redirect.

return NextResponse.rewrite(new URL("/login", request.url), {
        status: 303,
      });
yasermazlum commented 12 months ago

@anni1236012 no, the problem is not solved. a short time still does not give the expected result

anni1236012 commented 12 months ago

@yasermazlum Please share your github link to recreate the issue.

babpulss commented 10 months ago

try to avoid loading cached page append random query on querystring ex) localhost:3000/hello=${Date.now()}

cyb3rsalih commented 6 months ago

I also stucked at login page, when I debug see that The user object found and returns true response. But the status code is 303

pedroSoaresll commented 5 months ago

At NextJS 15 (canary version) it was fixed, the server middleware is correct changing the browser URL after NextResponse.redirect.

But I guess I found another error when passing a hash parameter /some-url#value-here, the hash parameter is not sending with the redirected URL.

The problem was not completely fixed.

skychavda commented 4 months ago

I am experiencing an issue with the middleware. I need to route the user based on their role, but the middleware is not able to route properly. Here's the explanation:

I have two roles in my app, manager and staff, and a user can have either of these roles in their respective org.

When the user changes the org using a dropdown in the UI, the middleware is supposed to detect the new selected org and route the user to the corresponding URL. I've written the following logic for this:

if (orgRole === 'manager' && request.nextUrl.pathname.includes('/staff')) {
      const url = request.nextUrl.clone()
      url.pathname = `${orgShortId}/dashboard/manager/app`
      return NextResponse.redirect(url, { status: 303 })
    }

    if (orgRole === 'staff' && request.nextUrl.pathname.includes('/manager')) {
      const url = request.nextUrl.clone()
      url.pathname = `${orgShortId}/dashboard/staff/app`
      return NextResponse.redirect(url, { status: 303 })
    }

However, the issue I'm facing is that the app is not getting routed to the URL as expected; it remains unchanged.

JohnHuang-CT commented 4 months ago

might need to use router.refresh

Enzo-PVsyst commented 3 months ago

I'm facing a similar issue, where in this middleware :

import { NextRequest, NextResponse } from "next/server";
import unauthorizedRoutes from "./utils/routes";
import evaluateTokensStatus from "./utils/evaluateTokens";

export async function middleware(request: NextRequest) {
  let tokensStatus = evaluateTokensStatus();

  const isAuthenticated =
    tokensStatus === "valid" || tokensStatus === "expired";

  const isUnauthorizedRoute = unauthorizedRoutes.some((route) =>
    request.nextUrl.pathname.startsWith(route)
  );

  console.log("isUnauthorizedRoute", isUnauthorizedRoute);
  console.log("isAuthenticated", isAuthenticated);

  if (!isAuthenticated && !isUnauthorizedRoute) {
    return NextResponse.redirect(new URL("/auth/login", request.url), {
      status: 303,
    });
  }

  if (isAuthenticated && isUnauthorizedRoute) {
    console.log("should redirect to /");
    return NextResponse.redirect(new URL("/", request.url), {
      status: 303,
    });
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|.*\\.png$).*)"],
};

When redirect should happen it is not working, and nothing happens.

Strange thing ....

avalanche-tm commented 3 months ago

Any solution to this? I have the same issue. NextResponse.redirect doesn't redirect but stays on the same site. I'm using 14.3.0-canary.30, adding status 303 to redirect doesn't help either.

skychavda commented 3 months ago

I did one workaround though it is not a perfect solution but it worked fine in my case.

I have one drop-down through which I am changing the orgs in user accounts so when the user switches between orgs I add one line in the click event,

window.location.href = "/"

So here what happens is, the app sends the user to root and nextjs middleware executes the code as per my needs.

I hope this workaround helps you. ✌️

Gamez0 commented 2 months ago

Workaround for all, Type redirectResponse.headers.set("x-middleware-cache", "no-cache"); to set x-middleware-cache to no-cache Middleware isn't running due to cahce. Seems Vercel team is coming up with the fix but anyone can't wait plz check below.

import { type NextMiddlewareResult } from "next/dist/server/web/types";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export const middleware = async (request: NextRequest): Promise<NextMiddlewareResult> => {
  const cookieHeader = request.headers.get("cookie");
  const cookies = cookieHeader
    ? Object.fromEntries(cookieHeader.split("; ").map((c) => c.split("=")))
    : {};

  const accessToken = cookies["accessToken"];
  const userType = cookies["userType"];

  if (
    request.nextUrl.pathname.startsWith("/profile")
  ) {
    if (!accessToken) {
      const redirectResponse = NextResponse.redirect(new URL("/login", request.url));
      redirectResponse.headers.set("x-middleware-cache", "no-cache"); // Set x-middleware-cache to no-cache
      return redirectResponse;
    }
  }

  if (request.nextUrl.pathname.startsWith("/admin")) {
    if (userType !== "admin") {
      const redirectResponse = NextResponse.redirect(new URL("/", request.url));
      redirectResponse.headers.set("x-middleware-cache", "no-cache"); // Set x-middleware-cache to no-cache
      return redirectResponse;
    }
  }

  const response = NextResponse.next();
  response.headers.set(`x-middleware-cache`, `no-cache`); // Set x-middleware-cache to no-cache
  return response;
};

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)"],
};
lpknv commented 2 months ago

I'm facing a similar issue, where in this middleware :

import { NextRequest, NextResponse } from "next/server";
import unauthorizedRoutes from "./utils/routes";
import evaluateTokensStatus from "./utils/evaluateTokens";

export async function middleware(request: NextRequest) {
  let tokensStatus = evaluateTokensStatus();

  const isAuthenticated =
    tokensStatus === "valid" || tokensStatus === "expired";

  const isUnauthorizedRoute = unauthorizedRoutes.some((route) =>
    request.nextUrl.pathname.startsWith(route)
  );

  console.log("isUnauthorizedRoute", isUnauthorizedRoute);
  console.log("isAuthenticated", isAuthenticated);

  if (!isAuthenticated && !isUnauthorizedRoute) {
    return NextResponse.redirect(new URL("/auth/login", request.url), {
      status: 303,
    });
  }

  if (isAuthenticated && isUnauthorizedRoute) {
    console.log("should redirect to /");
    return NextResponse.redirect(new URL("/", request.url), {
      status: 303,
    });
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|.*\\.png$).*)"],
};

When redirect should happen it is not working, and nothing happens.

Strange thing ....

I do have a similar issue following the official docs / guide https://nextjs.org/docs/app/building-your-application/authentication#optimistic-checks-with-middleware-optional. The redirect simply does not work as expected.

Gamez0 commented 2 months ago

@lpknv Try disable middleware cache before you return response using redirectResponse.headers.set("x-middleware-cache", "no-cache");

lpknv commented 2 months ago

@lpknv Try disable middleware cache before you return response using redirectResponse.headers.set("x-middleware-cache", "no-cache");

Thanks, I will give it a try!

joshjh2002 commented 2 months ago

@lpknv Did that work? I am having the same issue trying to deploy to a digital ocean server

lpknv commented 2 months ago

"x-middleware-cache", "no-cache");

I didn't try it yet. If you are curious you can add redirectResponse.headers.set("x-middleware-cache", "no-cache"); to your middleware. I don't think it has something to do with digital ocean server.

joshjh2002 commented 2 months ago

@lpknv unfortunately it didn't work. I belive my issue stems from my SSL certificate which is described here. My SSL guy is sorting it out now.

https://vercel.com/guides/resolve-err-too-many-redirects-when-using-cloudflare-proxy-with-vercel

Edit: it was an SSL issue, as well as an API being redirected to the wrong route which caused a loop during production as the environment variable wasn't set properly

Phreeshad commented 1 month ago

I fixed it with rewrite instead of redirect.

return NextResponse.rewrite(new URL("/login", request.url), {
        status: 303,
      });

This might be because your use case only requires server-side. However, this doesn’t actually solve the issue. https://www.dhiwise.com/post/nextjs-rewrites-everything-you-need-to-know

lubojanski commented 1 month ago

for me the problem was that I was trying to make the middleware cleaner

export async function middleware(request: NextRequest) {

  test(request);

  return NextResponse.next();
}

function test(request: NextRequest) {
  return NextResponse.redirect(new URL('/someroute', request.url))
}

this didn't work until I just squashed everything into middleware function directly

lpknv commented 1 month ago

for me the problem was that I was trying to make the middleware cleaner

export async function middleware(request: NextRequest) {

  test(request);

  return NextResponse.next();
}

function test(request: NextRequest) {
  return NextResponse.redirect(new URL('/someroute', request.url))
}

this didn't work until I just squashed everything into middleware function directly

I can't recall that in the nextjs docs that you can use Middleware everywhere you want.

toannguyenUITT commented 1 month ago

`

export default async function middleware(req: NextRequest) { const publicPathnameRegex = RegExp( ^(/(${locales.join('|')}))?(${publicRoutes .flatMap((p) => (p === '/' ? ['', '/'] : p)) .join('|')})/?$, 'i', ) const isPublicPage = publicPathnameRegex.test(req.nextUrl.pathname)

if (isPublicPage) { return intlMiddleware(req) } else { // return (authMiddleware as any)(req)

const token = await getToken({ req, secret: secret })

if (token && Math.floor(Date.now() / 1000) < (token.exp as number)) {
  // Token is valid, continue with the request
  return intlMiddleware(req)
}

return NextResponse.redirect(`http://localhost:3000`, 303)

} }

export const config = { // Skip all paths that should not be internationalized matcher: ['/((?!api|_next|.\..).*)'], }

`

Still stuck :((, the network tab show the post request to '/' (200 success) but redirect not perform ,

sunipan commented 4 weeks ago

@lubojanski

for me the problem was that I was trying to make the middleware cleaner

export async function middleware(request: NextRequest) {

  test(request);

  return NextResponse.next();
}

function test(request: NextRequest) {
  return NextResponse.redirect(new URL('/someroute', request.url))
}

this didn't work until I just squashed everything into middleware function directly

You're not returning your test function result in the middleware going straight to NextResponse.next()

truongnv98 commented 2 weeks ago

I'm facing this issue. Any update. I'm using the next.js 15.0.1

Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

joshjh2002 commented 2 weeks ago

@truongnv98

It probably isn't the same for you, but my issue was a combination of a mistyped environment variable name for the API route and my SSL certificate being invalid so it tried to route to a HTTP version

lpknv commented 2 weeks ago

@truongnv98

It probably isn't the same for you, but my issue was a combination of a mistyped environment variable name for the API route and my SSL certificate being invalid so it tried to route to a HTTP version

"mistyped environment variable name" - I can relate to that haha...