Don't think! Just do it!

종합 IT 기술 정체성 카오스 블로그! 이... 이곳은 어디지?

Next.js/nextauth

next-auth cognito login

방피터 2022. 6. 23. 13:54

중요!) 2022년 6월 23일 현재 next-auth 소셜 로그인에 약간 문제가 있어. 어휴 지친다. cognito로 소셜 로그인을 하면 cognito에서는 정상적으로 로그인되지만 next-auth의 하위 모듈인 openid-client에서 nonce mismatch error를 뱉어. 이 때문에 next-auth 세션이 로그인 상태로 안가고 error 페이지로 리디렉션!

oauth callback error

이 상태에서 한번 더 로그인을 시도하면 cognito는 이미 로그인이 되어 있는 상태여서 그런지 ㅎㅎㅎ 에러 없이 로그인 상태로 넘어가. 유저가 로그인 두번 눌러야 한다는 거지 -_-;; 암튼 그래서 현재 PR을 기다리고 있어. 👉 #4100  PR이 완료되기 전까지 사람들이 임시방편으로 patch-package를 사용해서 버티고는 있는데... 왠지... 기약이 없는 것처럼... 오픈 소스의 폐해인가??? 꽤 쓸만해 보였는데... production에 쓰면 낭패볼지도 모르겠어. 조심하자.


next-auth에서 cognito로 login을 해보려구.. firebase는 이상한 짓 많이 했어야 했는데 aws cognito는 어떨지!! 일단 amplify를 사용하고자 하는 입장에서 cognito가 별도의 작업없이 next-auth 위에서 자연스럽게 구동된다면 뭐....랄까.. firebase 버려! ㅋ

난 내가 만든 기본 템플릿 위에서부터 시작할거야👇👇 ㅎㅎ 등신같지만 자부심을 가져야 1등 등신! ㅋㅋ

https://github.com/peter-bang/nextjs-basic-configuration

 

GitHub - peter-bang/nextjs-basic-configuration: nextjs basic configurations

nextjs basic configurations. Contribute to peter-bang/nextjs-basic-configuration development by creating an account on GitHub.

github.com

이제 next-auth 설치해주자~

yarn add next-auth

 그리고 메뉴얼대로 pages/api/auth폴더에 [...nextauth].ts 파일을 만들고

import NextAuth from "next-auth";
import CognitoProvider from "next-auth/providers/cognito";

export default NextAuth({
  // Configure one or more authentication providers
  providers: [
    CognitoProvider({
      clientId: process.env.COGNITO_CLIENT_ID as string,
      clientSecret: process.env.COGNITO_CLIENT_SECRET as string,
      issuer: process.env.COGNITO_ISSUER,
      checks: "nonce",
    }),
  ],
});

여기 cognito provider 옵션에서 checks: "nonce"는 현재 지원을 하지 않아. 이것 때문에 이 글 처음에 언급했던 에러가 발생하는데 이걸 해결하려면 patch-package를 사용해서 next-auth 모듈을 수정해야 해. 임시방편으로 굉장히 찝찝하지. 언제 PR이 완료될지도 모르고 말이야. 일단은 신경끄고 넘어가도록 하자.

이제 저기 보이는 COGNITO_CLIENT_ID, COGNITO_CLIENT_SECRET 그리고 COGNITO_ISSUER 등 환경 변수를 넣어줘야 하겠지? 우선 nextjs 루트 폴더에 .env.local 빈 파일을 생성하자구.

//.env.local 파일
COGNITO_CLIENT_ID="xxxxxxxxx"
COGNITO_CLIENT_SECRET="xxxxxxxxxxx"
COGNITO_ISSUER ="https://cognito-idp.ap-northeast-2.amazonaws.com/ap-northeast-xxxxxxxx"
COGNITO_LOGOUT_URL="http://localhost:3000/"
COGNITO_DOMAIN="https://xxxxxxxxx-staging.auth.ap-northeast-2.amazoncognito.com"

NEXTAUTH_URL="http://localhost:3000/api/auth"
NEXTAUTH_SECRET="nextauth_secret"

위에서 언급한 세가지 말고도 COGNITO_DOMAIN과 CONGNITO LOGOUT_URL이 추가로 필요해. 이것들을 얻으러 가보자.

먼저 COGNITO_CLIENT_ID, COGNITO_SECRET은 aws cognito 대쉬 보드에서 얻을 수 있어. 이전 글에서 amplify 프로젝트 잘 따라한 사람은 사용자 풀이 이미 등록이 되어 있을 것이고 아니면 새로 사용자 풀을 만들어야 하겠지?

이전글 참고👉👉 2022.06.21 - [Next.js] - next.js + AWS amplify auth login

 

next.js + AWS amplify auth login

next.js에다가 aws amplify auth로 로그인을 구현해보자구. google부터 해볼거야. 나머지는 뭐 똑같겠지. 난 새로운 이메일 계정으로 aws 새로 가입해서 처음부터 시작했어. 새로 만들고 amplify 들어가면

engschool.tistory.com

