14 KiB
Facebook 로그인 연동 - 프론트엔드 개발계획서
1. 개요
CastAD 서비스에 Facebook 계정 연동 기능을 구현한다. 백엔드에 OAuth 2.0 Authorization Code Flow가 이미 구현되어 있으며, 프론트엔드는 이 플로우의 시작(연동 요청)과 끝(결과 수신)을 담당한다.
기술 스택: React 기반 SPA를 전제로 한다.
2. OAuth 2.0 전체 플로우
Facebook OAuth 연동은 총 8단계로 이루어진다. 프론트엔드가 직접 관여하는 단계는 1, 2, 8번이며, 3~7번은 Facebook과 백엔드 사이에서 자동으로 처리된다.
1단계 (프론트 → 백엔드): 프론트엔드가 GET /sns/facebook/connect를 호출한다. Authorization 헤더에 사용자의 Bearer 토큰을 포함해야 한다. 백엔드는 auth_url(Facebook 인증 페이지 URL)과 state(CSRF 방지 토큰)를 JSON으로 응답한다.
2단계 (프론트 → Facebook): 프론트엔드가 응답받은 auth_url을 팝업 윈도우로 연다. 사용자는 이 팝업에서 Facebook 로그인과 권한 승인을 수행한다.
3단계 (Facebook → 백엔드): 사용자가 승인을 완료하면 Facebook이 백엔드의 redirect_uri(/sns/facebook/callback)로 리다이렉트한다. 이때 query parameter로 code(인가 코드)와 state를 전달한다. 프론트엔드는 이 단계에 관여하지 않는다.
4~6단계 (백엔드 ↔ Facebook): 백엔드가 code를 Facebook API에 전달하여 access token을 획득하고, 장기 토큰으로 교환한 뒤, 사용자 정보를 조회하여 DB에 저장한다. 프론트엔드는 이 단계에 관여하지 않는다.
7단계 (백엔드 → 프론트): 백엔드가 처리를 완료한 뒤 302 리다이렉트로 프론트엔드 URL로 보낸다. 성공 시 /social/connect/success로, 실패 시 /social/connect/error로 리다이렉트하며 결과 정보를 query parameter에 포함한다.
8단계 (프론트): 리다이렉트된 페이지(팝업 내부)에서 query parameter를 파싱하고, window.opener.postMessage()로 부모 창에 결과를 전달한 뒤 팝업을 닫는다. 부모 창은 메시지를 수신하여 UI를 업데이트한다.
3. 백엔드 API 명세
3-1. Facebook 연동 시작
- 엔드포인트:
GET /sns/facebook/connect - 인증: 필수.
Authorization: Bearer <access_token>헤더를 포함해야 한다. - 성공 응답 (200):
auth_url필드에 Facebook 인증 페이지 URL,state필드에 CSRF 방지 토큰이 포함된 JSON 객체를 반환한다.
{
"auth_url": "https://www.facebook.com/v21.0/dialog/oauth?client_id=...&redirect_uri=...&state=...&scope=...&response_type=code",
"state": "5PcHtcvJdQskHw5aG-MIOmv5CCX3L9v3eqQOqyZlKsc"
}
- 에러 응답: 401 (인증 실패, 토큰 없음 또는 만료)
3-2. Facebook OAuth 콜백
- 엔드포인트:
GET /sns/facebook/callback - 인증: 불필요. 이 엔드포인트는 Facebook이 직접 호출하며, 프론트엔드가 직접 호출하지 않는다.
- query parameter:
code(Facebook 인가 코드),state(CSRF 토큰),error(선택, OAuth 에러 코드),error_description(선택, 에러 설명) - 응답: JSON이 아닌 302 리다이렉트로 응답한다. 프론트엔드의 성공 또는 에러 페이지로 리다이렉트한다.
성공 시 리다이렉트 URL: {OAUTH_FRONTEND_URL}/social/connect/success?platform=facebook&account_id={id}&channel_name={name}
실패 또는 취소 시 리다이렉트 URL: {OAUTH_FRONTEND_URL}/social/connect/error?platform=facebook&error={message}&cancelled=true|false
OAUTH_FRONTEND_URL은 백엔드 환경변수로 설정되며, 개발 환경 기본값은 http://localhost:3000이다. 프론트엔드 배포 도메인과 일치해야 하므로 백엔드 팀과 사전 확인이 필요하다.
3-3. Facebook 연동 해제
- 엔드포인트:
DELETE /sns/facebook/disconnect - 인증: 필수.
Authorization: Bearer <access_token>헤더를 포함해야 한다. - 성공 응답 (200):
{"success": true, "message": "Facebook 계정 연동이 해제되었습니다."} - 에러 응답: 401 (인증 실패), 404 (연동된 Facebook 계정 없음, 에러 코드
FACEBOOK_ACCOUNT_NOT_FOUND)
4. 프론트엔드 구현 항목
4-1. 라우트 등록
React Router에 다음 2개의 라우트를 추가해야 한다. 이 페이지들은 백엔드 콜백의 302 리다이렉트 대상이므로 반드시 존재해야 한다.
/social/connect/success: OAuth 연동 성공 처리 페이지/social/connect/error: OAuth 연동 실패/취소 처리 페이지
이 두 페이지는 팝업 윈도우 안에서 렌더링되며, 독립적으로 접근 가능해야 한다(SPA 라우팅이 아닌 직접 URL 접근으로도 동작해야 한다).
4-2. Facebook 연동 버튼 컴포넌트
소셜 계정 관리 페이지 또는 설정 페이지에 배치한다.
동작 흐름:
- 사용자가 "Facebook 연동하기" 버튼을 클릭한다.
- 버튼을 로딩(비활성) 상태로 전환한다.
GET /sns/facebook/connect를 호출하여auth_url과state를 받는다.window.open(auth_url, 'facebook-oauth', 'width=600,height=700,scrollbars=yes')로 팝업을 연다.window.open()의 반환값이null이면 브라우저가 팝업을 차단한 것이다. 이 경우window.location.href = auth_url로 현재 창에서 리다이렉트하는 폴백을 적용한다.- 팝업에서
postMessage를 수신할message이벤트 리스너를 등록한다.
팝업 방식을 권장하는 이유: 현재 페이지의 상태(폼 입력값, 스크롤 위치 등)를 유지하면서 OAuth 인증을 진행할 수 있다.
4-3. OAuth 성공 페이지 (/social/connect/success)
이 페이지는 팝업 윈도우 안에서 렌더링된다.
동작 흐름:
URLSearchParams로 query parameter를 파싱한다. 사용 가능한 파라미터는platform("facebook"),account_id(연동된 계정 ID, 숫자),channel_name(Facebook 사용자 이름)이다.window.opener가 존재하는지 확인한다(팝업으로 열렸는지 판별).- 팝업인 경우:
window.opener.postMessage()로 부모 창에 성공 결과를 전달하고,window.close()로 팝업을 닫는다. postMessage의targetOrigin은 보안을 위해window.location.origin으로 지정한다. - 팝업이 아닌 경우(폴백 리다이렉트): 소셜 계정 관리 페이지로
navigate()한다.
postMessage 데이터 구조:
{
"type": "FACEBOOK_OAUTH_RESULT",
"success": true,
"platform": "facebook",
"accountId": "연동된 계정 ID",
"channelName": "Facebook 사용자 이름"
}
4-4. OAuth 에러 페이지 (/social/connect/error)
이 페이지도 팝업 윈도우 안에서 렌더링된다.
동작 흐름:
URLSearchParams로 query parameter를 파싱한다. 사용 가능한 파라미터는platform("facebook"),error(에러 메시지 문자열),cancelled("true" 또는 "false")이다.cancelled가 "true"이면 사용자가 Facebook에서 직접 취소한 것이다. "false"이면 시스템 에러이다.- 팝업인 경우:
window.opener.postMessage()로 부모 창에 에러 결과를 전달하고,window.close()로 팝업을 닫는다. - 팝업이 아닌 경우: 에러 메시지를 화면에 표시하고 "다시 시도" 버튼과 소셜 관리 페이지로 돌아가는 링크를 제공한다.
postMessage 데이터 구조:
{
"type": "FACEBOOK_OAUTH_RESULT",
"success": false,
"platform": "facebook",
"error": "에러 메시지",
"cancelled": true
}
4-5. 부모 창 메시지 수신 처리
연동 버튼이 있는 컴포넌트에서 window.addEventListener('message', handler)로 메시지를 수신한다.
처리 로직:
event.origin이 현재 페이지의 origin과 일치하는지 검증한다(보안).event.data.type이'FACEBOOK_OAUTH_RESULT'인지 확인한다.event.data.success가true이면 연동 성공 UI를 표시한다(토스트 메시지, 연동된 계정 정보 갱신 등).event.data.success가false이면event.data.cancelled여부에 따라 취소 메시지 또는 에러 메시지를 표시한다.- 컴포넌트 언마운트 시 이벤트 리스너를 반드시 해제한다.
4-6. 연동 해제 기능
동작 흐름:
- 연동된 계정 정보 옆에 "연동 해제" 버튼을 배치한다.
- 클릭 시 확인 다이얼로그를 표시한다("Facebook 연동을 해제하시겠습니까?").
- 확인 시
DELETE /sns/facebook/disconnect를 호출한다. Authorization 헤더를 포함해야 한다. - 성공 응답(200) 수신 시 UI를 미연동 상태로 업데이트한다.
- 404 응답 시 이미 연동 해제된 상태이므로 UI를 미연동 상태로 업데이트한다.
4-7. 연동 상태 표시
연동 전 상태: Facebook 아이콘과 "연동하기" 버튼을 표시한다.
연동 후 상태: Facebook 아이콘, 연동된 사용자 이름(channel_name), "연동 해제" 버튼을 표시한다.
참고: 현재 백엔드에 사용자의 Facebook 연동 상태를 조회하는 전용 API가 없다. 페이지 진입 시 연동 여부를 표시하려면, 기존 사용자 프로필 API에 소셜 연동 정보가 포함되어 있는지 확인하거나, 백엔드에 상태 조회 API 추가를 요청해야 한다. 연동 직후에는 postMessage로 받은 accountId와 channelName을 컴포넌트 상태에 저장하여 표시할 수 있다.
5. 에러 처리
프론트엔드에서 처리해야 하는 에러 시나리오와 대응 방법은 다음과 같다.
사용자 취소 (cancelled=true): 사용자가 Facebook 인증 화면에서 "취소"를 눌렀다. "Facebook 연동이 취소되었습니다." 메시지를 표시한다. 재시도 가능하므로 연동 버튼을 다시 활성화한다.
state 만료 (FACEBOOK_STATE_EXPIRED): 사용자가 Facebook 로그인을 너무 오래 지체하여 백엔드의 state 토큰이 만료되었다(기본 TTL: 300초, 약 5분). "인증 세션이 만료되었습니다. 다시 시도해주세요." 메시지를 표시한다.
토큰 교환 실패 (FACEBOOK_AUTH_FAILED): Facebook에서 받은 인가 코드로 access token 교환이 실패했다. "Facebook 인증에 실패했습니다. 다시 시도해주세요." 메시지를 표시한다.
Facebook API 오류 (FACEBOOK_API_ERROR): Facebook Graph API 호출 중 서버 오류가 발생했다. "일시적인 오류가 발생했습니다. 잠시 후 다시 시도해주세요." 메시지를 표시한다.
연동 해제 시 계정 없음 (FACEBOOK_ACCOUNT_NOT_FOUND): 이미 연동 해제된 상태에서 다시 해제를 시도했다. UI를 미연동 상태로 갱신한다.
인증 실패 (401): 서비스 로그인 토큰이 만료되었다. 로그인 페이지로 이동시킨다.
6. 폴백 처리 (팝업 차단 시)
브라우저가 팝업을 차단하는 경우 window.open()이 null을 반환한다. 이때는 현재 창에서 window.location.href = auth_url로 직접 이동시키는 폴백을 적용한다.
폴백 모드에서는 OAuth 완료 후 /social/connect/success 또는 /social/connect/error 페이지로 돌아왔을 때 window.opener가 null이다. 이 경우 postMessage 대신 다음과 같이 처리한다:
- query parameter에서 결과를 파싱한다.
- 결과를
sessionStorage에 저장한다(키 예시:facebook_oauth_result). - 소셜 계정 관리 페이지로
navigate()한다. - 소셜 계정 관리 페이지 마운트 시
sessionStorage에서 결과를 확인하고, 있으면 UI에 반영한 뒤sessionStorage에서 삭제한다.
이 폴백이 필요한 이유: 현재 창 리다이렉트 방식에서는 OAuth 전에 있던 페이지 상태가 사라지므로, 결과를 임시 저장하여 원래 페이지로 복귀 후 처리해야 한다.
7. 환경 설정 확인 사항
프론트엔드 개발 시작 전에 백엔드 팀과 다음 값들을 확인해야 한다.
-
OAUTH_FRONTEND_URL: 백엔드가 콜백 처리 후 프론트엔드로 302 리다이렉트할 때 사용하는 URL이다. 개발 환경에서는http://localhost:3000, 운영 환경에서는 실제 프론트엔드 도메인이 설정되어야 한다. 이 값이 잘못되면 OAuth 완료 후 엉뚱한 URL로 리다이렉트된다. -
OAUTH_SUCCESS_PATH: 성공 시 리다이렉트 경로. 기본값은/social/connect/success이다. -
OAUTH_ERROR_PATH: 실패 시 리다이렉트 경로. 기본값은/social/connect/error이다. -
FACEBOOK_REDIRECT_URI: Facebook이 인증 완료 후 호출하는 백엔드 콜백 URL이다. 프론트엔드가 변경할 수 없으며, Facebook 개발자 콘솔에 등록된 URI와 정확히 일치해야 한다. 현재 설정값은http://dev.castad.net/sns/facebook/callback이다.
8. 구현 순서
다음 순서로 구현을 권장한다.
- React Router에
/social/connect/success와/social/connect/error라우트를 등록한다. - 성공 페이지를 구현한다. query parameter 파싱과 postMessage 전송 로직을 작성한다.
- 에러 페이지를 구현한다. 취소와 에러를 분기 처리하고 postMessage 전송 로직을 작성한다.
- Facebook 연동 버튼 컴포넌트를 구현한다. API 호출, 팝업 열기, postMessage 수신을 작성한다.
- 팝업 차단 시 폴백 로직을 구현한다(sessionStorage 기반).
- 연동 해제 기능을 구현한다.
- 연동 상태 표시 UI를 구현한다(상태 조회 API 확인 후).