Joonas' Note
[FastAPI + React] 소셜 로그인 구현하기 - 커스텀 로그인 (feat. 카카오) 본문
아래의 글을 먼저 읽고 진행하시는 것을 추천한다.
들어가기 전에
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
'개발' 카테고리의 다른 글
SOLID 원칙 - Single Responsibility Principle (SRP; 단일 책임 원칙) (0) | 2023.05.16 |
---|---|
Android Studio 깨끗하게 정리하기 (0) | 2023.01.29 |
[FastAPI + React] 소셜 로그인 구현하기 - 구글 로그인 (0) | 2022.09.18 |
[FastAPI + React] 소셜 로그인 구현하기 - 이메일 로그인 (2) | 2022.09.16 |
[FastAPI + React] 소셜 로그인 구현하기 - 기본 환경 구축 (0) | 2022.09.13 |