Next JS - Supabase Error: AuthApiError: Invalid Refresh Token: Already Used
Asked Answered
M

1

8

I'm trying to implement Supabase Auth in a Next JS project

Now I'm encounter the following error which keeps repeating when I npm run dev:

[AuthApiError: Invalid Refresh Token: Already Used]{
 __isAuthError: true,
name: 'AuthApiError',
status: 400
}

this is my middleware.ts

import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { createMiddlewareClient } from "@supabase/auth-helpers-nextjs";
import { Database } from "./types";

const locales = ["en", "ar"];
const defaultLocale = "en";
const PUBLIC_FILE = /\.(.*)$/;

export async function middleware(req: NextRequest) {
  const res = NextResponse.next();
  const supabase = createMiddlewareClient<Database>({ req, res });
  const user = await supabase.auth.getSession();

  if (
    req.nextUrl.pathname.startsWith("/_next") ||
    req.nextUrl.pathname.includes("/api/") ||
    PUBLIC_FILE.test(req.nextUrl.pathname)
  ) {
    return;
  }

  const pathname = req.nextUrl.pathname;

  // Manage route protection
  const isAuthenticated = user?.data?.session?.user.role === "authenticated";

  const isLoginPage =
    pathname.startsWith("/login") || pathname.startsWith("/ar/login");

  const sensitiveRoutes = ["/", "/ar"];
  const isAccessingSensitiveRoute = sensitiveRoutes.some((route) =>
    pathname.startsWith(route),
  );

  if (isLoginPage) {
    if (isAuthenticated) {
      return NextResponse.redirect(
        new URL(`${pathname.startsWith("/ar") ? "/ar" : "/en"}/`, req.url),
      );
    }

    if (
      pathname.startsWith(`/${defaultLocale}/`) ||
      pathname === `/${defaultLocale}`
    ) {
      return NextResponse.redirect(
        new URL(
          pathname.replace(
            `/${defaultLocale}`,
            pathname === `/${defaultLocale}` ? "/" : "",
          ),
          req.url,
        ),
      );
    }
  }

  if (!isAuthenticated && isAccessingSensitiveRoute && !isLoginPage) {
    return NextResponse.redirect(
      new URL(`${pathname.startsWith("/ar") ? "/ar" : ""}/login`, req.url),
    );
  }

  // Check if the default locale is in the pathname
  if (
    pathname.startsWith(`/${defaultLocale}/`) ||
    pathname === `/${defaultLocale}`
  ) {
    // e.g. incoming request is /en/products
    // The new URL is now /products
    return NextResponse.redirect(
      new URL(
        pathname.replace(
          `/${defaultLocale}`,
          pathname === `/${defaultLocale}` ? "/" : "",
        ),
        req.url,
      ),
    );
  }

  const pathnameIsMissingLocale = locales.every(
    (locale) =>
      !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`,
  );

  if (pathnameIsMissingLocale) {
    // We are on the default locale
    // Rewrite so Next.js understands

    // e.g. incoming request is /products
    // Tell Next.js it should pretend it's /en/products
    return NextResponse.rewrite(
      new URL(`/${defaultLocale}${pathname}`, req.url),
    );
  }
}

export const config = {
  matchter: [
    "/",
    "/login",
    "/((?!api|_next/static|_next/image|assets|favicon.ico).*)",
  ],
};

sometimes it's solved automatically (keeps repeating several times then stop) redirecting me to login page, and sometimes it keeps looping in this error without stopping.

Medlock answered 13/10, 2023 at 10:16 Comment(1)
Thank you for asking this question. I kept getting this error on my mobile app and couldn't tell what it was from so just knowing that it's Supabase is helpful.Vertical
O
1

The problem lies here:

const user = await supabase.auth.getSession();

This will not refresh your token. As the docs say:

Never trust supabase.auth.getSession() inside server code such as middleware. It isn't guaranteed to revalidate the Auth token.

It's safe to trust getUser() because it sends a request to the Supabase Auth server every time to revalidate the Auth token.

A middleware implementation based on the recommended way in the docs would look like this:

import { createServerClient, type CookieOptions } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';

export async function middleware(request: NextRequest) {
  let response = NextResponse.next({
    request: {
      headers: request.headers,
    },
  });

  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,
          });
        },
      },
    },
  );

await supabase.auth.getUser();


  return response;
}
Oxley answered 30/5 at 7:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.