📜 서론
사이트의 여러가지 기본적인 기능들을 구현하던 중 반드시 거쳐가야할 로그인 적용 단계에 이르렀다. 카카오 로그인 공식 문서와 실제 적용 사례들을 찾아보며 카카오 로그인을 구현했고, 그 과정에서 맞닥뜨린 이슈들을 어떻게 해결했는지 설명하고자 한다.
📩 카카오 로그인 요청
📌 CORS 에러 발생
카카오 로그인을 하려면 먼저 사용자의 동의를 받는 인가 화면으로 넘어가야 한다. 그러기 위해서는 인가 코드를 카카오로부터 받아야 하는데, 맨 처음에 클라이언트단에서 axios를 통해 인가 코드 발급 요청을 보내니 로컬 환경에서 CORS 에러가 발생하는 문제가 발생했다.
찾아보니 해당 요청은 CORS가 열려있지 않아 ajax 방식으로 요청하면 안되고, 대신 a
태그와 같은 방식으로 해당 uri로 직접 이동하거나, REST API 대신 JS SDK를 써야한다고 한다. 그래서 window.location.href
를 통해 해당 uri로 직접 이동하도록 했다. 참고로 이렇게 하면 REST API 키가 주소창이 노출되는 문제가 발생하는데, 보안상 딱히 문제가 없다고 한다. 절대 유출되면 안되는 건 어드민 키이다.
📌 Vite와 환경변수
이 작업을 하며 프론트 단에서 env
파일을 생성해 카카오 REST API 키와 Redirect URI를 환경 변수로 설정하도록 했다. 이렇게 설정한 환경 변수를 가져오는 작업 중 환경 변수값이 제대로 가져와지지 않는 문제가 발생했다. 공식문서를 찾아보니 Vite를 사용할 때는 환경변수명 앞에 접두사 VITE_
를 붙여야 한다고 한다. 의도치 않게 중요한 사실을 알게 되었다.
어쨌든 카카오 로그인 사용자 동의를 거쳐 등록된 Redirect URI로 이동하면서 인가 코드가 URL 상에서 쿼리 파라미터로 주어진다. Redirect URI로 이동 시 Authorization
컴포넌트로 이동하도록 라우팅 설정을 했고, 여기서 이 인가 코드를 처리하도록 했다.
📌 accessToken, refreshToken 가져오기
이제 인가 코드로 토큰을 가져와야 한다. 공식 문서를 참고해 어떤 형식으로 요청을 해야하는지 살펴보며 구현했다.
추가로, 카카오 로그인 서비스에서 제공하는 ID 토큰에 어떤 데이터들이 담겨있는지 궁금해서 가져와 디코딩하여 살펴보았다(jwt-decode
라이브러리를 통해 디코딩하는 것이 편하다). 하지만 기본적으로 가져오는 사용자 정보만으로 충분하다고 판단해 최종적으로 ID 토큰은 사용하지 않기로 했다.
참고로 카카오 로그인 서비스에서 제공하는 accessToken, refreshToken은 JWT가 아니다.
🍪 토큰 쿠키에 저장하기
📌 쿠키에 보안 설정 적용하기
카카오 서버에서 받아온 토큰들은 보안적인 문제 때문에 localStorage 같은 곳에 함부로 저장해서는 안된다. 이 때문에 보안 조치를 어떻게 취할지를 찾아보다가 브라우저의 쿠키를 활용하는 방법에 대한 자료를 발견했고, 이걸 참고했다.
다음 과정을 거치면 된다.
- 클라이언트 측에서 인가 코드를 받아 서버에 로그인 요청 보냄
- 서버에서 인가 코드로 카카오에서 토큰들 얻어오기
- 서버에서 토큰들에 이런 저런 보안 설정을 적용 후 쿠키에 저장하라고 헤더에 담아 응답 보내기. 이때 적용되는 보안 설정들은 다음과 같다.
- httpOnly: 자바스크립트로 해당 쿠키에 접근할 수 없게 하여 XSS(Cross-site Scripting) 공격을 방지
- secure: HTTPS 연결을 통해서만 쿠키가 전송되도록 하여 네트워크 상에서 쿠키가 가로채지는 것을 방지
- sameSite: 크로스 사이트, 즉 다른 도메인에서 요청 시 전송되지 않도록 하여 CSRF(Cross-Site Request Forgery) 공격을 방지
Express.js의 res.cookie
메서드를 통해 이를 구현했다. 참고로, res.cookie
메서드에서 maxAge
의 값을 설정할 시 밀리초 단위로 설정된다고 한다. 문제는 카카오에서 제공하는 각 토큰의 만료 시간은 초 단위였다. 이 점을 놓치는 바람에 처음 적용했을 때 토큰들이 이상하게 빠르게 만료되는 현상이 발생했고, 카카오에서 받아오는 만료 시간에 1000을 곱해 쿠키에 저장하도록 했다.
최종적으로 다음과 같은 코드를 통해 쿠키에 토큰들이 설정되도록 했다.
// 코드 생략
res.cookie("accessToken", access_token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production", // 로컬 환경에 https가 적용되어 있지 않아 이렇게 적용
sameSite: "Strict",
maxAge: expires_in * 1000, // 엑세스 토큰 유효 기간
});
// ...
쿠키에 보안 설정들을 적용하면 사용자 측에서 다음과 같은 행동들을 하지 않는 이상 쿠키에 저장한 토큰들이 유출될 일은 없다고 보면 된다.
- 공공장소의 기기에서 서비스 로그인 후 로그아웃하지 않고 그냥 가버리기
- 알 수 없는 이유로 사용자가 직접 브라우저의 개발자 도구를 열어 토큰 값을 다른 사람에게 제공
사실 이 토큰들이 유출될 경우 직접적으로 피해를 보는 건 사용자 밖에 없다. 적어도 그 피해의 책임이 서비스 제공자에게 있으면 안되기에 이렇게 조치를 잘 취해야 한다.
📌 서버에서 쿠키 읽을 수 있도록 설정하기
서버에서 쿠키를 읽을 수 있도록 아래와 같이 설정해주었다. 현재 클라이언트와 서버의 포트가 다르기 때문에 서버와 클라이언트 간의 요청은 서로 다른 도메인(Cross-origin)으로부터의 요청이라고 할 수 있다. 따라서 아래와 같이 수동으로 이를 허용한다는 옵션을 설정해주어야 한다. 클라이언트 측의 api 요청의 경우, 사용자 인증이 필요한 요청에만 서버가 쿠키를 읽을 수 있도록 withCredentials
옵션을 넣어주면 된다.
단순히 port 번호가 다르기 때문에 Same Origin이 아닌 것 뿐이지, 여전히 Same Site인 것은 변하지 않는다. 그렇기 때문에 아래 설정만 하면 위에서 sameSite = "Strict"로 설정해 쿠키에 저장한 토큰을 서버에서 읽어올 수 있다. 더 자세한 설명이 필요하다면 이 자료를 참고하자.
// 서버
app.use(
cors({
origin: "http://localhost:5173",
credentials: true,
})
);
// 클라이언트
const response = await axios.post(
`${API_URL}/login/kakao`,
{ code: code },
{ withCredentials: true }
);
이제 저장된 토큰을 사용해 카카오로부터 사용자 정보를 가져오거나, 역으로 카카오 서버에 저장을 할 수 있다.
❓ 근데 왜 accessToken과 refreshToken은 같이 써야할까?
카카오 로그인 서비스에서 accessToken은 정보를 가져오고 저장하는 데에 쓰인다. refreshToken은 이런 accessToken을 갱신하는 데 쓰인다. 이는 다른 서비스의 경우에도 마찬가지다.
그렇다면 이런 의문이 들 수 있다.
accessToken 갱신이 필요하면 그냥 로그인 할 때 쓰이는 요청을 써서 갱신하면 되지 않나?
결론부터 말하자면 사용자 경험을 위해 이 둘을 함께 쓴다고 볼 수 있다. 생각해봐라. 사용자가 직접 로그아웃하지도 않았는데 accessToken이 만료될 때마다 사용자가 로그인 과정을 계속 거쳐야 한다면 얼마나 짜증나겠는가?
따라서 accessToken이 만료되면 그걸 비교적 쉽게 갱신해줄 수 있는 refreshToken을 통해 갱신해주도록 하는 것이다. 물론 refreshToken까지 만료되면 사용자는 다시 로그인해야 된다.
참고로 카카오 로그인의 refreshToken은 accessToken을 갱신할 때마다 만료 기간을 체크해 한달 미만일 때 알아서 갱신된다.
따라서 지금 이 프로젝트에서 refreshToken이 만료된 경우는 크게 생각 안해도 되지만, 어떤 멍청한 사용자가 굳이 쿠키를 뒤져서 refreshToken을 삭제하는 상황이 발생할 수 있으니 이 경우를 위한 코드를 만들어 놓는 것도 괜찮을 듯 하다.
🏁 마무리
이렇게 해서 카카오 서버에서 토큰들을 가져오는 단계까지 마쳤다. 하지만 아직 끝난게 아니다. 만약 api 요청을 했는데 accessToken이 만료되어 사라진 상태라면 어떻게 해야할까? 다음 포스팅에서는 그 해결 방법을 다룰 예정이다.
'프로젝트 > 올평' 카테고리의 다른 글
[올평] 6. 카카오 로그인 적용기 - redux-toolkit으로 사용자 정보 관리하기 & 로그아웃 (1) | 2024.09.04 |
---|---|
[올평] 5. 카카오 로그인 적용기 - Axios 인터셉터로 토큰 자동 갱신 구현하기 (0) | 2024.09.03 |
[올평] 3. 깃허브 unverified 이슈 해결 과정 (0) | 2024.05.10 |
[올평] 2. 프로젝트 진행 방식 정립 (0) | 2024.05.10 |
[올평] 1. 주요 기술 스택 선정 및 깃허브 레포 생성 (0) | 2024.05.09 |