[FastAPI + React] 소셜 로그인 구현하기 - 커스텀 로그인 (feat. 카카오)

Joonas' Note

[FastAPI + React] 소셜 로그인 구현하기 - 커스텀 로그인 (feat. 카카오) 본문

개발

[FastAPI + React] 소셜 로그인 구현하기 - 커스텀 로그인 (feat. 카카오)

2022. 9. 20. 23:57 joonas 읽는데 3분
  • 들어가기 전에
  • FastAPI
  • 커스텀 클라이언트 클래스
  • 라우터 추가
  • React
  • 결과
  • 코드

아래의 글을 먼저 읽고 진행하시는 것을 추천한다.

 

[FastAPI + React] 소셜 로그인 구현하기 - 구글 로그인

아래 글에서 이어지는 내용입니다. [FastAPI + React] 소셜 로그인 구현하기 - 이메일 로그인 아래 글에서 이어지는 내용이다. [FastAPI + React] 소셜 로그인 구현하기 - 기본 환경 구축 들어가기 전에 Reac

blog.joonas.io


들어가기 전에

Google, GitHub, Microsoft 등은 FastAPI Users에서 친절하게 구현을 미리 해주었습니다. 하지만 카카오와 같은 국내 기업들에 대한 로그인은 구현되어 있지 않다.

그래서 이 글에서는 클래스를 오버라이딩해서, 기존에 작성한 FastAPI Users와 완전히 호환되는 로그인을 구현한다.


FastAPI

커스텀 클라이언트 클래스

구글 로그인을 연동할 때에는, 아래와 같이 GoogleOAuth2 라는 클래스를 사용했었다.

from httpx_oauth.clients.google import GoogleOAuth2

google_oauth_client = GoogleOAuth2(
    client_id=Configs.GOOGLE_CLIENT_ID,
    client_secret=Configs.GOOGLE_CLIENT_SECRET,
    scope=[
        "https://www.googleapis.com/auth/userinfo.profile",
        "https://www.googleapis.com/auth/userinfo.email",
        "openid"
    ],
)

이것과 동일하게 KakaoOAuth2 라는 클래스를 만들어서, Client ID와 Client Secret, 그리고 scope를 넘기면 유저 생성부터 기존 아이디 연동까지, FastAPI Users 에 그대로 호환되도록 한다.

아래와 같이 클래스를 만든다.

from typing import Any, Dict, List, Optional, Tuple, cast

import json
from httpx_oauth.errors import GetIdEmailError
from httpx_oauth.oauth2 import BaseOAuth2
from httpx_oauth.typing import TypedDict

AUTHORIZE_ENDPOINT = "https://kauth.kakao.com/oauth/authorize"
ACCESS_TOKEN_ENDPOINT = "https://kauth.kakao.com/oauth/token"
PROFILE_ENDPOINT = "https://kapi.kakao.com/v2/user/me"
BASE_SCOPES = ["account_email"]
BASE_PROFILE_SCOPES = ["kakao_account.email"]


class KakaoOAuth2(BaseOAuth2[Dict[str, Any]]):
    display_name = "Kakao"
    logo_svg = LOGO_SVG

    def __init__(
        self,
        client_id: str,
        client_secret: str,
        scopes: Optional[List[str]] = BASE_SCOPES,
        name: str = "kakao",
    ):
        super().__init__(
            client_id,
            client_secret,
            AUTHORIZE_ENDPOINT,
            ACCESS_TOKEN_ENDPOINT,
            name=name,
            base_scopes=scopes,
        )

    async def get_id_email(self, token: str) -> Tuple[str, Optional[str]]:
        async with self.get_httpx_client() as client:
            response = await client.post(
                PROFILE_ENDPOINT,
                params={"property_keys": json.dumps(BASE_PROFILE_SCOPES)},
                headers={**self.request_headers,
                         "Authorization": f"Bearer {token}"},
            )

            if response.status_code >= 400:
                raise GetIdEmailError(response.json())

            account_info = cast(Dict[str, Any], response.json())
            kakao_account = account_info.get('kakao_account')

            return str(account_info.get('id')), kakao_account.get('email')

주의할 점은, 카카오 API에서는 계정 id를 정수형으로 반환하는데, get_id_email 함수에서는 DB에서 조회할 때 이 타입이 달라서 검색을 하지 못하고 계속 새로운 계정을 생성한다. 그렇기 때문에 문자열로 변환해서 리턴해야 한다.

라우터 추가

이렇게 만든 클래스를 사용해서 구글 로그인과 동일하게 아래와 같이 라우터를 만들어서 추가한다.

auth_backend_kakao = AuthenticationBackend(
    name="jwt-kakao",
    transport=bearer_transport,
    get_strategy=get_jwt_strategy
)

# 인증 클라이언트
kakao_oauth_client = KakaoOAuth2(
    client_id=Configs.KAKAO_CLIENT_ID,
    client_secret=Configs.KAKAO_CLIENT_SECRET,
    scopes=[
    	# https://developers.kakao.com/docs/latest/ko/kakaologin/common#user-info-kakao-account
        "profile_nickname", "profile_image", "account_email",
    ]
)

# 카카오 로그인 JWT 라우터
kakao_oauth_router = fastapi_users.get_oauth_router(
    oauth_client=kakao_oauth_client,
    backend=auth_backend_kakao,
    state_secret=Configs.SECRET_KEY,
    redirect_url="http://localhost:3000/login/kakao",
    associate_by_email=True,
)

# 라우터 추가
app.include_router(kakao_oauth_router, prefix="/auth/kakao", tags=["auth"])

이제 Interactive docs에서 엔드포인트를 확인해보면 잘 추가된 것을 볼 수 있다.


React

리액트쪽에서 해줄 것은 구글 로그인과 동일하게 콜백에 대한 처리를 하는 부분밖에 없다.

<Route path="/login">
  <Route index element={<Auth.Login />} />
  <Route path="google" element={<Auth.Redirects.Google />} />
  <Route path="github" element={<Auth.Redirects.Github />} />
  <Route path="kakao" element={<Auth.Redirects.Kakao />} />
</Route>

엔드 포인트의 형식이 다른 로그인과 동일하기 때문에, /auth/kakao/callback 로 데이터를 그대로 전달해서 토큰을 발급받으면 된다.

customAxios()
  .get("/auth/kakao/callback" + location.search)
  .then(({ data }) => {
    // 토큰 받아서 로그인 처리
    login({
      token: data.access_token,
    });
  })
  .catch(({ response }) => {
    console.error(response);
    // 에러 처리
  });

결과

카카오 로그인

코드

https://github.com/joonas-yoon/fastapi-react-oauth2/tree/signin-with-kakao

 

GitHub - joonas-yoon/fastapi-react-oauth2: FastAPI + MongoDB with Beanie + React Login Example

FastAPI + MongoDB with Beanie + React Login Example - GitHub - joonas-yoon/fastapi-react-oauth2: FastAPI + MongoDB with Beanie + React Login Example

github.com

 

Comments