From 36de908431512f2bef82b008b4f61bcc45ed0eb9 Mon Sep 17 00:00:00 2001 From: Dohyun Lim Date: Wed, 21 Jan 2026 08:53:16 +0900 Subject: [PATCH] add kakao_verify endpoint for kakao login at frontend side --- app/user/api/routers/v1/auth.py | 49 +++++++++++++++++++++++++++++++++ app/user/schemas/__init__.py | 4 +-- app/user/schemas/user_schema.py | 4 +-- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/app/user/api/routers/v1/auth.py b/app/user/api/routers/v1/auth.py index 904a05a..2ba6768 100644 --- a/app/user/api/routers/v1/auth.py +++ b/app/user/api/routers/v1/auth.py @@ -15,6 +15,7 @@ from app.user.dependencies import get_current_user from app.user.models import User from app.user.schemas.user_schema import ( AccessTokenResponse, + KakaoCodeRequest, KakaoLoginResponse, LoginResponse, RefreshTokenRequest, @@ -78,6 +79,54 @@ async def kakao_callback( ) +@router.post( + "/kakao/verify", + response_model=LoginResponse, + summary="카카오 인가 코드 검증 및 토큰 발급", + description=""" +프론트엔드에서 카카오 로그인 후 받은 인가 코드를 검증하고 JWT 토큰을 발급합니다. + +## 사용 시나리오 +1. 프론트엔드가 카카오 로그인 완료 후 인가 코드(code)를 받음 +2. 프론트엔드가 이 엔드포인트에 code를 POST로 전달 +3. 서버가 카카오 서버에 code 검증 및 사용자 정보 조회 +4. JWT 토큰 발급 및 사용자 정보 반환 + +## 응답 +- 신규 사용자인 경우 `user.is_new_user`가 `true`로 반환됩니다. +- `redirect_url`은 로그인 후 이동할 프론트엔드 URL입니다. +""", +) +async def kakao_verify( + request: Request, + body: KakaoCodeRequest, + session: AsyncSession = Depends(get_session), + user_agent: Optional[str] = Header(None, alias="User-Agent"), +) -> LoginResponse: + """ + 카카오 인가 코드 검증 및 토큰 발급 + + 프론트엔드가 카카오 콜백에서 받은 인가 코드를 전달하면 + 카카오 서버에서 검증 후 JWT 토큰을 발급합니다. + + 신규 사용자인 경우 자동으로 회원가입이 처리됩니다. + """ + # 클라이언트 IP 추출 + ip_address = request.client.host if request.client else None + + # X-Forwarded-For 헤더 확인 (프록시/로드밸런서 뒤에 있는 경우) + forwarded_for = request.headers.get("X-Forwarded-For") + if forwarded_for: + ip_address = forwarded_for.split(",")[0].strip() + + return await auth_service.kakao_login( + code=body.code, + session=session, + user_agent=user_agent, + ip_address=ip_address, + ) + + @router.post( "/refresh", response_model=AccessTokenResponse, diff --git a/app/user/schemas/__init__.py b/app/user/schemas/__init__.py index 25dc83a..6841f87 100644 --- a/app/user/schemas/__init__.py +++ b/app/user/schemas/__init__.py @@ -1,6 +1,6 @@ from app.user.schemas.user_schema import ( AccessTokenResponse, - KakaoCallbackRequest, + KakaoCodeRequest, KakaoLoginResponse, KakaoTokenResponse, KakaoUserInfo, @@ -13,7 +13,7 @@ from app.user.schemas.user_schema import ( __all__ = [ "AccessTokenResponse", - "KakaoCallbackRequest", + "KakaoCodeRequest", "KakaoLoginResponse", "KakaoTokenResponse", "KakaoUserInfo", diff --git a/app/user/schemas/user_schema.py b/app/user/schemas/user_schema.py index b5cb9f2..dee7fe6 100644 --- a/app/user/schemas/user_schema.py +++ b/app/user/schemas/user_schema.py @@ -27,8 +27,8 @@ class KakaoLoginResponse(BaseModel): } -class KakaoCallbackRequest(BaseModel): - """카카오 콜백 요청 (인가 코드)""" +class KakaoCodeRequest(BaseModel): + """카카오 인가 코드 검증 요청 (프론트엔드에서 전달)""" code: str = Field(..., min_length=1, description="카카오 인가 코드")