인프런 커뮤니티 질문&답변

성민최님의 프로필 이미지
성민최

작성한 질문수

Next + React Query로 SNS 서비스 만들기

완전히 로그아웃 하기 & 프론트 서버에서 쿠키보내기

connect.sid 삭제가 되지 않습니다.

해결된 질문

작성

·

247

·

수정됨

0

개별적으로 서버를 만들어서 진행중입니다.

// 로그인시 서버에서 발급하는 코드
    response.cookie('connect.sid', accessToken, {
      httpOnly: true,
      sameSite: 'none',
      secure: false,
    });



// 프론트에서 아래와 같이 저장
if (res.ok && res.status === 204) {
let setCookie = res.headers.get("set-cookie");

if (setCookie) {
const parsed = cookie.parse(setCookie);
cookies().set("connect.sid", parsed["connect.sid"]);
const user = jwtDecode(parsed["connect.sid"]);

return {
...user,
            };
          }
        }



// 서버에서 삭제하는 코드
  logout(request: any, response: Response) {
    const { user } = request;
    response.clearCookie('connect.sid', {
      httpOnly: true,
      sameSite: 'none',
      secure: false,
    });
...
  }

로그인 하면 서버에서 쿠키 설정 -> 프론트에서 저장을 진행한 후, 로그아웃시 위 코드처럼 삭제하도록 하고있습니다.

강의 하단부에 작성해주신것 처럼 아래와 같이 events부분에 작성하였는데 쿠키 삭제가 되지 않습니다 ㅠㅠ events에 넣지 않고 강의 그대로 했을 때는 s%3..으로 바뀌어서 삭제되지 않습니다. events에 넣었을때는 아예 값도 바뀌지 않고 삭제되지 않습니다. 어떤 작업이 필요할까요 ㅠㅠ

  //auth.ts
events: {
    signOut: async (data) => {
      const token = cookies().get("connect.sid");
      if (!token) return;
      const res = await logout(token.value);
      console.log("signOut");
    },
  },

