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

성민최님의 프로필 이미지

작성한 질문수

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

next-auth로 로그인하기

서버 로그인 방식과 결합

24.06.15 17:25 작성

·

304

0

안녕하세요!

개인 프로젝트에서 백엔드 먼저 개발 후 프론트를 Next로 개발하면서 강의를 통해 next-auth를 접했는데요.

 

저는프론트에서 로그인을 하면 해당 로그인 request가 서버로 전달되고, 서버에서 jwt access, refresh token을 발급해프론트에 전달하고 저장하는 기존의 서버 로그인 방식으로 설계를 하였습니다.

 

또한 서버의 protected request에 대해 위에서 발급한 access token을 활용하여 유효한지 검증 후 진행하도록 하고자 하는데요.

 

next-auth부분을 보며 현재의 공식 문서와도 조금 다르고 제가 원하는 방식과 통합하여 구현이 가능한지 잘 이해가 안가더라구요ㅠ

혹시 이러한 방식으로도 서버에서 얻은 jwt토큰을 next-auth에 삽입이 가능할까요? 대략적인 흐름은 어떻게 진행될까요?

답변 2

0

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

2024. 06. 15. 17:38

refresh token을 프론트에 전달하는 건 아니시죠? 하나 알아두셔야 할 점은 next-auth는 프론트 서버의 로그인 방식이라 백엔드 서버와는 별개입니다. 프론트 서버에서는 next-auth로 로그인하고, 백엔드 서버에서는 access token으로 인증하는 식인 것입니다. 저는 그래서 프론트 서버에서 먼저 로그인하고, 프론트 서버가 백엔드 서버의 로그인을 진행하여 access token을 발급받는 식으로 합니다.

access token만 프론트에 전달했다고 생각하면 그 access token을 쿠키나 로컬 스토리지에 넣어두셨을테니 서버 api 호출 시에도 access token을 서버로 전달해서 쓰시면 됩니다.

 

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

2024. 06. 15. 18:07

refreshToken는 백엔드 로직에서 디비와 쿠키에 저장하고 있습니다.

 

프론트 서버에서는 next-auth로 로그인하고, 백엔드 서버에서는 access token으로 인증하는 식인 것입니다. 저는 그래서 프론트 서버에서 먼저 로그인하고, 프론트 서버가 백엔드 서버의 로그인을 진행하여 access token을 발급받는 식으로 합니다.

이 부분이 조금 이해가 어려워 추가 질문 드립니다.

  1. 말씀하시는 프론트 서버 로그인과 백엔드 서버 로그인이 어떤 부분이 다른 것일까요? 느낌상 프론트 서버에서의 protected page를 접근하는 것과 백엔드 서버에서 api활용을 위한 token에 대한 것이라고 생각이 듭니다. 이해한것이 맞을까요?

 

  1. 답변해주신 내용과 강의 내용을 토대로 이해해본 바로는 auth.ts (강의 섹션4: 서버 쿠키 공유하기)의 connect.sid 를 세팅하는 부분이 답변에서 말씀해주시는 부분이라고 생각드는데 맞을까요?

 

기존에 저는 서버에서 accessToken을 받으면 해당 토큰은 decode하여 id, email등은 zustand로 관리하여 user정보를 관리하는 방식을 사용하였습니다. 이것으로 로그인 여부를 판단해 프론트의 protected page도 접근하였구요.

 

그렇다면 위 과정을 적용하고자 할 경우, auth.ts 파일에서 authResponse로 서버에서 받은 access token을 decode해서 user에 삽입하고(protected page 유저 검증),

이 토큰을 connet.sid로 셋팅(서버 전송 access token)하면 되는 것이라고 생각듭니다! 혹시 이 과정이 맞을까요?

 

현재 실습이 어려운 상황이라 질문을 먼저 남깁니다 ㅠ 감사합니다.

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

