티스토리 뷰
4. 어려웠던 점
4-1. 카카오 로그인
처음에는 카카오 로그인과 네이버 로그인 중 하나를 고르려고 했었다. 그런데 팀원들과 회의를 하면서 카카오페이 결제를 구현하기로 했기에, 로그인도 카카오 로그인으로 하기로 했다. 카카오 로그인의 대략적인 과정은 다음과 같다.
카카오 로그인의 과정을 간단히 이야기하자면, 로그인 요청을 보낸 후 인가코드를 받는다. 그리고 인가 코드를 이용해 토큰을 발급받는다. 그리고 이 토큰을 이용해 유저 정보를 받는다.
이 과정을 위해서 우선 '내 애플리케이션' 설정을 해주어야 한다. 설정을 완료하면 각 키값들을 확인할 수 있다.
공식 문서를 보면 카카오 로그인을 구현하는 여러 방법이 있다는 것을 알 수 있다. 본 프로젝트에서는 REST API를 이용하여 구현하기로 하였다. 첫 번째로는 인가 코드를 받아온다. (https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#kakaologin) 인가 코드를 받기 위해서는 REST_API_KEY와 REDIRECT_URI가 필요하다. 이 부분은 프로젝트를 진행하면서 코드를 지워버려 남아있지 않다.
그리고 인가 코드를 이용해 토큰을 받는다. (https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#request-token) 만약 요청이 성공한다면 액세스 토큰, 리프레시 토큰 등 여러 정보를 줄 것이다. 백엔드의 API 명세서가 완성되기 전, 개인적으로 서버 코드를 작성하여 테스트를 했었는데, 이 때 작성했던 서버 코드는 다음과 같다.
const { KAKAO_CODE } = req.body;
const result = await fetch("https://kauth.kakao.com/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `grant_type=authorization_code&client_id=${process.env.REST_API_KEY}&redirect_uri=${process.env.REDIRECT_URI}&code=${KAKAO_CODE}`,
});
const data = await result.json();
// data의 형태는 아래와 같다.
{
"token_type":"bearer",
"access_token":"${ACCESS_TOKEN}",
"expires_in":43199,
"refresh_token":"${REFRESH_TOKEN}",
"refresh_token_expires_in":5184000,
"scope":"account_email profile"
}
하지만 토큰으로는 아무 것도 할 수 없다. 토큰을 이용해 정보를 받아와야 한다. (https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info) 이용자 정보를 받아오기 위해서는 액세스 토큰이 필요하다. 개인적으로 이를 구현했던 코드는 다음과 같다.
try {
const userResponse = await axios.get("https://kapi.kakao.com/v2/user/me", {
headers,
});
const kakaoUserData = userResponse.data;
// 이하 생략
} catch (error) {
return res.status(401).send("Not Authorized");
}
우려했던 것보다는 코드가 간단했다. 카카오 로그인 부분에서 토큰을 받고 유저 정보를 받아오는 부분까지의 전체 코드는 다음과 같다.
export const kakaoLogin = async (req, res) => {
// 1. 토큰 받아오기
const { KAKAO_CODE } = req.body;
const result = await fetch("https://kauth.kakao.com/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `grant_type=authorization_code&client_id=${process.env.REST_API_KEY}&redirect_uri=${process.env.REDIRECT_URI}&code=${KAKAO_CODE}`,
});
const data = await result.json();
// 2. 유저 정보 받아오기
const { access_token } = data;
const headers = {
Authorization: `Bearer ${access_token}`,
};
try {
const userResponse = await axios.get("https://kapi.kakao.com/v2/user/me", {
headers,
});
const kakaoUserData = userResponse.data;
// 유저 정보가 이미 있는지 확인한다.
let userInfo = dummyAccounts.find(
(account) =>
account.kakao &&
account.kakao.kakao_account.profile.nickname ===
kakaoUserData.kakao_account.profile.nickname
);
if (userInfo === undefined) {
// 해당 (카카오) 계정이 없는 계정일 경우
// 회원가입을 한 후 진행한다.
const newAccount = {
id: dummyAccounts.length + "",
userId: kakaoUserData.id + "",
password: "",
name: kakaoUserData.kakao_account.profile.nickname,
callNumber: "",
birthDate: kakaoUserData.kakao_account.birthday,
email: kakaoUserData.kakao_account.email,
seller: false,
kakao: kakaoUserData,
};
// 유저 정보가 저장되어 있는 배열에 push한다.
dummyAccounts.push(newAccount);
userInfo = newAccount;
}
const { accessToken, refreshToken } = generateToken(userInfo, true);
res.cookie("refresh_jwt", refreshToken, refreshCookieOption);
res.cookie("access_jwt", accessToken, cookieOption);
res.redirect("userInfo");
} catch (error) {
return res.status(401).send("Not Authorized");
}
};
이 코드들을 보여주며 백엔드 분들과 회의를 했었다. 처음에는 백엔드 쪽에서 배포하고 이용하는 서버의 url로 이동을 하면 response의 헤더에 토큰을 담아 주기로 하였다. 그런데 지금까지 해왔던 것은 fetch나 axios를 이용해 request를 보내면 response를 받는 형식만 진행했었기에, 백엔드에서 알려준 것이 실현 가능한지 조사해보았다. 하지만 아무리 찾아 보아도 이에 대한 방법을 찾을 수 없었다. 그래서 계속 머리를 맞댄 결과, 위에서 작성한 코드를 응용하여 백엔드에서 대부분 처리한 후 나온 액세스 토큰과 리프레시 토큰을 우리의 uri에 담아서 보내주기로 하였다. 예를 들어, 로컬에서 카카오 로그인을 진행한다면 'localhost:4000/oauth2/~...accessToken=~'와 같이 보내주었다. 그래서 이를 통해 유저 정보를 확인하고 카카오 로그인을 진행할 수 있었다. 이 부분에 대한 코드는 다음과 같다.
export default function KakaoLogin() {
const location = useLocation();
const navigate = useNavigate();
const dispatch = useDispatch();
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
(async () => {
const searchParams = new URLSearchParams(location.search);
const accessToken = searchParams.get("accessToken");
const refreshToken = searchParams.get("refreshToken");
const validTokens = accessToken.split(".");
const encoded = validTokens[1];
const sanitizedString = encoded.replace(/-/g, "+").replace(/_/g, "/");
const decoded = decodeURIComponent(
Array.prototype.map
.call(atob(sanitizedString), function (c) {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
})
.join("")
);
const decodedObject = JSON.parse(decoded);
const kakaoUser = {
...decodedObject,
accessToken: "Bearer " + accessToken,
};
dispatch(handleLogin(kakaoUser));
navigate("/");
})();
}, []);
return (
<Wrapper>
<Spinner />
</Wrapper>
);
}
그리고 만약 일반 계정이 있는데 카카오 로그인을 실행하는 경우, 즉 카카오 계정의 이메일과 동일한 이메일의 계정이 있는 경우, 백엔드 쪽에서 /login?error로 자동으로 리턴하게 해주었다. 그래서 일반 계정과 카카오 계정이 중복되는 경우는 다음과 같이 처리하였다.
const location = useLocation();
useEffect(() => {
if (userState.login) {
navigate("/");
}
if (location.search.includes("error")) {
toast("이미 존재하는 계정입니다.");
navigate("/login");
}
}, []);
4-2. 코드 정리
처음에 파일 이름을 짓는 법 같이 프로젝트를 진행하며 지켜야 할 규칙들을 자세히 정하지 않았다. 그리고 리액트의 장점 중 하나가 컴포넌트를 만들어 이를 활용할 수 있다는 것인데, 이에 대해서도 이야기를 거의 하지 않았다. 그래서 다음과 같은 참사가 일어났다.
파일이 너무 쓸데없이 많고 난잡하다. 이것은 처음부터 규칙을 정하지 않았기 때문이라 생각한다. 프로젝트 후반부에 들어서 알게 된 사실이 있다. 원래 프로젝트 시작 전에 이런 것들에 대한 규칙을 정하는데 하루를 통으로 이용한다는 것이다. 우리는 너무 안일하게 생각했었다. 이는 프론트엔드 팀장으로서 잘못이 크다고 생각한다. 그래서 날잡고 팀원들이랑 코드 리팩토링을 하기로 했다. 다음에는 이런 일이 없게 주의해야 할 것이다.
5. 느낀 점 및 후기
기획부터 제작까지, 아예 처음부터 시작하는 프로젝트는 이번이 처음이었다. 저번 프리 프로젝트는 이미 기획은 되어 있는 상황이었기에 제작만 하면 됐었다. 그래서 프로젝트를 너무 안일하게 생각했던 것 같다. 게다가 이번 프로젝트는 팀장으로서 신경쓰고 조율할 것이 많았다. 그래서 나름 열심히 했지만, 정말 아쉬움이 남는다. 특히, 4번에서도 언급했지만 파일들이 깔끔하지 못한 것이 너무 아쉽다. 그래도 다음에는 이런 아쉬움을 거름삼아 더더욱 성장할 것이다.
그리고 이번 프로젝트에는 카카오맵과 카카오페이, 날짜 선택 기능이 있었는데 이 부분은 내가 담당하지 않은 부분이다. 그래서 이 세 가지는 개인적으로 꼭 구현해 볼 것이다. 그리고 알고리즘 문제도 열심히 풀 것이다.
참고
- 카카오로그인 REST API
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api
- 내 애플리케이션
'코드스테이츠 부트캠프' 카테고리의 다른 글
Main Project를 마치고 - 1 (0) | 2023.05.28 |
---|---|
Main Project ~ 05/07 (0) | 2023.05.04 |
preproject ~ 04/27 (0) | 2023.04.27 |
Cloud Computing, AWS (0) | 2023.03.31 |
Lighthouse (0) | 2023.03.30 |
- Total
- Today
- Yesterday
- 타입스크립트
- 햄버거버튼
- Next.js
- 순열
- 백준
- 비트마스킹
- BFS
- 구현
- react
- Redux
- 브루트포스
- typescript
- 리액트
- 카카오맵
- themoviedb
- C++
- 코드스테이츠
- 자바스크립트
- 알고리즘
- 프로그래머스
- NextJS
- async
- CSS
- 넥스트js
- 동적계획법
- 완전탐색
- 스택
- 다이나믹프로그래밍
- SQL
- aws
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |