diff --git a/src/components/sns/SnsKakaoBt.jsx b/src/components/sns/SnsKakaoBt.jsx index c55edd4..03ebd60 100644 --- a/src/components/sns/SnsKakaoBt.jsx +++ b/src/components/sns/SnsKakaoBt.jsx @@ -4,8 +4,8 @@ const SnsKakaoBt = () => { const KAKAO_CLIENT_ID = import.meta.env.VITE_APP_KAKAO_CLIENTID; const REDIRECT_URI = import.meta.env.VITE_APP_KAKAO_CALLBACKURL; - const KakaoLogin = () => { - const state = issueState("kakao"); + const KakaoLogin = async () => { + const state = await issueState("kakao"); const url = "https://kauth.kakao.com/oauth/authorize?response_type=code" + `&client_id=${encodeURIComponent(KAKAO_CLIENT_ID)}` + diff --git a/src/components/sns/SnsNaverBt.jsx b/src/components/sns/SnsNaverBt.jsx index da99b99..7e54724 100644 --- a/src/components/sns/SnsNaverBt.jsx +++ b/src/components/sns/SnsNaverBt.jsx @@ -4,8 +4,8 @@ const SnsNaverBt = () => { const NAVER_CLIENT_ID = import.meta.env.VITE_APP_NAVER_CLIENTID; const REDIRECT_URI = import.meta.env.VITE_APP_NAVER_CALLBACKURL; - const NaverLogin = () => { - const state = issueState("naver"); + const NaverLogin = async () => { + const state = await issueState("naver"); const url = "https://nid.naver.com/oauth2.0/authorize?response_type=code" + `&client_id=${encodeURIComponent(NAVER_CLIENT_ID)}` + diff --git a/src/utils/oauthState.js b/src/utils/oauthState.js index 20dd77d..152440c 100644 --- a/src/utils/oauthState.js +++ b/src/utils/oauthState.js @@ -1,9 +1,16 @@ +import { SERVER_URL } from "@/config"; + const stateKey = (provider) => `oauth_state_${provider}`; -export function issueState(provider) { - const buf = new Uint8Array(16); - crypto.getRandomValues(buf); - const state = Array.from(buf, (b) => b.toString(16).padStart(2, "0")).join(""); +// 백엔드에서 state 발급 — 동일 값이 OAUTH_STATE 쿠키로도 심어진다(double-submit). +// 백엔드 콜백이 URL state 와 쿠키 state 를 대조하므로 CSRF 방어 권한은 백엔드가 가진다. +// sessionStorage 저장은 프론트 1차 검증(consumeState)용. +export async function issueState(provider) { + const resp = await fetch(`${SERVER_URL}/auth/oauth-state`, { + method: "GET", + credentials: "include", + }).then((r) => r.json()); + const state = resp.state; sessionStorage.setItem(stateKey(provider), state); return state; }