-
[Next.js] Next-auth + Amplify 노트Dev 2022. 3. 21. 23:54
[...nextauth].ts
import NextAuth, { Account, Profile, User } from 'next-auth'; import Providers from 'next-auth/providers'; import { NextApiRequest, NextApiResponse } from 'next-auth/internals/utils'; import api from '@lib/api'; import dayjs from 'dayjs'; import Cookies from 'cookies'; // 사용할 oauth provider const providers = [ Providers.Naver({ clientId: process.env.NEXT_PUBLIC_NAVER_CLIENT_ID, clientSecret: process.env.NEXT_PUBLIC_NAVER_CLIENT_SECRET, }), Providers.Kakao({ clientId: process.env.NEXT_PUBLIC_KAKAO_REST_CLIENT_ID, clientSecret: process.env.NEXT_PUBLIC_KAKAO_CLIENT_SECRET, }), Providers.Facebook({ clientId: process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_ID, clientSecret: process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_SECRET, }), Providers.Google({ clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID, clientSecret: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_SECRET, }), Providers.Instagram({ clientId: process.env.NEXT_PUBLIC_INSTAGRAM_CLIENT_ID, clientSecret: process.env.NEXT_PUBLIC_NEXT_PUBLIC_INSTAGRAM_CLIENT_SECRET, }), ]; // 인증 옵션 설정 const options = (req: any, res: NextApiResponse<any>) => ({ providers, //위에서 설정한 provider callbacks: { // 로그인 플로우 커스텀 // 소셜연동, 비회원인 경우 회원가입, 로그인 기능 필요 async signIn(user: User, account: Account, profile: Profile) { const cookies = new Cookies(req, res); console.log('user', user); console.log('req.options.callbackUrl', req.options.callbackUrl); // 해당 요청이 로그인이 아닌 소셜연동인지 구분하기 위한 변수, signIn 함수 호출 시 두번째 인자로 넘긴 callbackUrl에 user-edit이 포함되어 있으면 연동 const isSnsConnect = req.options.callbackUrl.includes('user-edit'); // 해당 요청이 소셜연동이고, 새로고침 후 모달을 다시 띄워주기위한 조건용 변수 const isInfluencerReq = req.options.callbackUrl.includes('influ'); const body = { sns_type: account.provider === 'instagram' ? 'insta' : account.provider, auth_key: account.id, sns_account_id: user.email ?? user.name, }; // 소셜 연동 const handleSnsLinkRegist = async () => { const response = await api.patch<any>('/api/mall/users/auth/sns', body, { headers: { Authorization: `Bearer ${cookies.get('token')}` }, }); console.log('sns 연동', response); if (response.ok) { return isInfluencerReq ? '/mypage?page=user-edit&type=influ' : '/mypage?page=user-edit'; } return `/mypage?page=user-edit&error=${response.data.code}`; }; // 로그인 const handleLogin = async () => { const response = await api.post<any>('/api/auth/sns-login', body); console.log('소셜 로그인', response); if (response.ok && response.data) { cookies.set('token', response.data.token, { httpOnly: false, expires: dayjs().add(1, 'day').toDate(), }); // 로그인 성공 시 메인으로 return '/'; } // 로그인 실패 => 연동되지 않은 소셜계정인 경우 회원가입 페이지로 이동 return `/signup-policy?sns=${account.provider}&uid=${account.id}`; }; // isSnsConnect 변수를 사용하여 함수 분기 return isSnsConnect ? handleSnsLinkRegist() : handleLogin(); }, }, pages: { signIn: '/login', }, // callbackUrl이 무시되는 에러를 해결하기 위해 redirect 설정 redirect: async (url: any, baseUrl: any) => url.startsWith(baseUrl) ? Promise.resolve(url) : Promise.resolve(baseUrl), }); const Auth = (req: NextApiRequest, res: NextApiResponse<any>) => NextAuth(req, res, options(req, res)); export default Auth;
Error
# Amplify로 배포 시 callbackUrl 값이 무시되는 에러
vercel로 배포 시에는 해당 에러가 발생하지 않는다. vercel로 배포하는 경우 환경변수로 NEXTAUTH_URL도 설정해줄 필요 없으며, 자동으로 적용되게끔 되어있다고 문서에 나와있다.
하지만 나는 Amplify로 배포를 진행해야하고, 따라서 해당 에러를 고쳐야한다.
설정한 callbackUrl이 무시되고, 리다이렉트 되지 않는 문제를 검색하다보니 나와 같은 에러가 발생하는 사람들이 좀 있었다.
https://github.com/nextauthjs/next-auth/issues/591
위의 깃허브 토론을 보니 내 코드에는 없는 redirect 부분의 코드가 없는 것을 발견했다. 아래와 같이 추가해주어 기존에 signIn 함수의 두번째 인자로 넘겨주던 callbackUrl이 무시되는 에러가 해결되었다.
redirect: async (url, baseUrl) => { return url.startsWith(baseUrl) ? Promise.resolve(url) : Promise.resolve(baseUrl) }
# Facebook provider scope 커스텀
기본적으로 제공되는 프로필 정보 외에도 프로필 링크가 추가적으로 필요하여, scope 커스텀 작업 필요
next-auth 문서에 나오는데로 scope를 수정해 봤지만, 제대로 작동하지 않음
아래의 provider 소스코드를 보고, 페이스북에 유저 정보를 요청하는 부분으로 추정되는 url을 커스텀하여 요청하는 정보를 변경할 수 있는지 시도해보기로 했다.
그래서 소스코드와 비슷하게 provider에 userinfo 객체를 만들어주려하니 타입에러가 난다.
next-auth에 검색해보니 버전이 올라가면서 userinfo (replaces profileUrl) 과 같이 변경되었기 때문에 발생한 에러였고, 타입을 보니 객체 타입도 아니고 스트링 타입이었다.
따라서 요청할 url을 스트링 타입으로 넣어주는 방향으로 커스텀하였다.
+ 위 상황에서 되었던 것은 페이스북 api 테스트하는 사이트에서 권한을 허용한 후 토큰을 다시 생성했기 때문
따라서 실제로 사용할때도 필요한 권한을 추가 요청하여 사용하기 위해서는 scope를 추가해주어야했다.여기서 scope는 api 요청하는 파라미터와 동일하게 입력하는 것이 아닌 페이스북에서 지정한 로그인 권한 이름으로 설정해주어야한다.
// 소스코드 userinfo: { url: "https://graph.facebook.com/me", // https://developers.facebook.com/docs/graph-api/reference/user/#fields params: { fields: "id,name,email,picture" }, async request({ tokens, client, provider }) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return await client.userinfo(tokens.access_token!, { // @ts-expect-error params: provider.userinfo?.params, }) }, }, // 요청 url 커스텀 Providers.Facebook({ clientId: process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_ID, clientSecret: process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_SECRET, profileUrl: 'https://graph.facebook.com/me?fields=id,name,email,link,picture{url}', scope: 'user_link email public_profile', }),
https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/facebook.ts
'Dev' 카테고리의 다른 글
[Next.js] Nice API 본인인증 구현하기 (10) 2022.08.17 웹사이트 아이콘을 훔쳐보자 (0) 2022.04.09 http와 https (0) 2022.03.18 [Next.js] Amplify 찍먹 배포 해보기 (0) 2022.03.18 React Uncaught ReferenceError: process is not defined (0) 2022.03.08