[디버깅] Supabase Auth getUser() AuthSessionMissingError

POLO·2024년 9월 20일

소셜 로그인 인증 라이브러리로 supabase auth를 사용하게 되어 iron-session이 아닌 supabase auth에서 제공하는 쿠키 기반 세션으로 변경했다.

AuthSessionMissingError

Supabase Auth에서 제공하는 Next.js 가이드를 미들웨어 부분을 빼고 따라서 코드를 작성했다.
password 기반 로그인 이후 getUser()로 세션 정보를 가져오려고 했으나 다음과 같은 오류가 나타났다.

AuthSessionMissingError: Auth session missing!
    at eval (webpack-internal:///(rsc)/./node_modules/@supabase/auth-js/dist/module/GoTrueClient.js:888:59)  
    at SupabaseAuthClient._useSession (webpack-internal:///(rsc)/./node_modules/@supabase/auth-js/dist/module/GoTrueClient.js:790:26)
    at async SupabaseAuthClient._getUser (webpack-internal:///(rsc)/./node_modules/@supabase/auth-js/dist/module/GoTrueClient.js:880:20)
    at async eval (webpack-internal:///(rsc)/./node_modules/@supabase/auth-js/dist/module/GoTrueClient.js:867:20)
    at async eval (webpack-internal:///(rsc)/./node_modules/@supabase/auth-js/dist/module/GoTrueClient.js:735:28) {
  __isAuthError: true,
  status: 400,
  code: undefined

Supabase에서는 사용자 세션을 가져올 때 getSession()이 아닌 getUser() 사용을 권장했다.
그 이유는 getSession()은 인증 토큰 재검증이 보장되지 않기 떄문이다.

getSession() : 클라이언트가 현재 가지고 있는 세션 정보(access_token 및 refresh_token 그리고 사용자 정보)를 그대로 반환한다. 현재 클라이언트에 저장된 세션 정보를 반환하기만 할 뿐, 이 세션이 유효한지 여부를 서버에 확인하지 않는다. 클라이언트 측에서 access_token이 만료되었더라도 이를 확인하지 않고 만료된 세션 정보를 그대로 반환한다.
getUser(): 현재 쿠키에 저장되어 있는 access_token을 서버에 전송하여, 이 토큰이 유효한지 검증하고, 해당 토큰에 연관된 사용자 정보를 가져온다. access_token이 만료된 경우, refresh_token을 사용하여 새로운 access_token을 발급하고, 그 후 사용자 정보를 반환한다. 즉, 항상 최신 상태의 인증 정보를 가져오며, 세션이 유효한지 재검증하는 과정을 포함한다.

오류가 나는 getUser() 대신 getSession()을 사용하면 이미 쿠키에 저장되어 있는 세션 정보를 잘 가지고 왔다.

오류 해결

이 과정에서 자동 완성 추천 내역에서 setSession() 메서드를 찾았다.

getUser() 오류가 AuthSessionMissingError이었으니 왠지 setSession()을 이용하면 해결될 거 같았다.

(그 전에 두 시간 동안 구글링을 했었지만 로그인 이후 해당 오류가 발생했다는 내용은 없었다.. 외국인이 업로드한 Supabase Authentication with Next.js 영상을 봤지만 영상에서는 getSession()을 사용해 세션 정보를 가져왔다.. )

그래서 login server action에서 signInWithPassword()으로 받은 data에서 액세스 토큰과 리프레시 토큰을 setSession()으로 넘겨주는 로직을 추가했다.

"use server";

import { z } from "zod";
import loginFormSchema from "../schema/login-form-schema";

import { createClientServerSide } from "@/shared";
import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";

const login = async (
  formData: z.infer<typeof loginFormSchema>,
): Promise<string> => {
  const supabase = await createClientServerSide();
  const { data, error } = await supabase.auth.signInWithPassword({
    email: formData.userEmail,
    password: formData.password,
  });

  if (error || !data)
    return "가입되지 않은 이메일이거나 잘못된 패스워드입니다.";

  if (!data || !data.session) return "세션 설정을 실패했습니다.";

  await supabase.auth.setSession({
    access_token: data.session.access_token,
    refresh_token: data.session.refresh_token,
  });

  /*전체 경로에 대한 캐시를 무효화하고 루트 페이지로 리다이렉트*/
  revalidatePath("/", "layout");
  redirect("/");
};
export default login;

오류가 해결이 됐다.

결론은 내가 middleware를 작성하지 않았기 때문에 이런 일이 발생했던 거였다..

가이드에서도 미들웨어에서는 setSession()을 이용해 세션을 설정하는 게 아니라 NextRequest에서 쿠키를 꺼내와서 그걸로 세션을 설정한다.

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

export async function updateSession(request: NextRequest) {
  let supabaseResponse = NextResponse.next({
    request,
  });

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return request.cookies.getAll();
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) =>
            request.cookies.set(name, value),
          );
          supabaseResponse = NextResponse.next({
            request,
          });
          cookiesToSet.forEach(({ name, value, options }) =>
            supabaseResponse.cookies.set(name, value, options),
          );
        },
      },
    },
  );
  return supabaseResponse;
}

export async function middleware(request: NextRequest) {
  return await updateSession(request);
}

export const config = {
  matcher: [
    // 정적 파일 및 이미지, favicon 경로를 제외한 모든 경로에 대해 미들웨어 적용
    "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
  ],
};

하지만 setSession()도 동일한 기능을 하는 걸로 보이는데..
미들웨어를 굳이 작성해야 하는 걸까 싶었지만 supabase.auth.getUser()를 사용하면 토큰 만료 시 자동으로 리프레시 토큰이 유효한 경우 갱신이 이루어지는데, 이 때를 위해 미들웨어를 설정해 줘야 하는 거 같다.

setSession()은 세션을 수동으로 설정할 필요가 있는 경우에만 사용하면 될 거 같은데 그게 언제인지 몰라서 스택오버플로우에 질문을 남겨야겠다..

0개의 댓글