// logout fetch
export const logout = async (token: string) => {
  const res = await fetch(`${BASE_URL}/logout`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${token}`,
    },
    credentials: "include",
  });

  return res;
};

답변 3

0

성민최님의 프로필 이미지
성민최
질문자

며칠 혼자 시도해보다가 안돼서 결국 답변으로 질문 다시 남깁니다 ㅠ
제 증상은 다음과 같습니다.

  1. 로그인 이후 특정 페이지 이동 시 connect.sid가 s%3...으로 변경됨

  2. 로그아웃시에도 삭제되지 않고 위와 동일하게 변경됨. (아래 사진)

image

순서별로 주석으로 설명드리겠습니다.

로그인은 다음과 같습니다.

// 프론트에서 아래와 같이 로그인을 시도합니다 
async function onSubmit(data: z.infer<typeof FormSchema>) {
    const result = await signIn("credentials", {
      email: data.email,
      password: data.password,
      redirect: false,
    });

    if (result?.error) {
      alert("아이디 / 비밀번호를 다시 확인해주세요.");
    } else {
      router.replace("/");
    }
  }

// 백엔드에서는 컨트롤러를 거쳐 아래와 같이 토큰을 발급합니다.
  async setRefreshTokenToUser(user: IUser, response) {
    const payload = {
      ....
    };
    const { accessToken, refreshToken } = await this.generateTokens(payload);

    await this.authRepository.setRefreshToken(user.id, refreshToken);
    response.cookie('connect.sid', accessToken, {
      httpOnly: true,
      sameSite: 'none',
      secure: false,
      path: '/',
      domain: 'localhost',
    });
  }

// 이후 프론트의 auth.ts에서는 다음과 같이 로그인 로직을 진행중입니다.
authorize: async (credentials): Promise<any> => {
        const res = await login(
          credentials.email as string,
          credentials.password as string
        );

        if (res.ok && res.status === 204) {
          let setCookie = res.headers.get("set-cookie");

          if (setCookie) {
            const parsed = cookie.parse(setCookie);
            const token = parsed["connect.sid"];
            cookies().set("connect.sid", token);
            const user = jwtDecode(parsed["connect.sid"]);

            return {
              ...user,
            };
          }
        }

        if (res.status === 401) {
          const result = await res.json();
          throw new Error(result.message || "Failed to login");
        }

        return null;
      },
    }),
  ],

//login함수는 다음과 같습니다.
export const login = async (email: string, password: string) => {
  const res = await fetch(`${BASE_URL}/login/email`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    credentials: "include",
    body: JSON.stringify({ email, password }),
  });

  return res;
};


로그아웃은 다음과 같습니다.

// 로그아웃 버튼에서 다음과 같습니다.
  const onLogout = async () => {
    await signOut({ callbackUrl: "/" });
    // router.replace("/");
  };

// 프론트의 auth.ts입니다. nextauth 로그아웃을 먼저 진행하고 connect.sid를 삭제하려고 하였습니다.
 events: {
    signOut: async (data) => {
      const token = cookies().get("connect.sid");
      if (!token) return;
      await logout(token.value);
    },
  },

// 로그아웃 fetch 입니다 (달러부분은 에디터 오류로 대체)
export const logout = async (token: string) => {
  const res = await fetch(`${BASE_URL}/logout`, {
    method: "POST", 
   headers: {
Authorization : `Bearer (달러){token}`
}
  credentials: "include",
  return res;
};

// 서버코드는 다음과 같습니다.
 logout(request: any, response: Response) {
    const { user } = request;
    response.clearCookie('connect.sid', {
      httpOnly: true,
      sameSite: 'none',
      secure: false,
      path: '/',
      domain: 'localhost',
    });
    return this.authRepository.invalidateRefreshToken(user.id);
  }

가능한 혼자 해결해보려했는데 잘안되네요.. 어느 부분을 수정해야할까요 ㅠ

 

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

저기 connect.sid에 느낌표있는데 손 안올려보셨나요?

성민최님의 프로필 이미지
성민최
질문자

확인해봤었습니다.

캡쳐 당시 samesite, domain 옵션 등을 만지다가 생겼었는데 현재는 없습니다.

image

 

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

로그아웃도 서버에서 Set-Cookie를 보내주는 것인데, 그걸 다시 Next 서버에서 브라우저로 보내주는 게 없는 것 같습니다. 로그인할 때처럼 백엔드로부터 Set-Cookie를 받아서 다시 브라우저에 전달해줘야하거든요. (res.status === 204 부분)

성민최님의 프로필 이미지
성민최
질문자

아 감사합니다. 브라우저가 응답한다는 뜻이 다시 set 해야한다거군요

로그아웃은 아래처럼 수정하여 해결하였습니다! 감사합니다.

events: {
    //  Todo: 토큰 없을 때 로그아웃 처리
    signOut: async (data) => {
      const token = cookies().get("connect.sid");
      if (!token) return;

      const res = await logout(token.value);
      if (res.ok && res.status === 204) {
        cookies().set("connect.sid", "", { expires: new Date(0) });
      }
    },
  },

아직 특정 페이지로 이동시 변경되는 문제는 해결못했습니다ㅠ 이 부분은 뭐가 문제인지 가닥도 안잡히네요.. 열심히 해결해보겠습니다. 감사합니다!

성민최님의 프로필 이미지
성민최
질문자

특정 페이지로 이동하였을 때 connect.sid의 값이 변하는 이유를 발견하였니다.

이전 질문에서 저는 현재 서버에서 발급 받은 accessToken을 connect.sid라는 이름으로 쿠키에 저장하여 사용한다고 하였는데요.

로그인 이후 진행되는 서버와의 통신의 response에서 set-cookie에 connect.sid=s%3...으로 돌아오고 있는 것을 확인했습니다.

 

expressSession이 원인인것 같습니다.

https://github.com/expressjs/session


해당 깃헙 docs의 한 부분을 보면

The name of the session ID cookie to set in the response (and read from in the request).

The default value is 'connect.sid'.

라고합니다.

제 예상에는 세션 관리를 위한 ID를 connect.sid로 expressSession이 보내고 있고 이 부분에서 이름 충돌이 일어난것으로 보입니다. accesstoken을 connect.sid가 아닌 다른 이름으로 변경하니 이 문제도 해결되었습니다.

아래는 질문인데요, 제로초님은 주로 로그인을 진행하실 때 그럼 서버 통신용 토큰으로 at를 쓰지 않으시는건가요? 아니면 세션에 저장하시는 건가요? 어떤 방식인지 궁금합니다.

전에 connect.sid를 백엔드 토큰으로 사용하신다고 하였는데 강의에서는 저와 같은 문제가 없이 지나갔던것 같아 궁금하여 여쭤봅니다!

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

s3%로 시작하는 건 익스프레스세션에서 암호화를 해주는 것입니다. 액세스 토큰을 쓰더라도 그냥 암호화된 채로 사용하면 됩니다.

0

성민최님의 프로필 이미지
성민최
질문자

네 path와 domain도 동일합니다.

브라우저로 응답하는 부분이 없어서요. 브라우저가 응답을 받아야 쿠키가 지워집니다. 그리고 쿠키가 지워질 때 네트워크 탭에서 Set-Cookie가 있어야 합니다.

왜 동작하지 않는지 이 부분 조금 더 자세히 설명해주실수 있으실까요 ! return해주고 네트워크탭을 확인해보는데 어떤 수정이 필요한지 잘 모르겠습니다 ㅠ

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

네트워크 탭에 로그아웃에 대한 응답에서 Set-Cookie가 없지 않나요? 즉, res.clearCookie한 res가 응답되고 있지 않는 것입니다.

성민최님의 프로필 이미지
성민최
질문자

네 이벤트에서 진행했을때 확인해보니 response헤더에 존재하지 않습니다.
로그아웃 버튼에서 해보면 또 s%3..으로 수정되구요ㅠ 코드가 어떻게 수정되어야 할까요?

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

지금 프론트에서 어떻게 요청하는지, 서버에서 어떻게 응답하는지 코드를 몰라서 답변드릴 수가 없네요

0

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

쿠키를 제거하려면 유효기간을 제외한 쿠키의 옵션이 정확히 똑같아야 합니다. httpOnly, sameSite, secure 같은 것들이요. domain이랑 path도 있네요. 얘네들도 확인해보셨나요?

그리고 일단 아래 코드는 동작하지 않을 겁니다. 브라우저로 응답하는 부분이 없어서요. 브라우저가 응답을 받아야 쿠키가 지워집니다. 그리고 쿠키가 지워질 때 네트워크 탭에서 Set-Cookie가 있어야 합니다.

성민최님의 프로필 이미지
성민최

작성한 질문수

질문하기