똑같은 삽질은 2번 하지 말자

Error: The 'mode' field on 'RequestInitializerDict' is not implemented (in Cloudflare Workers) 본문

카테고리 없음

Error: The 'mode' field on 'RequestInitializerDict' is not implemented (in Cloudflare Workers)

곽빵 2024. 3. 31. 22:28

개요

Cloudflare Pages & Workers를 이용해 Next.js로 만든 프로젝트를 배포하는 중 만났던 에러 Error: The 'mode' field on 'RequestInitializerDict' is not implemented의 트러블 슈팅 기록

문제

이전에 내가 작성한 NextJS + authjs(next-auth v5)를 edge runtime(Cloudflare Worker)로 인증시스템 구성하기

 

React vol.21 NextJS + App Router + edge runtime에서 next-auth와 cognito로 인증 시스템 구현하기

개요 Cloudflare Pages + Workers를 이용해 edge runtime의 환경에서 next-auth + cognito를 이용해 인증 시스템을 구축했던 기록을 남기고자 한다. next-auth v4는 edge runtime환경에서 사용할 수 없었다. 도입을 하려

heewon26.tistory.com

위의 글을 바탕으로 실제로 Cloudflare Pages + Worker에 배포를 했지만 로그인을 하려고하면 Error: The 'mode' field on 'RequestInitializerDict' is not implemented 라는 에러가 발생했다.

 

원인

우선 Cloudflare Worker는 Edge runtime이라는 환경에서 동작하는데 이 환경이 어떤 환경인지 알아야한다.

 

Edge Runtime

서버리스 환경에서 최적화된 작업을 수행할 수 있도록 설계된 런타임 환경이다. 이 런타임은 특히 사용자에게 더 가까운 위치에서 코드를 실행하도록 설계되어 있어, 응답 시간을 단축하고 성능을 향상시키는 데 큰 도움이 된다. Edge Runtime은 전통적인 Node.js 환경에서 사용할 수 있는 모든 기능을 지원하지는 않지만, 대부분의 웹 애플리케이션에서 필요한 핵심 기능을 제공해준다.

 

즉, Edge Runtime은 cdn과 같이 사용자와 가까운 거리에 위치할 수 있다. 하지만 특성상 무거운 기능들을 다 넣을 수는 없어서 NodeJS의 일부 기능들만 이 런타임 환경에서 사용할 수 있다.

 

이제 에러메세지(Error: The 'mode' field on 'RequestInitializerDict' is not implemented)를 살펴보면 Request를 날리는데 mode라는 필드는 없다는 말이다. 여기서 말하는 mode는 우리가 클라이언트에서 자주 애용하는 fetch API의 하나의 옵션이다. 이 모드는 cors 정책을 정의할 때 사용된다.

https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#supplying_request_options

 

Using the Fetch API - Web APIs | MDN

The Fetch API provides a JavaScript interface for accessing and manipulating parts of the protocol, such as requests and responses. It also provides a global fetch() method that provides an easy, logical way to fetch resources asynchronously across the net

developer.mozilla.org

근데 cors는 브라우저간에 리소스를 교환할 때 필요한거지 edge runtime은 브라우저의 환경이 아니므로 당연히 mode라는 필드는 필요없으며 이는 edge runtime에서 지원되지 않는 요소라 에러가 발생한 것이다.

(여기서 한가지 의문이 든게 우리가 NextJS에서 fetch API를 사용할 때 종종 cors 설정을 위해 mode를 사용하는 경우가 있는데 이때 SSR에서 움직이는 fetch API도 있을건데 왜 에러가 발생안했는지 궁금했는데, 이는 NextJS쪽에서 적절하게 Polyfill같은걸 해주기 때문에 우리가 굳이 신경쓸 필요가 없었다.)

 

해결

