일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 오블완
- SQL 개발자
- 자바스크립트
- 톺아보기
- javascript
- 공부를 가장한 일기일지도
- K-Digital Credit
- 구름edu
- IT 지식
- 티스토리챌린지
- 노마드코더
- boj
- CodeStates
- 개발자북클럽
- 비전공자를 위한
- 최원영 저자
- 알고리즘
- SQLD
- nomadcoders
- 엘리스코딩
- js
- 제로베이스
- Do it! 시리즈
- 노마드 코더
- 자격증
- 프로그래머스
- 노개북
- 이해할 수 있는
- 백준
- 모던 자바스크립트 deep dive
- Today
- Total
개발자를 희망하는 초보의 자기개발 이야기
[카카오 로그인] 리액트로 카카오 로그인 구현하기 - 프론트엔드 (3) 본문
카카오 로그인 구현하기 - 프론트엔드 (3)
이전 글에서는 인가 코드 처리 및 스토리지를 활용한 JWT 토큰 저장을 다루었다. 하지만 스토리지에 저장하는 것은 보안상 문제가 생기기 때문에 최종적으로는 쿠키방식을 적용해야 한다. 이번 글에서는 withCredentials 설정과 HttpOnly 쿠키를 통한 로그인 유지 방식을 설명한다.
1. withCredentials: true 설정의 의미
withCredentials이란?
- withCredentials: true를 설정하면, 클라이언트와 서버 간 쿠키를 포함한 요청을 주고받을 수 있다.
- 또 백엔드 설정에 따라 헤더에 기본적으로 브라우저는 CORS 요청에서 쿠키를 전송하지 않는다. 이를 활성화하려면 프론트엔드와 백엔드 모두에서 설정해야 한다.
// withCredentials 전역 설정방법1
axios.defaults.withCredentials = true;
// withCredentials 전역 설정방법2
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
headers: {
"Content-Type": "application/json",
},
withCredentials: true, // 쿠키를 포함한 요청 허용
});
- 위와 같이 withCredentials: true를 한 줄 추가하면 된다.
- 백엔드에서 Set-Cookie 헤더를 사용해 HttpOnly 쿠키로 access_token을 저장하는 HttpOnly 쿠키 방식이 적용된다.
참고자료 : CORS로 쿠키 전송하기 (withCredentials 옵션)
2. HttpOnly 쿠키를 통한 로그인 유지
HttpOnly 쿠키란?
- HttpOnly 쿠키는 JavaScript에서 접근할 수 없는 보안 쿠키이다.
- XSS(크로스 사이트 스크립팅) 공격을 방지할 수 있으며, 로그인 토큰을 안전하게 저장할 수 있다.
HttpOnly 옵션을 사용하면, 클라이언트에서는 document.cookie를 통해 접근할 수 없다.
이를 통한 카카오 로그인 프로세스는 다음과 같다.
- 클라이언트가 카카오 로그인 API를 호출하여 authorization_code를 백엔드로 보냄.
- 백엔드가 카카오 서버에서 access_token을 받아옴.
- 백엔드가 토큰을 쿠키에 Set-Cookie로 저장 (HttpOnly, Secure 옵션 가능)
- 백엔드가 클라이언트에게 응답을 보냄.
이 단계에서 토큰이 클라이언트의 브라우저 쿠키에 저장됨.
3. 클라이언트에서 로그인 요청 및 인증 확인
로그인 요청 보내기
카카오 로그인을 성공하면, 백엔드에서 HttpOnly 쿠키에 토큰을 저장하게 된다. 프론트엔드는 이를 직접 저장하지 않고 매 요청 시 쿠키를 자동 전송한다.
export const kakaoLogin = async (code: string) => {
try {
const response = await api.post("/auth/kakao", { code });
return response.data;
} catch (error) {
console.error("로그인 실패:", error);
throw error;
}
};
- 별도로 토큰을 저장하지 않아도 자동으로 쿠키가 전송된다.
인증된 사용자 확인 요청(1) - 엔드포인트 호출이 필요할 경우
로그인 후, 사용자가 인증된 상태인지 확인할 때 /auth/me 와 같은 전용 엔드포인트를 호출한다.
export const fetchUser = async () => {
try {
const response = await api.get("/auth/me");
return response.data;
} catch (error) {
console.error("인증 확인 실패:", error);
return null;
}
};
요청을 보낼 때 자동으로 쿠키가 포함되며, 백엔드는 이를 확인하여 인증된 사용자 정보를 반환한다.
인증된 사용자 확인 요청(2) - 모든 API 요청 시 자동으로 쿠키를 검증하는 구조
백엔드에서 검증하는 구조라면, 별도의 엔드포인트가 필요하지 않고, 이미 존재하는 /user/info 같은 일반적인 API를 호출해도 로그인 여부를 확인할 수 있다.
export const fetchUser = async () => {
try {
const response = await api.get("/user/info");
return response.data;
} catch (error) {
console.error("인증 확인 실패:", error);
return null;
}
};
4. Axios 인터셉터 설정 (토큰 자동 갱신)
JWT 토큰이 만료되었을 경우, 자동으로 /auth/refresh-token을 호출하여 새 토큰을 받아온다.
import axios from "axios";
import { logout } from "@/services/authService";
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
withCredentials: true,
});
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
await api.post("/auth/refresh-token"); // 토큰 갱신 요청
return api(originalRequest); // 원래 요청 다시 실행
} catch (refreshError) {
console.error("토큰 갱신 실패, 로그아웃 처리:", refreshError);
logout(); // 로그아웃 처리
window.location.href = "/main";
}
}
return Promise.reject(error);
}
);
export default api;
axios 인터셉터를 통해 토큰이 만료되었을 경우 자동으로 갱신을 시도하고, 실패 시 로그아웃을 수행할 수 있다.
5. Zustand 로그인 여부 감지 Store
zustand에서는 토큰 자체를 관리하는게 아니라 로그인 여부를 관리한다.
"use client";
import { create } from "zustand";
import { persist, createJSONStorage } from "zustand/middleware";
import { fetchUser } from "@/services/authService";
interface AuthState {
isAuthenticated: boolean;
checkAuth: () => Promise<void>;
logout: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
isAuthenticated: false,
// 로그인 여부 확인 (API 요청)
checkAuth: async () => {
try {
const user = await fetchUser();
set({ isAuthenticated: !!user }); // user 정보가 있으면 true, 없으면 false
} catch (error) {
set({ isAuthenticated: false });
}
},
// 로그아웃 처리
logout: () => {
set({ isAuthenticated: false });
},
}),
{
name: "auth-store", // zustand 상태 저장 (세션스토리지 사용)
storage: createJSONStorage(() => sessionStorage),
}
)
);
6. 로그아웃 구현 (HttpOnly 쿠키 삭제)
로그아웃할 때는 쿠키를 삭제해야 한다.
export const logout = async () => {
try {
await api.post("/auth/logout");
window.location.href = "/main";
} catch (error) {
console.error("로그아웃 실패:", error);
}
};
백엔드에서 로그아웃 요청시 쿠키를 삭제하도록 해놓았다면 로그아웃 api 호출만으로 인증 상태가 자동으로 해제된다.
7. 로그인 상태에 따른 UI 변경
로그인 여부에 따라 로그인 버튼과 사용자 정보를 다르게 표시한다.
"use client";
import { useEffect } from "react";
import { useAuthStore } from "@/store/authStore";
import KakaoLoginButton from "@/components/KakaoLoginButton";
import { logout } from "@/services/authService";
export default function MainPage() {
const { isAuthenticated, checkAuth } = useAuthStore();
useEffect(() => {
checkAuth(); // 로그인 여부 확인
}, []);
return (
<div className="w-full flex justify-center items-center">
{isAuthenticated ? (
<div>
<p>로그인 완료!</p>
<button onClick={logout}>로그아웃</button>
</div>
) : (
<KakaoLoginButton />
)}
</div>
);
}
로그인한 경우 사용자 정보를 표시하고, 로그아웃 버튼을 노출한다.
최종 정리
- withCredentials: true를 설정하면 쿠키 기반 인증이 가능하다.
- HttpOnly 쿠키를 사용하면 토큰이 브라우저에서 노출되지 않아 보안이 강화된다.
- 클라이언트는 토큰을 직접 관리할 필요 없이, 매 요청 시 자동으로 쿠키가 전송된다.
- 로그아웃 시 쿠키를 삭제하여 인증을 해제할 수 있다.
'프론트엔드(Front-end)' 카테고리의 다른 글
[카카오 로그인] 리액트로 카카오 로그인 구현하기 - 프론트엔드 (2) (0) | 2025.03.17 |
---|---|
[카카오 로그인] 리액트로 카카오 로그인 구현하기 - 프론트엔드 (1) (0) | 2025.03.06 |
[카카오 로그인] Kakao Developers 설정 (1) | 2025.03.05 |
코딩 질문 잘하는 방법 (0) | 2024.08.22 |
자주 사용하는 마크다운 문법 12가지 (0) | 2024.08.05 |