우리는 프론트엔드에서 인가코드와 로그인 토큰을 발급받은 후, 백엔드에 전송해주면 백엔드에서 해당 토큰으로 유저정보를 조회해 JWT토큰을 생성해 프론트에게 넘겨주기로 했다. 이 토큰은 프론트에서 로그인 여부를 확인해 페이지를 리다이렉트하거나 로그아웃, 혹은 백엔드로부터 인증이 필요한 요청을 할 때 사용한다.
인증/인가 과정과 JWT토큰에 대해서는 지난 포스팅에서 정리해두었다.
왜 이 방식을 사용했을까?
팝업창을 이용한 방법이 더 쉽겠지만, REST API에 대해 공부하면서 조금이라도 더 사용해보고 싶었다.
공식문서를 읽고 빠르게 적용하는 것 또한 개발자로서 중요한 능력이라고 생각했기 때문이기도 했다.
인가 코드 요청의 응답은 redirect_uri로 HTTP 302 Redirect되며, Location에 인가 코드가 담긴 쿼리 스트링(Query String) 또는 에러 메시지를 포함합니다. 사용자가 [취소] 버튼을 클릭한 경우에는 에러 메시지를 담은 쿼리 스트링이 redirect_uri로 전송됩니다.
서비스 서버는 redirect_uri로 받은 요청을 처리해 인가 코드를 얻거나 상황에 맞는 페이지를 보여주도록 처리해야 합니다. 받은 인가 코드는 토큰 받기에 사용합니다.
카카오 디벨로퍼스 사이트에서 등록한 redirect URI 로 리다이렉트 시키면, 해당 URI에서 code 파라미터로 인가코드를 보내준다.
GET /oauth/authorize?client_id={REST_API_KEY}
&redirect_uri={REDIRECT_URI}&response_type=code HTTP/1.1
Host: kauth.kakao.com
const REDIRECT_URI = 'http://localhost:3000/signin_kakao';
const KAKAO_AUTH_URL =`
https://kauth.kakao.com/oauth/authorize
?client_id=${API_KEY}
&redirect_uri=${REDIRECT_URI}&response_type=code
`;
리다이렉트된 페이지의 url창에 다음과 같이 인가코드가 뒤에 code파라미터 값으로 발급된 것을 확인할 수 있다.
http://localhost:3000/users/signin-kakao?code=
이것을 useSearchParams로 인가코드만 받아와 카카오로그인 토큰을 발급받는데 사용하였다.
공식문서에 작성된 내용은 다음과 같다.
POST /oauth/token HTTP/1.1
Host: kauth.kakao.com
Content-type: application/x-www-form-urlencoded;charset=utf-8
curl -v -X POST "https://kauth.kakao.com/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "client_id={REST_API_KEY}" \
--data-urlencode "redirect_uri={REDIRECT_URI}" \
-d "code={AUTHORIZE_CODE}"
주의할 점은! content-type이 application/x-www-form-urlencoded라는 것이다. 따라서 body에 전달하는 값은 쿼리스트링 형태여야한다.
(이 부분을 잘 몰라서 해결하는데 시간이 걸렸다.)
const [searchParams, setSearchParams] = useSearchParams();
const API_KEY = process.env.REACT_APP_KAKAO_API_KEY;
const CLIENT_SECRET = process.env.REACT_APP_CLIENT_SECRET;
const getKakaoToken = () => {
fetch('https://kauth.kakao.com/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
},
body: `grant_type=authorization_code&client_id=${API_KEY}&${searchParams}&client_secret=${CLIENT_SECRET}`,
})
.then(res => res.json())
.then(res => getLoginToken(res.access_token));
};
인증/인가 방식을 포함한 JWT토큰에 대해서 대해서는 별도 포스팅으로 정리해두었다.
const getLoginToken = kakaoToken => {
fetch(
`url`,
{
method: 'POST',
headers: {
Authorization: kakaoToken,
},
}
)
.then(res => res.json())
.then(res => saveLoginToken(res.token));
goToMain();
};
백엔드에서 받은 JWT 토큰을 세션스토리지에 저장해 이후에도 다른 페이지에서 로그인 정보가 필요할 경우 사용할 수 있도록 하고, 메인 페이지로 이동시켰다.
const getLoginToken = kakaoToken => {
fetch(
`url`,
{
method: 'POST',
headers: {
Authorization: kakaoToken,
},
}
)
.then(res => res.json())
.then(res => saveLoginToken(res.token));
goToMain();
};
const saveLoginToken = loginToken => {
sessionStorage.setItem('loginToken', loginToken);
};
const goToMain = () => {
navigate('/');
};
return null;
}
export default KakaoLogin;
로그인이 반드시 성공할 수는 없기 때문에 성공할 경우에만 세션스토리지 토큰 저장 및 메인페이지 이동 액션을 취하고, 에러일 경우에는 별도의 처리가 필요하다. 이 부분은 시간관계상 마무리하지 못해 리팩토링 기간동안 다시 완성할 예정이다.