어찌보면 간단할 수 있었는데 나는 cognito와의 연계를 위해 amazon-cognito-identity-js 라는 라이브러리를 사용했고 이는 클라이언트쪽에서 작동하게끔 만든 라이브러리라서 cors의 설정이 기본적으로 들어가 있었다. 이 라이브러리 대신에 서버쪽에서도 문제없이 작동할 수 있는 @aws-sdk/client-cognito-identity-provider를 사용해 인증처리를 함으로써 문제를 해결할 수 있었다.

 

코드로 보면 이하의 코드를

변경전

import {
  AuthenticationDetails,
  CognitoIdToken,
  CognitoUser,
  CognitoUserPool,
  CognitoUserSession,
} from 'amazon-cognito-identity-js';

  providers: [
    CredentialsProvider({
      credentials: {
        email: { label: 'Email', type: 'text' },
        password: { label: 'Password', type: 'password' },
      },
      authorize: async credentials => {
        const email = (credentials?.email as string) ?? '';
        const password = (credentials?.password as string) ?? '';

        const userPool = new CognitoUserPool({
          UserPoolId: process.env.NEXT_PUBLIC_COGNITO_USER_POOL_ID!,
          ClientId: process.env.NEXT_PUBLIC_COGNITO_CLIENT_ID!,
        });

        const user = new CognitoUser({
          Username: email,
          Pool: userPool,
        });

        const authenticationDetails = new AuthenticationDetails({
          Username: email,
          Password: password,
        });

        const session: CognitoUserSession = await new Promise((resolve, reject) => {
          user.authenticateUser(authenticationDetails, {
            onSuccess: session => resolve(session),
            onFailure: err => reject(err),
            newPasswordRequired: function (userAttributes, requiredAttributes) {
              user.completeNewPasswordChallenge(password, {}, this);
            },
          });
        });

        if (!session) {
          return null;
        }

        return {
          idToken: session.getIdToken(),
        };
      },
    }),
  ],

 

변경후

import {
  AdminInitiateAuthCommand,
  AdminRespondToAuthChallengeCommand,
  AuthFlowType,
  ChallengeNameType,
  CognitoIdentityProvider,
} from '@aws-sdk/client-cognito-identity-provider';

  providers: [
    CredentialsProvider({
      credentials: {
        email: { label: 'Email', type: 'text' },
        password: { label: 'Password', type: 'password' },
      },
      authorize: async credentials => {
        const email = (credentials?.email as string) ?? '';
        const password = (credentials?.password as string) ?? '';

        try {
          const initiateAuthCommand = new AdminInitiateAuthCommand({
            AuthFlow: AuthFlowType.ADMIN_USER_PASSWORD_AUTH,
            ClientId: process.env.NEXT_PUBLIC_COGNITO_CLIENT_ID!,
            UserPoolId: process.env.NEXT_PUBLIC_COGNITO_USER_POOL_ID!,
            AuthParameters: {
              USERNAME: email,
              PASSWORD: password,
            },
          });

          let initiateAuthResponse = await cognitoClient.send(initiateAuthCommand);

          if (initiateAuthResponse.ChallengeName === ChallengeNameType.NEW_PASSWORD_REQUIRED) {
            const respondToAuthChallengeCommand = new AdminRespondToAuthChallengeCommand({
              ChallengeName: ChallengeNameType.NEW_PASSWORD_REQUIRED,
              ClientId: process.env.NEXT_PUBLIC_COGNITO_CLIENT_ID!,
              UserPoolId: process.env.NEXT_PUBLIC_COGNITO_USER_POOL_ID!,
              ChallengeResponses: {
                USERNAME: email,
                NEW_PASSWORD: password,
              },
              Session: initiateAuthResponse.Session,
            });

            initiateAuthResponse = await cognitoClient.send(respondToAuthChallengeCommand);
          }

          return {
            idToken: initiateAuthResponse.AuthenticationResult?.IdToken,
          };
        } catch (error) {
          console.error('Error authenticating user:', error);
          return null;
        }
      },
    }),
  ],

 

클라이언트단에서 사용되는 amazon-cognito-identity-js을 대신해서 서버단에서 사용되는@aws-sdk/client-cognito-identity-provider 로 인증로직을 구현한 것이다.

Comments