fix(be): add redirection to frontend after kakao oauth callback#3627
fix(be): add redirection to frontend after kakao oauth callback#3627nhjbest22 wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces frontend URL configuration and updates the Kakao social login callback in AuthController to redirect users back to the frontend application. The review feedback highlights a critical issue where setting the authorization header on a 302 redirect response is ineffective because the browser handles the redirect automatically, meaning the frontend cannot read the header. Additionally, the reviewer suggests using the URL API instead of template strings to safely construct redirect URLs and handle query parameters without manual string concatenation.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| ) | ||
| } | ||
|
|
||
| this.setJwtResponse(res, result.jwtTokens) |
There was a problem hiding this comment.
리다이렉트(302 Redirect) 응답에 authorization 헤더를 설정하더라도, 브라우저가 리다이렉트를 자동으로 처리하기 때문에 프론트엔드 애플리케이션에서는 이 헤더 값을 읽을 수 없습니다.
따라서 setJwtResponse 내에서 설정되는 res.setHeader('authorization', ...)를 통한 accessToken 전달은 무효화됩니다.
프론트엔드가 로그인 완료 후 refresh_token 쿠키를 이용해 /reissue를 호출하여 accessToken을 획득하는 구조라면 문제가 없으나, 그렇지 않고 콜백 응답에서 직접 accessToken을 받아야 하는 구조라면 토큰 전달 방식을 재검토해야 합니다.
| if ('oauthToken' in result) { | ||
| return res.redirect( | ||
| `${frontendUrl}/login?modal=social-unlinked&oauthToken=${result.oauthToken}` | ||
| ) | ||
| } |
There was a problem hiding this comment.
하드코딩된 문자열 템플릿 대신 URL 객체를 사용하여 리다이렉트 URL을 생성하는 것이 좋습니다. URL 객체를 사용하면 frontendUrl에 슬래시(/)가 포함되어 있는지 여부와 상관없이 경로를 안전하게 병합할 수 있으며, searchParams를 통해 oauthToken과 같은 쿼리 파라미터를 설정하면 자동으로 안전하게 URL 인코딩이 적용됩니다.
| if ('oauthToken' in result) { | |
| return res.redirect( | |
| `${frontendUrl}/login?modal=social-unlinked&oauthToken=${result.oauthToken}` | |
| ) | |
| } | |
| if ('oauthToken' in result) { | |
| const redirectUrl = new URL('/login', frontendUrl) | |
| redirectUrl.searchParams.set('modal', 'social-unlinked') | |
| redirectUrl.searchParams.set('oauthToken', result.oauthToken) | |
| return res.redirect(redirectUrl.toString()) | |
| } |
References
- When handling social authentication, do not trust client-provided OAuth IDs directly. Instead, use a server-signed token (e.g., JWT) to securely pass OAuth information for server-side verification before linking accounts.
| } | ||
|
|
||
| this.setJwtResponse(res, result.jwtTokens) | ||
| return res.redirect(`${frontendUrl}/`) |
|
해당 PR 작업으로 인해서 소셜 로그인 후에 FE에서 처리해주셔야 할 게 생겨서 별도로 코멘트 드려요!
|
| if ('oauthToken' in result) return result | ||
| if ('oauthToken' in result) { | ||
| return res.redirect( | ||
| `${frontendUrl}/login?modal=social-unlinked&oauthToken=${result.oauthToken}` |
There was a problem hiding this comment.
oauthToken이 URL에 포함되어 있으면 브라우저 히스토리에 기록이 남으니 보안에 안좋을거 같아서
쿠키에 저장하고, 리다이렉트 후 프론트에서 쿠키를 읽는 방식은 어떨까요?
There was a problem hiding this comment.
원래는 쿠키를 통해서 이를 전달하려고 했는데, 다음 이유 때문에 Query String으로 바꾸었어요.
FE에서 쿠키에 저장된 값을 직접 읽어서 사용하려면 httpOnly 값을 false로 두어야 하는데 그러면 XSS 공격에 노출될 수 있어요. 이럴 경우에는 사실 상 QueryString으로 전달하는 것이랑 크게 다른게 없지 않나 싶어서요.
또, 쿠키로 전달하면 브라우저 내부에 쿠키가 지속적으로 저장될 텐데, 회원가입이나 소셜 연동 이후에 FE 쪽에서 쿠키 무효화까지 신경써줘야 할 것 같은데, 그럴 경우에는 품이 크게 들 수 있을 것 같아요.
추가적으로, oauthToken 내부에 사실 provider 값과 provider에서 제공하는 oauthId 정도인데 이게 보안 상 그렇게 중요한가? 싶기도 해요
| ) | ||
| } | ||
|
|
||
| this.setJwtResponse(res, result.jwtTokens) |
There was a problem hiding this comment.
이거 제미나이 코멘트도 지금 코드상으로는 반영해야 할거 같아요! 나머지 medium priority 2개는 흠.. 안해도 괜찮을거 같은데 잘 모르겠네요
There was a problem hiding this comment.
기존 가입자가 소셜 로그인 (소셜 로그인 후에 바로 메인 페이지로 가능 케이스)
BE에서 res.redirect('/')를 사용해서 리다이렉트하면 refresh_token은 쿠키에 저장되지만 access_token은 헤더에 들어가는 값이라 자연스럽게 소멸되어요.
이런 경우에는 로그인 후 메인 페이지로 갔지만 로그인 정보가 없는 상황(액세스 토큰 정보가 없음)이라서 FE에서 별도로 api 서버로 reissue를 호출해야 할 것 같아요.
위 쪽 코멘트 통해서 FE쪽에 이미 요청드렸습니다! 저희 쪽에서는 처리가 힘든 문제라서요
if ('oauthToken' in result) {
return res.redirect(
`${frontendUrl}/login?modal=social-unlinked&oauthToken=${result.oauthToken}`FE쪽 요구사항에 맞춰서 로그인 성공시 위 링크로 redirect 되도록 처리는 했는데, 예시) 로그인되어 있는 유저가 마이페이지에서 한번도 연동하지 않았던 구글 계정과 연동 시도 이런 케이스는 어떻게 처리하실 생각이실까요? |
위 상황에 대해 작업 당시 문제 의식을 했는데, 해당 내용에 대해 정리한 내용을 전달드립니다! [현재 문제 상황] /auth/kakao-callback (소셜 콜백 엔드포인트 전반)은 요청을 보낸 사람이 이미 로그인된 상태인지 알지 못합니다. 그래서 로그인된 유저가 마이페이지에서 소셜 계정 연동을 시도해도, 콜백 입장에서는 일반 로그인 시도와 구분이 안 됩니다. 이로 인해 두 가지 문제가 발생합니다. [왜 백엔드에서 해결해야 하는지] 콜백 시점에 “현재 로그인 세션이 있는지”를 확인할 수 있는 건 백엔드뿐입니다. 프론트는 콜백 URL로 redirect되기 전에 개입할 수단이 없고, 카카오에서 돌아온 이후에는 이미 백엔드가 JWT를 발급하거나 redirect를 결정한 뒤입니다. [요청사항] 콜백 진입 시점에 요청에 유효한 인증 세션(토큰)이 존재하는지 먼저 확인하고, 존재한다면 로그인 흐름이 아니라 연동 흐름(social-link)으로 분기하도록 수정해주세요. [프론트에서의 후속 처리] 백엔드에서 분기를 나눠주면, 프론트는 연동 성공/실패 결과에 맞는 redirect URL을 따로 정의해서 마이페이지에서의 연동 결과를 정상적으로 처리할 수 있습니다. 현재는 이 분기 자체가 없어서 프론트에서 처리할 방법이 없는 상태입니다. |
카카오 로그인 시에는 백엔드 서버로 fetch 등을 통해 요청을 보내는 것이 아닌, 그래서 콜백 진입 시점에 백엔드에서도 토큰 정보가 존재하는지 파악하기에는 힘들 것 같아요! |
Description
해당 PR 요약
카카오 OAuth 콜백이 JSON을 반환하던 방식에서 프론트엔드 URL로 redirect하는 방식으로 변경합니다.
배경 설명
우선 대략적인 소셜 로그인 WorkFlow는 다음과 같습니다.
프론트: window.location.href = 'https://api.codedang.com/auth/kakao'
↑ 브라우저가 백엔드 URL로 이동
백엔드 → 카카오 로그인 페이지로 redirect
↑ 브라우저가 카카오 URL로 이동
사용자가 카카오에서 로그인
카카오 → 백엔드 /auth/kakao-callback으로 redirect
↑ 브라우저가 다시 백엔드 URL로 이동
기존 카카오 로그인은
window.location.href로 백엔드 URL로 이동하는 방식이라,OAuth 콜백 이후 브라우저가 백엔드 URL에 위치하게 됩니다.
해당 시점에서는 백엔드가 JSON을 반환해도 프론트엔드 코드가 실행 중이 아니므로
응답을 처리할 수 없는 문제가 있었습니다.
이 때 기존 BE 코드와 같이 단순히
result를return하는 방식으로는 빈 화면에 JSON 결과물만 보이게 되고정상적으로 FE 페이지로 돌아가지 못하는 문제가 발생합니다.
이를 해결하기 위해 BE에서 직접
redirect를 통해 페이지를 이동시켜줘야 합니다.다만, 이럴 경우 다음과 같은 문제가 발생합니다.
oauthToken증발기존에는
return result를 통해result.oauthToken을 전달하였으나 res.redirect를 사용하도록 바뀌어 return을 통한 데이터 전달이 불가능하게 되었습니다.이를 해결하기 위해
oauthToken를 queryString 형식으로 FE에 전달하도록 수정하였습니다.oauthToken을 임시로 cookie(이 때, httpOnly: false 적용)로 만들어서FE로 전달 후, 이를 FE에서 추출해 사용하는 방식도 생각해 보았으나, 기존 oauthToken 쿠키를 무효화하는 등 관리 상의 여러 어려움이 있을 것 같아 현재와 같이 queryString 형식을 채택하였습니다.Additional context
Before submitting the PR, please make sure you do the following
fixes #123).