Error: The 'mode' field on 'RequestInitializerDict' is not implemented (in Cloudflare Workers)
개요
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 로 인증로직을 구현한 것이다.