2024. 06. 15. 18:15

  1. 네 맞습니다. 프론트 서버는 페이지 접근, 로그인/로그아웃을 담당하고, 백엔드 서버는 api 호출을 담당합니다.

  2. 네 connect.sid가 백엔드 토큰입니다. 강의에서는 쿠키를 활용한 것이고요.

  3. 네 일단 과정은 맞는데요. 다만 프론트에서도 auth-js.session-token같은 쿠키를 따로 활용해서 인증 관리를 하고있어서 프론트 로그인은 별개입니다.

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

2024. 06. 17. 20:59

안녕하세요. 말씀해주신 부분 참고하여 많이 진행하였습니다. 감사합니다.
추가적으로 어떤 것이 더 좋은 방법 일까 궁금한 부분 있어 질문 남깁니다 !

백엔드에서 발급 받은 백엔드 호출을 위한 rt, at 입니다.

  1. rt, at 둘 다 쿠키에 저장하기 (혹은 at: zustand)

  2. rt 쿠키에 저장, at 세션에 저장하기

저는 2번에 대해서 보안 문제 때문에 1번 방법으로 진행하며, 유저 정보는 세션에 저장하고 진행하고자 하였습니다.

그런데 https://next-auth.js.org/configuration/callbacks#jwt-callback 글을 참고하여 at를 세션에 저장해서 사용하기도 하는 것 같은데 어느 방법이 더 좋은 방법일지 궁금합니다.

 

하단 저장 코드입니다.


export const { handlers, signIn, signOut, auth } = NextAuth({
  pages: {
...
  },
  providers: [
    Credentials({
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" },
      },
      authorize: async (credentials): Promise<any> => {
        const res = await login(
          credentials.email as string,
          credentials.password as string
        );
        const result = await res.json();

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

        let setCookie = res.headers.get("set-cookie");

        if (setCookie) {
          const parsed = cookie.parse(setCookie);
          cookies().set("token", parsed["token"]); // set refresh token
        }

        const { accessToken } = result;

        if (res.ok && accessToken) {
          cookies().set("connect.sid", accessToken); // 1번 : 쿠키에 저장
          const user = jwtDecode(accessToken);

          return {
            ...user,
            accessToken,
          };
        }

        return null;
      },
    }),
  ],
  callbacks: {
    async jwt({ token, user }: { token: any; user: any }) {
      if (user) {
        token.id = user.id;
        token.email = user.email;
        token.nickname = user.nickname;
        token.image = user.image;
        token.role = user.role;
        token.accessToken = user.accessToken;
      }

      return token;
    },
    async session({ session, token }: { session: any; token: any }) {
      if (token) {
        session.accessToken = token.accessToken;
        session.user.id = token.id;
        session.user.email = token.email;
        session.user.nickname = token.nickname;
        session.user.role = token.role;
        session.user.image = token.image;
      }
      console.log("session", session); // 2번 : 세션에 저장
      return session;
    },
  },
});

 

 

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

2024. 06. 18. 13:13

2번에 대해 보안문제가 있다하셨는데 무슨 문제가 있나요? 그리고 refresh token을 쿠키로 전달하시는 거면 replay attack이나 csrf 공격에 대한 대비는 하셨나요? 애초에 저는 프론트에 왜 리프레시 토큰을 내려보내야 하는지 잘 모르겠습니다.

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

2024. 06. 18. 14:55

우선, 저는 rt의 경우가 at 갱신 용도로 사용하고 있습니다. at만료시 갱신 용도로만 활용되기에 rt가 at보다 유출에 대한 위험성이 적다고 생각했습니다. csrf 등 공격에 대해서도 쿠키의 secure, httpOnly등 옵션으로 저장할 경우 비교적 타협이 가능하지 않을까 하는 의견이였습니다.

 

반면 at의 경우 직접적으로 백엔드 서버 reqeust에 활용이 되니까 더 보안을 신경써야하지 않았나 싶었고 브라우저 쿠키보다 세션이 더 보안상 취약하지 않을까?라는 생각이였습니다.

(이 부분은 방금 더 확인해보니 세션이 쿠키와는 달리 서버에 저장이 되기에 더 보안상 더 강하다고 하는것을 확인하였습니다.)

 