난 이미 사용자 풀이 만들어져 있으니가 새로 만들지 않을 거야. 기존에 생성된 사용자 풀 선택 -> 앱 통합 탭 선택

사용자 풀 선택
앱 통합 탭

앱 통합 탭 하단에 앱 클라이언트 목록이 있는데 여기에서 클라이언트 ID랑 클라이언트 secret을 얻을 수 있어. 하지만 기존에 만들어진 애들은 client secret이 없어. 그래서 새로 만들어줘야 해. 앱 클라이언트 생성 버튼 클릭.

앱 클라이언트 목록

앱 클라이언트 생성 화면에서 앱 유형: 퍼블릭 클라이언트, 앱 클라이언트 이름: 아무거나 맘대로, 클라이언트 보안키: 클라이언트 보안키 생성 선택.

앱 클라이언트 생성화면

호스팅 ui 설정에서 인증 후 callback url하고 허용된 logout url을 아래와 같이 입력해. 인증 후 허용된 url은 cognito 로그인 인증이 완료되면 돌아갈 콜백 URL을 말하는 거고 허용된 logout url은 마찬가지로 cognito 로그아웃이 완료되면 돌아갈 URL을 말하는 건데 허용된 URL로만 돌아갈 수 있어. 개발하는 중에는 localhost:3000으로 해야겠지?

호스팅 UI 설정

그리고 자격 증명 공급자에서 Google을 추가해주고 그 하단에서 openID connect 범위만 조절해줬어. 전화, 이메일 등 있었는데 지웠어. 회원 가입을 email로 하면 전화번호랑 이메일을 입력

openid connect 범위

그리고 속성 읽기 및 쓰기 권한에서도 다 해제하고 조금만 남겼어. 유저한테 이만큼 정보 털어간다고 하면 누가 그 서비스 쓰겠어? 그치? 이제 다 했어. 앱 클라이언트 생성.

속성 권한

그럼 앱 클라이언트가 생성이 되는데 생성된 앱 클라이언트를 클릭해서 들어가보자. 그럼 거기서 앱 클라이언트 ID하고 secret을 얻을 수 있어. 이걸 복사해서 .env.local에 붙여넣기

앱 클라이언트 ID and secret

그 다음에 COGNITO_ISSUER를 찾자. issuer https://cognito-idp.{region}.amazonaws.com/{PoolId} 형태로 되어 있어. region은 cognito 생성한 지역, poolid는 user poolid. 난 서울리전으로 해서 ap-northeast-2 그리고 풀 id는 cognito 사용자 풀 대시보드에서 쉽게 찾을 수 있어. 이것도 .env.local에 입력해주자.

사용자 풀 ID

그리고 COGNITO_LOGOUT_URL="http://localhost:3000/" 은 그대로 놔두고(cognito login 이후에 돌아갈 url) 마지막으로 COGNITO_DOMAIN인데 이건 사용자 풀 앱 통합 탭 처음에 있어.👇👇 이것도 .env.local 에 붙여넣기!

Cognito 도메인

다 찾았네 ㅎㅎ 다 입력했으면 코드를 계속 짜보자구. _app.js에서 useSession hook 사용을 위해 sessionProvider로 감싸주자구.

