Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next.js middleware token rotation issue #709

Open
olafurns7 opened this issue Feb 14, 2024 · 3 comments
Open

Next.js middleware token rotation issue #709

olafurns7 opened this issue Feb 14, 2024 · 3 comments

Comments

@olafurns7
Copy link

Hey, thanks for all your hard work around iron-session!

We're implementing authentication, and checks via Next.js 14 middleware. An issue we are running into is that if we detect that a session has expired, and we want to refresh it during the middleware run, the first response back to the client/page usually still has the expired session.

We've tested using both getIronSession with req,res and using the Next.js cookies.

I, believe that for iron-session to fully work within the middleware there needs to be a similar functionality as how Supabase sets up its client:

https://supabase.com/docs/guides/auth/server-side/nextjs?router=app

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return request.cookies.get(name)?.value
        },
        set(name: string, value: string, options: CookieOptions) {
          request.cookies.set({
            name,
            value,
            ...options,
          })
          response = NextResponse.next({
            request: {
              headers: request.headers,
            },
          })
          response.cookies.set({
            name,
            value,
            ...options,
          })
        },
        remove(name: string, options: CookieOptions) {
          request.cookies.set({
            name,
            value: '',
            ...options,
          })
          response = NextResponse.next({
            request: {
              headers: request.headers,
            },
          })
          response.cookies.set({
            name,
            value: '',
            ...options,
          })
        },
      },
    }
  )
@headironc
Copy link

Excuse, did you solve this problem?

@Emrin
Copy link

Emrin commented May 3, 2024

I am also observing the bug where after updating the session in the middleware, the first response in a server component is stale.

Edit: This was an issue with next but it was fixed in v14.3.0-canary.24
The issue is still present in iron session however.

@olafurns7
Copy link
Author

@headironc and everyone else that is trying to deal with this.

What we basically did was to create a next.js api route, in our case: /api/refresh-token.

In the middleware check, if a user has a authentication session, but it has "expired", we want to attempt to refresh the authentication the user has.

middleware.tsx

....

  if (session && session?.isLoggedIn) {
    const tokenValid = (await tokenValidation(session)).valid;

    /**
     * Instead of doing a refresh of the token here, we can bounce the user
     * to the auth portal, where the refresh logic is attempted, and if successful the user just gets
     * redirected here
     */
    if (!tokenValid) {
      return NextResponse.redirect(`${req.nextUrl.origin}/api/refresh-token`);
    }
  }
....

the /api/refresh-token endpoint doesn't do much else than checking if there is a session, and attempt to refresh the session authentication.

export async function GET(req: NextRequest) {
  const session = await getSession();
  if (!session.isLoggedIn) {
    return NextResponse.redirect(authRedirectUrl());
  }

  const correlationId = getCorrelationId(req.headers);

  log.info({
    msg: 'Refreshing token',
    user: session?.username,
    correlationId,
  });
  const newSession = await refreshAuthentication();

  if (!newSession) {
    log.info({
      msg: 'Failed to refresh token',
      user: session?.username,
      correlationId,
    });
    return NextResponse.redirect(authRedirectUrl(), {
      headers: {
        'x-correlation-id': correlationId,
      },
    });
  }

  return NextResponse.redirect(env.PUBLIC_URL, {
    headers: {
      'x-correlation-id': correlationId,
    },
  });
}

export const dynamic = 'force-dynamic';

So basically by having the middleware do a redirect to a next.js api route or route handler, the route handler does the updating and then redirects back to where the user wanted to go.

This has at least worked for us.

the refreshAuthentication function basically does the following:

  1. gets the current session
  2. attempts to get new credentials from our database
  3. updates the session and calls sesion.save() and returns the session

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants