From 0d503f0ef26c9bbc80d581fa45ef18a6e7db60ab Mon Sep 17 00:00:00 2001 From: egovframesupport Date: Fri, 26 Jun 2026 15:17:40 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20OAuth=20state=20=EB=B0=B1=EC=97=94?= =?UTF-8?q?=EB=93=9C=20=EB=B0=9C=EA=B8=89(double-submit=20=EC=BF=A0?= =?UTF-8?q?=ED=82=A4)=EC=9C=BC=EB=A1=9C=20CSRF=20=EB=B0=A9=EC=96=B4=20?= =?UTF-8?q?=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/sns/SnsKakaoBt.jsx | 4 ++-- src/components/sns/SnsNaverBt.jsx | 4 ++-- src/utils/oauthState.js | 15 +++++++++++---- 3 files changed, 15 insertions(+), 8 deletions(-) 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; }