시작하며...
이번 코드잇 인턴 프로젝트인 "Codeit Resources"가 11월 1일에 무사히 완료되었고 긍정적인 평가를 받았다!
그러나... 실제 직원분들이 사용하고 피드백을 받기 위해서는 구글 캘린더가 연동이 필수적이라고 요구사항을 받았다.
사실 여기서 프로젝트를 마무리할 수도 있었지만, 팀원들과의 회의 끝에 구글 캘린더 연동 작업을 추가로 진행하기로 결정을 내렸다. 다행히도 코드잇 측에서 2주간 리소스(AWS)를 제공할 수 있다는 답변을 받아 계속 프로젝트를 이어나가게 되었다.
구현 초기 단계
백엔드 서버가 필요해!
먼저 구글 캘린더 API와 연동을 하기 위해서는 백엔드 서버가 필요했다.
현재 프로젝트의 AWS Amplify Gen2를 사용하고 있어 간편하게 풀스택 개발을 할 수 있었다.
그런데.. 추가적인 백엔드 로직을 연결하기 위해서는 AWS Lambda를 사용할 수 있지만 잘 작동되지 않은 이슈가 있었고, 레퍼런스를 찾아봤지만 관련된 자료가 부족해서 결국 Lambda 기능을 사용하지 못했다.
(그래서 데이터 정렬 로직도 서버에서 처리 못 하고 프론트엔드에서 처리함😅)
팀원 모두 프론트엔드 개발자였기 때문에 2주 안에 서버를 구축하는 작업은 현실적으로 어려울 수 있다는 판단이 들었고, 이를 해결할 수 있는 방안을 구안하다가 생각한 것이 바로 Next.js의 API Routes였다.
그래서 구글 캘린더 연동 작업을 진행하기 위해서 기존 React 프로젝트를 Next.js로 마이그레이션 하기로 했다.
특히 Next.js의 최신 버전인 App Router를 사용하지 않은 이유는, 빠른 시간 내에 마이그레이션 해야 되기 때문에 서버 컴포넌트, 클라이언트 컴포넌트를 구분하기에 번거롭고, 아직 안정성이 부족하다는 점을 고려하여 Page Router를 선택하여 마이그레이션을 진행하였다.
또한, 데이터 정렬 로직도 API Routes를 사용하여 처리하기로 했다!
내가 맡은 역할은??
나는 기존에 User와 Team 백엔드를 담당했고 멤버 관리, 팀 관리 페이지와 유저 인증 기능을 구현했었다.
(유저 인증 로직 구현한 내용..)
그래서 자연스럽게 구글 캘린더 연동을 하기 위한 구글 로그인 파트을 담당하게 되었다!
구글 로그인은 예전부터 구현해보고 싶었던 기능이었기 때문에, 이번 기회에 직접 구현해 볼 수 있는 기회가 생겨서 좋았다..😁
Cognito를 활용한 구글 로그인
이미 Cognito를 사용하여 인증을 처리하고 있었기 때문에, 자연스럽게 구글 로그인 로직도 Cognito를 사용해서 구현하기로 했다!
구글 로그인 구현하기
처음에는 공식 문서를 참고했으나, 다른 소셜 로그인들까지 함께 다루고 있어 내용이 좀 빈약했다.
그래서 유튜브 영상을 찾아보았고, 마침 딱 내 스택에 맞는 AWS Amplify Gen2, Cognito, Next.js를 활용한 구글 로그인 구현 영상을 발견하여 해당 영상을 참고하여 구글 로그인을 구현하였다! (완전 뿌듯)
소셜 로그인 구현에 대한 두려움이 있었지만, 영상을 보고 바로 구현해보니 생각보다 잘 작동해서 정말 기뻤다! ㅎㅎ🎉
구글 캘린더 연동을 위해서 구글 캘린더 API를 scope에 추가하고, 구글 클라우드에서 캘린더 접근 권한을 설정했기 때문에 당연히 잘 작동될 거라 생각했다.
Google Access Token 필요!!
구글 로그인도 잘 되고 Cognito와 연동도 잘 되어, 가입한 유저가 Cognito에 추가되었다. 🥳
그러나... 구글 캘린더 연동 작업을 담당한 팀원에게 이런 DM이 왔다..
일단 Cognito로 구글 로그인을 하면 아래 사진처럼 쿠키에 자동으로 인증 관련 정보가 저장된다.
그 중 두 번째에 있는 Access Token이 구글에서 제공하는 Access Token이라고 착각을 했었다...
(다행히 빨리 알게 됨)
하지만 구글에서 제공하는 Refresh Token을 사용해 Access Token을 재발급받는 방법을 찾아보다가, 쿠키에 저장된 Token의 형태가 Google Token과 다른 것을 보고
Cognito Token !== Access Token
라는 것을 알게 되었다.
Google Access, Refresh Token 어떻게 가져올 수 있을까?
새로운 난관에 부딪히게 되었다...
로그인 직후 인증 이벤트를 모니터링했을 때, fetchUserAttributes 메서드로 응답받은 데이터에는 Cognito에 저장된 사용자의 상세 정보가 담겨 있었다.
이를 사용해서 구글에서 제공하는 Access Token과 Refresh Token을 가져올 수 있을 것 같았다.
(일단 로그인 메서드에서 아무것도 반환을 하지 않음)
그래서 Cognito에 들어가서 이것저것 살펴보다가.. 구글 OAuth로부터 받아온 사용자 정보를 Cognito에서 받아 올 수 있는 것을 확인했다!
찾았다!!!
다시 로그인하고 fetchUserAttributes 메서드를 통해 반환된 값을 출력했을 때, 구글에서 제공하는 Access Token과 Refresh Token이 잘 담겨 오는 것을 확인했다. 🥹
그리고 Refresh Token으로 Access Token 재발급받는 것도 잘 동작했다.
근데... 아니었다.
Refresh Token이 사라졌어요
여러 번의 테스트 후 알게 된 사실인데, 최초 로그인을 할 때에만 Refresh Token이 발급되고, 그 이후에 로그인할 때에는 Refresh Token이 발급되지 않는다는 점이었다.
사실 Refresh Token을 쿠키나 로컬 스토리지에 저장해서 Access Token을 재발급받아서 구글 캘린더를 사용할 수 있지만, 이 과정에서 여러가지 제약사항을 생각해 보았다.
만약 Refresh Token을 잃어버리면??
여러 시나리오...🧐
일반 사용자가 쿠키나 로컬스토리지를 삭제하는 경우가 별로 없지만, 만약 삭제하여 Refresh Token을 잃어버리게 될 경우에는 Access Token이 만료될 때마다 재발급을 하지 못하고 사용자가 로그인을 반복해야 하는 불상사가 발생하게 된다.
또한 Refresh Token을 잃어버리지 않아도, 크롬 브라우저에는 Refresh Token가 저장되어 있지만, 사파리나 파이어폭스 등 다른 브라우저에서는 저장되어 있지 않기 때문에 여기서도 로그인을 반복해야 한다.
이러한 문제들은 사용자 경험 저하로 이어질 수 있을 것 같다고 생각이 들었다.
다른 방법을 사용해 보자!
처음에는 Cognito를 사용해서 간편하게 구글 로그인을 구현했지만, 정보도 부족하고 Token 관리 방식이 유연하지 않기 때문에 최적의 방안은 아니었다.
그래서 더 유연한 방식으로 구글 로그인을 할 수 있는 방식을 선택하기로 했다.
다행히도 구글 캘린더 기능 구현을 담당하는 팀원이 NextAuth.js를 사용하여 구글 로그인을 임시로 구현하여 Refresh Token과 Access Token이 매번 잘 발급받아와 지는 것을 확인해주셨다! 그래서 팀원이 임시로 구현한 코드를 공유받아 NextAuth.js 라이브러리를 사용하여 구글 로그인을 다시 구현하게 되었다.
빠르게 바꾼 이유
문제를 해결하지 않고 너무 쉽게 바꾼 거 아니야? 생각할 수 있겠지만...
깃허브에서 AWS Amplify와 Cognito로 구글 로그인을 한 코드를 찾아봤지만 자료가 매우 한정적이었다..
문제를 해결하기 위해 많은 시간을 소비하는 것보다는, 많은 사람들이 널리 사용하고 있는 범용적인 방법을 선택하는 것이 더 효율적이라고 생각했기 때문에 바로 기술 스택을 전환하였다.
특히, 제한된 시간 안에 구글 캘린더 연동 작업을 마쳐야 했던 상황에서 빠르게 최선의 결정을 내렸다고 생각한다. (포기하는 법도 알아야.. 한다고 생각함)
결론을 미리 말하자면 이때 NextAuth.js로의 전환은 효과적이고 탁월한 결정이었다.
NextAuth.js를 활용한 구글 로그인
결국 최종적으로 Cognito를 활용한 구글 로그인을 NextAuth.js를 활용한 구글 로그인 방식으로 변경하게 되었다.
Google Token 발급이 너무 잘돼요
기존 Cognito를 사용했을 때와 다르게 NextAuth.js는 로그인 후 콜백함수를 작성할 수 있다.
이를 활용해 jwt 콜백, session 콜백에 구글 로그인 후 반환된 Access Token과 Refresh Token을 추가하여 토큰을 관리할 수 있게 했다.
추가된 Token을 가져오기 위해서 쿠키나 로컬 스토리지와 같은 별도의 저장소 필요 없이 useSession(컴포넌트에서 사용), getToken(API Routes에서 사용) 메서드를 통해 쉽게 Token에 접근할 수 있었다. 보안 측면에서 매우 좋은 방식인 것 같다.
Cognito를 계속 사용할까?
원래 AWS Amplify gen2 + Cognito를 사용해, User Pool 방식을 통해 유저 인증을 처리하고 있었다. 이를 통해 유저가 인증된 후에만 데이터 요청이 가능했고, 속한 그룹에 따라 요청 가능한 작업을 제한적으로 처리했다.
그래서 NextAuth.js로 인증 작업을 구현한 후, 이를 Cognito와 연동할지 고민하게 되었고
두 시스템을 동시에 관리하면 다음과 같은 문제가 발생할 것 같았다.
- 사용자 정보 동기화 문제
- 에러 처리 복잡성
- 로그인 플로우 복잡성
이러한 문제를 고려한 끝에, 결국 과감하게 Cognito를 사용하지 않기로 결정했다.
특히, 기존에도 유저에 대한 그룹 정보를 Cognito에서도 관리하고 User 테이블에서도 각각 관리를 했었기 때문에, 이 데이터를 동기화하는 과정이 복잡했었다.
이러한 경험을 바탕으로 NextAuth.js로만 유저 인증을 처리하기로 했다.
배포환경에서 동작을 안 하네..??
로컬환경에서 순조롭게 잘 진행이 되었다.. 😁
그러나 배포주소에 들어가 로그인 테스트를 하려고 하니 아래와 같은 에러가 떴다.
분명 구글 클라우드 설정과 환경 변수 설정도 제대로 잘했다고 생각했지만, 에러가 발생하였다...🥲
환경 변수 설정
발생한 문제의 원인을 찾기 위해, 공식문서를 살펴보니 배포할 때 필요한 환경 변수 설정이 있었다.
1. NEXTAUTH_URL
NEXTAUTH_URL에는 기본 경로를 설정해 주었다.
배포환경이랑 로컬환경에서는 각기 다른 URL을 사용해야 하므로, 파일에 각각 값을 다르게 설정해 주었다.
// .env.development
NEXTAUTH_URL="http://localhost:3000"
// .env.production
NEXTAUTH_URL="https://배포주소.com"
2. NEXTAUTH_SECRET
NextAuth.js JWT를 암호화하고 이메일 확인 토큰을 해시하는 데 사용된다고 한다.
딱히 정해진 값이 없었기 때문에 우리 팀의 이름인 2000을 NEXTAUTH_SECRET 값으로 설정하였다.
여전히 안 된다...
분명 환경 변수를 잘 설정했음에도, 로그인 버튼을 누르면 여전히 Server Error가 발생하였다.
에러 로그를 확인하자!
AWS CloudWatch에서 로그를 확인한 결과, 아래와 같은 에러를 발견하였다.
NextAuth가 production 환경에서 필요한 NEXTAUTH_SECRET 환경변수를 찾지 못했다는 의미이다.
즉, 환경 변수가 제대로 설정되지 않고 누락되었다는 것을 알게 되었다.
NEXTAUTH_SECRET 값 생성하기
다시 공식문서를 보니 openssl rand -base64 32
를 터미널에 입력하면, SECRET 값으로 사용할 수 있는 랜덤 문자열이 생성할 수 있다고 적혀있었다.
그래서 이 문제인가 싶어서 생성된 랜덤 문자열을 사용하여 NEXTAUTH_SECRET의 환경 변수로 설정하였다.
그러나.... 제대로 설정했음에도 불구하고 여전히 문제가 해결되지 않았다.😂
환경변수를 처리하는 방식 차이
공식문서와 깃허브를 찾아보면서 이런 문제가 발생하는 이유를 찾게 되었다!
바로 "Next.js에서 처리하는 환경변수와 AWS Amplify에서 처리하는 환경변수 방식 차이" 로 인해 발생하는 문제였다.
- Next.js: 빌드를 할 때(빌드 타임), 환경변수를 처리한다.
- Amplify: 서버가 실행될 때(런타임), 환경변수를 주입한다.
따라서 Next.js 빌드 시 환경변수를 찾지 못해, 결과적으로 빌드된 결과물에 환경 변수가 포함되어 있지 않았던 것이다.
환경변수 주입하기
문제를 해결하기 위해, Next.js를 빌드하기 전에 .env.production 파일을 생성하여 AWS Amplify 콘솔에서 설정한 환경변수 값을 .env.production 파일에 주입하였다.
그 결과 Next.js에서는 빌드 타임에 .env.production 파일을 읽을 수 있게 되어 환경 변수 값을 빌드에 포함시킬 수 있게 된다.
# 프론트엔드 설정
frontend:
phases:
# 빌드 단계
build:
commands:
# 환경변수 파일 생성
- touch .env.production
# Amplify 콘솔의 환경변수를 .env.production에 추가
- echo "NEXTAUTH_URL=\"${NEXTAUTH_URL}\"" >> .env.production
- echo "NEXTAUTH_SECRET=\"${NEXTAUTH_SECRET}\"" >> .env.production
- echo "GOOGLE_CLIENT_ID=\"${GOOGLE_CLIENT_ID}\"" >> .env.production
- echo "GOOGLE_CLIENT_SECRET=\"${GOOGLE_CLIENT_SECRET}\"" >> .env.production
# Next.js 빌드 실행
- pnpm run build
결과
변경 후 다시 배포하고 로그인 버튼을 눌렀더니... 배포환경에서 구글 로그인이 정상적으로 작동했다! 😂
드디어 성공!!
마치며...
해당 게시글에는 로그인 기능을 구현하기까지의 과정을 중심으로 다루었지만, 그 이외에도 구글 워크스페이스 프로필 사진 가져오기, 사용자 데이터 생성 및 수정, 인증 여부에 따른 렌더링 등 다양한 기능을 구현했다.
여러 기능들 중에서도 로그인 기능을 구현하는 것이 가장 도전적인 작업이었지만, 이번 프로젝트를 통해 로그인 기능을 직접 구현해 볼 수 있어서 뜻깊었다.
특히 프로젝트를 진행하는 동안 많은 문제들에 부딪혔고, 원인을 찾아 해결하는 과정이 쉽지는 않았지만 하나하나 난관을 헤쳐나가면서 스스로 성장하고 있음을 느끼게 된 값진 경험이 되었다. 🥹
기술 선정의 중요성
이번 프로젝트를 통해 기술 스택 선정이 왜 중요한지 뼈저리게 느끼게 되었다.
처음에는 React로 프로젝트를 시작했지만, 구글 캘린더 기능을 추가하면서 Next.js로 마이그레이션 해야 했고
AWS Cognito를 활용해 구글 로그인을 구현했으나, 토큰 관리와 관련된 예상치 못한 문제가 발생하여 결국 NextAuth.js로 스택을 변경해야 하는 상황에 직면했다.
여러 상황을 비교해 보았을 때, 솔직히 Cognito를 선택했을 수도 있겠지만 사용자 경험을 최우선으로 고려하였고 당장은 번거롭더라도 더 나은 방법을 선택하기 위해 과감한 결정을 내렸다.
환경 변수의 차이
우선 Next.js와 AWS Amplify의 환경변수 처리 방식이 다르다는 것을 새롭게 알게 되었다.
환경 변수 처리 방식에 대해 그동안 인식을 하지 못하고 있었는데, 각 플랫폼마다 환경 변수의 처리 방식이 다르고, 이에 따른 문제를 미리 파악하지 않으면 예기치 못한 오류가 발생할 수 있다는 점을 깨달았다.
만약 Vercel을 사용했다면 해당 문제가 발생하지 않았겠지만, AWS Amplify 환경에서 이를 해결하기 위해 CloudWatch 로그를 확인하고 문제를 분석했다. 이 과정에서 공식 문서를 참고하고 구글링을 통해 문제를 해결하며, 배포 환경에서 발생할 수 있는 문제를 예측하고 대응하는 방법을 배울 수 있었다.
이 경험을 통해 개발 환경뿐만 아니라 배포 환경에서의 동작 방식까지 꼼꼼히 검토해야 한다는 교훈을 얻었다.
+) 프로젝트 끝남..!
6주 동안 같이 함께 한 팀원 모두 고마웠고 짧은 시간 안에 많은 것들을 경험하고 성장할 수 있는 좋은 기회였다.
'💜 프로젝트 구현' 카테고리의 다른 글
[AWS Amplify Gen2] Cognito, AWS SDK를 활용한 유저 기능 구현 (5) | 2024.11.05 |
---|---|
[Jotai/Tailwind CSS] Toast 컴포넌트 직접 구현 (feat. Next.js App Router) (0) | 2024.09.19 |
[TanStack Query] 옵티미스틱 업데이트 사용하여 좋아요 구현 (1) | 2024.09.18 |
[Next.js] Lighthouse 웹 사이트 성능 개선 (100으로 만들기) (0) | 2024.09.10 |
[Next.js / TanStack query] useInfiniteQuery 사용하여 무한 스크롤 구현 (feat. cursor 방식) (0) | 2024.09.07 |
FE 개발자가 되고 싶은 짱잼이
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!