Nodemailer와 GCP로 Gmail OAuth2 설정
출처: Dev.to
애플리케이션에서 이메일을 보내는 방식은 수년간 크게 변했습니다. 예전에는 Gmail SMTP에 사용자명·비밀번호 인증을 사용했지만, 보안 문제와 Google이 “덜 안전한” 앱으로 간주하는 애플리케이션에 대한 제한 때문에 이제는 더 이상 권장되지 않습니다.
현재 가장 안전한 방법은 Gmail OAuth2 인증을 사용하고, Nodemailer와 같은 외부 메일 전송 라이브러리를 함께 활용하는 것입니다. 저는 이 과정을 제 포트폴리오에 적용해 보았고, 다른 사람들에게도 도움이 될 것이라 생각해 글을 남깁니다.
이 가이드를 통해 다음을 배울 수 있습니다:
- Google Cloud Platform(GCP) 프로젝트 설정
- Gmail API 활성화
- OAuth 동의 화면 구성
- OAuth 자격 증명 생성
- 리프레시 토큰 생성
- Nodemailer에 Gmail OAuth2 적용
- 흔히 발생하는 오류 처리
- 애플리케이션 보안 강화
이 튜토리얼은 다음 환경에서 동작합니다:
- Node.js
- Express
- Nuxt 서버 라우트
- Vue 백엔드 API
- Next.js API 라우트
- TypeScript 프로젝트
목차
- 왜 Gmail OAuth2를 사용해야 할까?
- 사전 준비 사항
- Google Cloud 프로젝트 만들기
- Gmail API 활성화
- OAuth 동의 화면 구성
- OAuth 자격 증명 만들기
- 의존성 설치
- 리프레시 토큰 생성
- Nodemailer 설정
- 이메일 전송
- HTML 이스케이프 및 인젝션 방지
- 흔한 오류와 해결 방법
- 운영 환경 권장 사항
- 마무리 생각
왜 Gmail OAuth2를 사용해야 할까?
Google은 많은 애플리케이션에서 비밀번호 기반 SMTP 인증을 더 이상 지원하지 않게 되었습니다.
OAuth2는 다음과 같은 장점을 제공합니다:
- 향상된 보안
- 토큰 기반 인증
- Gmail 비밀번호 노출 방지
- 접근 범위(스코프) 제어
- 언제든지 접근 권한 회수 가능
예전처럼 아래와 같이 작성하지 마세요:
auth: {
user: "example@gmail.com",
pass: "gmail-password"
}
전체 화면 모드 진입
전체 화면 모드 종료
다음과 같이 작성하면 됩니다:
auth: {
type: "OAuth2",
user,
clientId,
clientSecret,
refreshToken
}
전체 화면 모드 진입
전체 화면 모드 종료
사전 준비 사항
작업을 시작하기 전에 아래 항목들을 준비하세요:
- Google 계정
- Node.js 설치
- npm 또는 yarn
- 백엔드/서버 환경
- JavaScript 또는 TypeScript 기본 지식
Google Cloud 프로젝트 만들기
Google Cloud Console에 접속합니다:
1단계: 새 프로젝트 만들기
- 상단 바의 프로젝트 드롭다운을 클릭
- New Project 클릭
- 원하는 프로젝트 이름 입력
- Create 클릭
프로젝트가 초기화될 때까지 기다립니다.
Gmail API 활성화
프로젝트가 생성되면:
- API 라이브러리를 엽니다:
검색창에 다음을 입력:
Gmail API
전체 화면 모드 진입
전체 화면 모드 종료
- Gmail API를 클릭
- Enable 클릭
Gmail API를 활성화하지 않으면 OAuth를 이용한 메일 전송이 작동하지 않습니다.
OAuth 동의 화면 구성
OAuth 자격 증명을 만들기 전에 반드시 이 과정을 거쳐야 합니다.
다음 주소로 이동합니다:
1단계: 사용자 유형 선택
External
전체 화면 모드 진입
전체 화면 모드 종료
Create 클릭.
2단계: 앱 정보 입력
다음 항목을 입력:
- 앱 이름
- 지원 이메일
- 개발자 연락 이메일
예시:
App Name: Portfolio Mailer
전체 화면 모드 진입
전체 화면 모드 종료
3단계: 테스트 사용자 추가
앱이 아직 테스트 단계라면:
- Test Users 섹션을 엽니다
- 메일 전송에 사용할 Gmail 계정을 추가합니다
이 단계를 건너뛰면 다음 오류가 발생할 수 있습니다:
unauthorized_client
전체 화면 모드 진입
전체 화면 모드 종료
OAuth 자격 증명 만들기
이제 OAuth 자격 증명을 생성합니다.
다음 주소로 이동합니다:
1단계: OAuth 클라이언트 ID 만들기
- Create Credentials 클릭
- OAuth Client ID 선택
2단계: 애플리케이션 유형 선택
Web Application
전체 화면 모드 진입
전체 화면 모드 종료
다음 옵션은 선택하지 마세요:
- Desktop App
- Android
- iOS
3단계: 리디렉션 URI 추가
아래 섹션에 입력:
Authorized redirect URIs
전체 화면 모드 진입
전체 화면 모드 종료
예시:
http://localhost:3000
전체 화면 모드 진입
전체 화면 모드 종료
또는 사용 중인 포트를 입력합니다. 리프레시 토큰을 생성하려면 반드시 필요합니다.
4단계: 자격 증명 저장
설정을 마치면 Google이 다음 정보를 제공합니다:
Client ID
Client Secret
전체 화면 모드 진입
전체 화면 모드 종료
이 정보를 안전하게 보관하고, 복사하거나 다운로드해 두세요.
의존성 설치
필요한 패키지를 설치합니다:
npm install nodemailer googleapis dotenv
전체 화면 모드 진입
전체 화면 모드 종료
TypeScript를 사용하는 경우 추가로 실행합니다:
npm install -D tsx typescript
전체 화면 모드 진입
전체 화면 모드 종료
환경 변수
프로젝트 루트에 .env 파일을 생성하고 다음을 입력합니다:
SMTP_USER=your-email@gmail.com
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret
GOOGLE_OAUTH_REFRESH_TOKEN=your-refresh-token
전체 화면 모드 진입
전체 화면 모드 종료
.env 파일을 절대 GitHub 등에 커밋하지 마세요. 보안에 유의하시기 바랍니다.
리프레시 토큰 생성
이 단계는 설정 과정에서 가장 중요한 부분 중 하나입니다.
1단계: 스크립트 파일 만들기
프로젝트 루트에 다음 경로와 파일을 생성합니다:
scripts/generate-refresh-token.ts
전체 화면 모드 진입
전체 화면 모드 종료
다음 코드를 추가합니다:
import { google } from "googleapis";
import dotenv from "dotenv";
dotenv.config();
const oauth2Client = new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
"http://localhost:3000"
);
const scopes = [
"https://mail.google.com/",
];
const url = oauth2Client.generateAuthUrl({
access_type: "offline",
scope: scopes,
prompt: "consent",
});
console.log("Authorization url: ", url);
전체 화면 모드 진입
전체 화면 모드 종료
2단계: 스크립트 실행
TypeScript를 사용할 경우:
npx tsx scripts/generate-refresh-token.ts
전체 화면 모드 진입
전체 화면 모드 종료
JavaScript 파일로 실행하려면:
node scripts/generate-refresh-token.js
전체 화면 모드 진입
전체 화면 모드 종료
3단계: 인증 URL 열기
터미널에 출력된 Google 인증 URL을 복사해 브라우저에 붙여넣습니다.
SMTP_USER와 동일한 Gmail 계정으로 로그인합니다.
전체 화면 모드 진입
전체 화면 모드 종료
권한 요청 화면이 나타나면 승인합니다.
4단계: 인증 코드 복사
승인 후 Google이 설정해 둔 리디렉션 URL(예: http://localhost:3000/?code=...)로 이동합니다.
URL에 포함된 code 값을 복사합니다.
예시:
4/0AX4XfWh...
전체 화면 모드 진입
전체 화면 모드 종료
5단계: 코드와 토큰 교환
스크립트를 다음과 같이 수정합니다:
import { google } from "googleapis";
import dotenv from "dotenv";
dotenv.config();
const oauth2Client = new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
"http://localhost:3000"
);
async function getRefreshToken() {
const code = "PASTE_AUTHORIZATION_CODE";
const { tokens } = await oauth2Client.getToken(code);
console.log("Refresh Token:", tokens.refresh_token);
}
getRefreshToken().catch(console.error);
전체 화면 모드 진입
전체 화면 모드 종료