...
import { SessionProvider } from "next-auth/react";
import { Session } from "next-auth";
...
...
interface MyAppProps extends AppProps {
  emotionCache?: EmotionCache;
  Component: NextPageWithLayout;
  session: Session;
}
...
...
export default function MyApp(props: MyAppProps) {
  const {
    Component,
    emotionCache = clientSideEmotionCache,
    pageProps,
    session,
  } = props;
  const getLayout = Component.getLayout ?? ((page) => page);
  return getLayout(
    <SessionProvider session={session}>
      <CacheProvider value={emotionCache}>
        <Head>
          <meta name="viewport" content="initial-scale=1, width=device-width" />
        </Head>
        <ThemeProvider theme={theme}>
          {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
          <CssBaseline />
          {/* <GlobalStyles /> */}
          <Component {...pageProps} />
        </ThemeProvider>
      </CacheProvider>
    </SessionProvider>
  );
}

그리고 components 폴더에 login-btn.tsx 파일을 생성. 👇 LoginButton이라는 컴포넌트를 간단하게 만들었어.

import { useSession, signIn, signOut } from "next-auth/react";

const LoginButton: React.FC<any> = () => {
  const { data: session } = useSession();
  if (session) {
    return (
      <>
        Signed in as {session.user.email} <br />
        <button
          onClick={() =>
            signOut({ callbackUrl: "http://localhost:3000/api/auth/logout" })
          }
        >
          Sign out
        </button>
      </>
    );
  }
  return (
    <>
      Not signed in <br />
      <button
        onClick={() =>
          signIn("cognito", { callbackUrl: `${window.location.origin}` })
        }
      >
        Sign in
      </button>
    </>
  );
};

export default LoginButton;

여기서 잠깐! 설명하자면 헷갈리면 안돼! next-auth 콜백이랑ㅋㅋ cognito 콜백이랑 ㅋ 먼저 signOut 버튼의 콜백 url은 next-auth의 signOut이 잘 수행되면 돌아가는 콜백이야. next-auth는 signOut을 하면 session에서만 logout하고 끝나버려 cognito는 로그인 된 상태 그대로지. 이렇게 되면 유저가 다시 로그인을 할 때 묻지도 따지지도 않고 바로 로그인이 되어버리지! 이런 문제 때문에 next-auth의 signOut이 수행된 후에 cognito에서도 로그아웃을 해줘야 해. 저 콜백은 cognito 로그 아웃을 위한 api 인거야.

next auth + cognito에서 signout 순서

그럼 저 localhost:3000/api/auth/logout API endpoint를 만들어 주자. pages/api/auth 폴더에 logout.ts 파일을 만들어 주고 아래와 같이 코드 입력.👇 설명할 것도 없이 간단해. 이 api endpoint는 cognito 로그 아웃만 실행해.

async function handleLogout(req, res) {
  const result = res.redirect(
    `${process.env.COGNITO_DOMAIN}/logout?client_id=${process.env.COGNITO_CLIENT_ID}&logout_uri=${process.env.COGNITO_LOGOUT_URL}`
  );
  console.log("logout");
}

export default async function logout(req, res) {
  try {
    await handleLogout(req, res);
  } catch (error) {
    console.error(error);
    res.status(error.status || 400).end(error.message);
  }
}

다 했다 이제 새로만든 LogoutButton 컴포넌트만 index.ts에 삽입해서 테스트해보자규~👇

...
import LoginButton from "../components/login-btn";
...
    <main>
      <h1>안녕 여러분!!</h1>
      <LoginButton />
    </main>
...

아마 아래와 같은 현상이 나올겨. 두 번 로그인 버튼을 눌러야만 하지.

두 번 로그인 버튼 눌러야 함.

이건 이 글 초반에 말했다시피 cognito에서 정상적으로 로그인 했음에도 next-auth 하위 모듈인 openid-client에서 nonce가 발생해서 oauthcallback error 페이지로 리디렉션 했기 때문이야. 난 위에서 언급했던 것처럼 patch-package로 일부를 수정해서 해결했지만 정식 버전이 아니라는 거~ 그럼에도 불구하고 하고 싶다면 👇

 

우선 patch-package를 설치해줘야 해. patch-package node 모듈 커스터마이징한 후에 그거 유지시켜주는 애야. yarn install 하거나 모듈 upgrade하면 커스터마이징 한 코드들 다 날라가잖아. 안 날라가게 유지시켜주는 애야. 또 yarn을 사용하려면 postinstall-postinstall 모듈도 함께 설치해줘야 하는데 yarn remove에서도 postinstall이 수행되도록 돕는다고하네. 👇

https://www.npmjs.com/package/patch-package/v/6.4.7#why-use-postinstall-postinstall-with-yarn

 

patch-package

Fix broken node modules with no fuss. Latest version: 6.4.7, last published: a year ago. Start using patch-package in your project by running `npm i patch-package`. There are 461 other projects in the npm registry using patch-package.

www.npmjs.com

자 설치 해주자 👇👇

yarn add patch-package postinstall-postinstall

그리고 package.json에서 scripts에 postinstall: "patch-package" 추가.

"scripts": {
  "postinstall": "patch-package"
 }

원래는 next-auth 수정하고 npx patch-package next-auth 실행해서 patches 폴더랑 파일 만들어야 하는데 너무 힘드니깐 패치 파일을 그냥 공유할게. patches 폴더 만들어서 넣자. 아! next-auth 버전 4.5.0 임 다른거 쓰면 안됌~ 다른 버전 쓸람 새로 patch파일 만들어야 함~

patch-package 실행 후 생성된 폴더와 파일.

 

아래는 파일.👇

next-auth+4.5.0.patch
0.01MB

 

이렇게 하고 나서 로그인을 하면 정상적으로 동작하는 걸 확인할 수 있어. 휴~ 새벽까지 하다보니 자괴감이... 이런 불안정한 패키지를 써야하나... 음.... next-auth는 과연 실제로 프로젝트에 적용할 수 있나? 하는 의문이 드네. ㅎㅎ

 

다들 같이 고민해보쟈 ㅋㅋㅋㅋㅋㅋㅋ


아래는 github link!👇👇

https://github.com/peter-bang/nextauth-cognito

 

GitHub - peter-bang/nextauth-cognito: nextauth + cognito example

nextauth + cognito example. Contribute to peter-bang/nextauth-cognito development by creating an account on GitHub.

github.com

 

반응형

'Next.js > nextauth' 카테고리의 다른 글

nextauth 카카오 로그인 #1  (4) 2022.07.13
next.js + AWS amplify auth login  (0) 2022.06.21
nextauth + firebase authentication #2  (2) 2022.06.03
nextauth + firebase authentication #1  (1) 2022.06.02
nextauth + firebase authentication  (0) 2022.06.02