이틀동안 엄청 헤맨 카카오 소셜 로그인 구현 일지
유저 데이터(★이메일) 받아오는거까지 프론트에서 하고
서버에서 JWT 토큰 발행하는 순서 기록
전 처음부터 쭉 서버에서 진행했다가 프론트로 옮겼습니당
개발환경 : VITE, VUE3, TS / 서버 : node express, mongodb
Kakao Developers에서 앱키와 리다이렉트 URI을 변수로 저장해둔다!
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api
프론트에서 서버로 보내고
서버에서 카카오와 통신하고 응답을 반환하면 되는 듯
우선 인가코드를 get해보자!
서비스 서버가 카카오 인증 서버로 인가 코드 받기를 요청합니다.
카카오 인증 서버가 사용자에게 카카오계정 로그인을 통한 인증을 요청합니다.
클라이언트에 유효한 카카오계정 세션이 있거나, 카카오톡 인앱 브라우저에서의 요청인 경우 4단계로 넘어갑니다.
사용자가 카카오계정으로 로그인합니다.
카카오 인증 서버가 사용자에게 동의 화면을 출력하여 인가를 위한 사용자 동의를 요청합니다.
동의 화면은 서비스 애플리케이션(이하 앱)의 동의항목 설정에 따라 구성됩니다.
사용자가 필수 동의항목, 이 외 원하는 동의항목에 동의한 뒤 [동의하고 계속하기] 버튼을 누릅니다.
카카오 인증 서버는 서비스 서버의 Redirect URI로 인가 코드를 전달합니다.
로그인 버튼을 클릭하면 로그인 절차 ui를 띄우고 인가코드를 발급받는 경로이다.
redirect_uri
는 카카오 개발자에 등록한 주소를 넣으면 되는데 서버 라우터 경로이당.
<script setup lang="ts">
import LoginPng from '../../assets/images/fn/login.png'
const REDIRECT_URI = import.meta.env.VITE_REDIRECT_URI;
const REST_API_KEY = import.meta.env.VITE_REST_API_KEY;
function loginWithKakao() {
return `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}`
}
</script>
<template>
<a :href=loginWithKakao()>
<button class='loginBtn' :style="{ backgroundImage: `url(${LoginPng})` }"></button>
</a>
</template>
로그인 버튼을 클릭하면 Redirect URI로 인가 코드를 전달한다 했으니
서버 말고 우선 route후다닥 만들어서 구동해서 확인해보면
code= ~~
이 인가 코드이다.
받아오는거 확인했으니 redirect uri
를 서버로 바꿔주자
서비스 서버가 Redirect URI로 전달받은 인가 코드로 토큰 받기를 요청합니다.
카카오 인증 서버가 토큰을 발급해 서비스 서버에 전달합니다.
필수 파라미터를 포함해 POST로 요청합니다.
요청 성공 시 응답은 토큰과 토큰 정보를 포함합니다.
OpenID Connect를 사용하는 앱인 경우, 응답에 ID 토큰이 함께 포함됩니다.
토큰의 역할과 만료 시간에 대한 자세한 정보는 토큰 정보에서 확인할 수 있습니다.
액세스 토큰으로 사용자 정보 가져오기와 같은 카카오 API를 호출할 수 있습니다.
토큰 정보 보기로 액세스 토큰 유효성 검증 후, 사용자 정보 가져오기를 요청해 필요한 사용자 정보를 받아 서비스 회원 가입 및 로그인을 완료합니다.
순서를 정리하자면 서버에서
1) post
로 토큰 받기 요청 후
2) 응답으로 토큰과 토큰 정보를 받아서
3) api를 호출, 토큰 유효성 검증 후
4) 사용자 정보 가져오기를 요청하면 된다고 함
지난번에 vercel에 배포한 node express mongodb 서버에서 이어서 한다!
하나 만들고 닳도록 재사용중인 효자서버...
route에서 callback 경로의 router를 하나 만들어주고
인가코드를 get한당
예제 코드는 다음과 같다.
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}"
curl
이 뭔지 잘 모르겠으니 문서와 구글링으로 axios.post
를 해보자!
[참고링크] https://blog.naver.com/psj9102/222090449138
우선 본문을 보면 위 셋은 정해져있고 마지막 code
가 인가코드임
req.query.code
로 파라미터를 받아와서 할당해주자!!!
router.route('/oauth/callback')
.get(async (req, res) => {
res.send(req.query);
let query = req.query;
찍히는지 확인하고
네이버 psj9102님의 포스팅을 보고 코드를 작성했다.
CLIENT_ID
등등 env로 빼줌
// 액세스 토큰 받기
axios.post("https://kauth.kakao.com/oauth/token",
{
grant_type: 'authorization_code',
client_id: REST_API_KEY,
code: AuthorizationCode,
redirect_uri: REDIRECT_URI
}
, config)
.then(async response => {
const accessToken = response.data.access_token;
사용자 액세스 토큰 또는 어드민 키를 헤더(Header)에 담아 GET 또는 POST로 요청합니다. 사용자 정보 요청 REST API는 사용자 액세스 토큰을 사용하는 방법, 앱 어드민 키를 사용하는 방법 두 가지로 제공됩니다. 어드민 키는 보안에 유의하여 사용해야 하므로 서버에서 호출할 때만 사용합니다.
사용자 정보 요청 성공 시, 응답 본문은 사용자 정보를 포함한 JSON 객체를 반환합니다.
나중에 또 쓸줄알고 함수로 빼놨는데 사용자정보는 처음 로그인시에 한번만 호출하니까 안빼도 될거같긴 하다.
const userData = await GetUserKakaoData(accessToken);
요청 URL로 post
한당
await axios.post("https://kapi.kakao.com/v2/user/me", {}, {
headers: {
"Content-type": "application/x-www-form-urlencoded;charset=utf-8",
"Authorization": `Bearer ${accessToken}`
}
})
사용자 정보를 받아왔다면 이메일 데이터가 있을 것이당!
회원 목록에 해당 이메일이 없다면 회원 등록(저장)을 해줍쉬다~
mongodb에 minchoUsers
데이터베이스를 추가하고 요청 데이터(신청한거)를 기준으로 서버에 MinchoMapUser
모델을 추가했다.
서버에서 /user/list/:email
로 라우트를 만들고 get
요청으로 존재하는 회원인지 응답을 받아봅쉬다
1) 서버
// 유저 이메일 조회
router.route('/user/list/:email')
.get(async (req, res) => {
// console.log(req.params.email)
const data = await MinchoMapUser.findOne({ email: req.params.email });
res.send(data);
})
2) 프론트
const searchUserData = await axios.get(`${import.meta.env.VITE_DB_URL}/user/list/${userData.kakao_account.email}`)
if (searchUserData.data.length === 0) { // 신규회원은 저장 ㅇㅅㅇb
await axios.post(`${import.meta.env.VITE_DB_URL}/user/list`, userDataForm)
}
여기는 D님께서 JWT 토큰 개념 설명해주시고 대략적인 구현 순서도 알려주셔서 수월하게 할 수 있었다!
1) 서버
이어서 jsonwebtoken
라이브러리로 토큰발급을 해보겠습니다요
npm 문서를 보면 sign 관련 설정을 굉장히 다양하게 할 수 있는데,
https://www.npmjs.com/package/jsonwebtoken
시간 명시 정도만 필요해서 아래 예제 코드를 참고했다.
응답을 받기만하면 되긴하지만 보안을 위해 post
로 처리해줍시다
유지 시간은 1시간으로 잡았다. 회원 정보를 구워서 반환
// jwt 토큰
router.route('/jwt')
.post(async (req, res) => {
const token = jwt.sign({ data: req.body }, 'secret', { expiresIn: '1h' });
res.send(token);
})
2) 테스트
postman으로 api 테스트를 해봅쉬다
디코딩을 하기 위해
https://jwt.io/ 위 사이트에서 발급받은 코드를 넣으면 데이터가 디코딩된다!
시간도 1시간으로 잘 설정되어있구 확인했으면 프론트에서 통신하자!
2) 프론트
위에서 설정한 api에 post로 토큰을 받아 세션 스토리지에 저장해준다.
// 토큰 발급!
const jwtToken = await axios.post(`${import.meta.env.VITE_DB_URL}/jwt`, userDataForm)
sessionStorage.setItem('jwtToken', jwtToken.data);
router.push('/')
쿠키가 아니라 세션 스토리지에 저장한 이유는 D님의 추천과 창을 닫으면 휘발되기때문인데 쿠키에 저장하고 httponly, secure 처리하면 자바스크립트로 접근할 수 없다고 하니 다음엔 쿠키에 저장해보자
아래 글에서 잘 정리해주셨당
https://jhbljs92.tistory.com/entry/1-JWT-%ED%86%A0%ED%81%B0-%EC%9D%B8%EC%A6%9D%EA%B3%BC-%EC%BF%A0%ED%82%A4-%EC%84%B8%EC%85%98-%ED%86%A0%ED%81%B0
세션스토리지에 토큰이 있으면 디코딩하여 저장한 사용자 정보를 읽어와 사용했다. 루트 경로에서 vuex로 사용자 데이터를 저장했는데 마운트시에 상태변수가 자꾸 초기값으로 읽혀와서 대책을 고민중이다. 일단 디테일페이지 진입시 세션확인을 다시 하게 했는데 음으므 왜그럴까...