프론트에 왜 리프레시 토큰을 내려보내야 하는지 잘 모르겠다고 하셨는데 혹시 백엔드 서버 request를 위한 리프레시 토큰과 엑세스 토큰을 어떻게 유지하는 것이 더 좋은 방법이라고 생각하시는지 알 수 있을까요?

 

현업에서 어떤 방법으로 사용하는지 잘 몰라 어떤 것이 더 좋을지 여쭤보고 싶었습니다.

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

2024. 06. 18. 15:20

일단 리프레시 토큰이 훨씬 더 중요합니다. 리프레시 토큰이 털리는 순간 액세스 토큰을 무제한으로 발급할 수 있기에 리프레시 토큰이 털린다 = 모든게 다 털린다와 동일한 의미입니다. 그래서 프론트로 아예 내려보내지 않고요. 그냥 DB에 넣어둔 뒤에 액세스 토큰 재발급 요청을 서버로 보내시면 됩니다. 액세스 토큰은 수명이 짧아서 로컬스토리지/쿠키/메모리 등에 필요에 따라 넣어두시면 됩니다.

추가로 csrf 공격은 secure/http 쿠키로 해결되지 않습니다.

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

2024. 06. 18. 15:28

아 그런 이유도 있을 것이라고 생각 못해봤네요.. 재발급 로직을 수정해야겠습니다 ㅠ 감사합니다 !

좋은 강의 덕에 더 공부하고 알아갑니다 ! 감사합니다 !

0

인프런 AI 인턴님의 프로필 이미지

2024. 06. 15. 17:31

안녕하세요, 인프런 AI 인턴입니다. 성민최님의 질문에 대해 안내해 드리겠습니다.

질문하신 내용은 Next.js 프로젝트에서 next-auth를 사용하여 서버로부터 반환된 JWT 토큰(access token, refresh token)을 활용하여 인증 처리를 하는 방법에 관한 것으로 이해됩니다. 귀하의 경우처럼 백엔드에서 JWT 토큰을 발급받고 이를 이용해 프론트엔드에서 인증을 처리하려고 할 때, next-auth를 유연하게 사용하는 방법을 찾고 계신 것 같습니다.

next-auth는 다양한 인증 방식을 지원하며, 특히 소셜 로그인이나 이메일, 비밀번호를 이용한 로그인뿐만 아니라, 질문하신 것처럼 백엔드 시스템에서 발급받은 JWT 토큰을 활용한 인증 처리도 가능합니다. 이때 Credentials 제공자(provider)를 사용하여 커스텀 인증 로직을 구현할 수 있습니다.

따라서, 서버에서 발급받은 JWT 토큰을 next-auth에 통합하는 대략적인 개념은 다음과 같습니다:

  1. Credentials Provider 설정: next-auth에서 Credentials provider를 설정하여, 사용자가 로그인할 때 서버로부터 JWT 토큰을 받아오는 커스텀 로그인 로직을 구현합니다.

  2. 커스텀 로그인 페이지 생성: 필요한 경우 커스텀 로그인 페이지를 생성하여, 로그인 폼을 통해 사용자 정보를 입력받습니다.

  3. 서버와 통신하여 JWT 토큰 받아오기: 사용자가 로그인 버튼을 클릭하면, 입력한 정보를 서버로 전송하고, 서버는 유효한 사용자임을 검증한 후 JWT 토큰을 응답으로 반환합니다.

  4. JWT 토큰을 next-auth 세션에 저장: 서버로부터 받은 JWT 토큰을 next-auth 세션에 저장하여, 향후 사용자가 인증된 상태임을 시스템이 인지할 수 있도록 합니다.

  5. protected 페이지/기능 접근시 토큰 검증: 인증이 필요한 페이지나 기능에 접근할 때, next-auth 세션 내에 저장된 JWT 토큰을 사용하여 사용자의 인증 상태를 검증합니다.

이러한 과정을 통해, 백엔드 서버에서 JWT 토큰을 발급받아 next-auth와 통합하는 인증 시스템을 구현할 수 있습니다. 이 때, 세부적인 구현 방법이나 예제 코드는 next-auth의 공식 문서, GitHub 리포지토리의 Discussions 섹션, 또는 관련 커뮤니티에서 찾아볼 수 있습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.