๋ก๊ทธ์ธ ๋ณด์์ ์ํด์ jwt๋ก ํ ํฐ์ ๋ง๋ค์ด ์ฌ์ฉ์ ๊ถํ์ ์ธ์ฆํ๋ ์์คํ ์ ๋ง๋ค์์ต๋๋ค. accessToken ํ๋๋ก๋ง์ผ๋ก๋ ๋ณด์์ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ๋ฐ๋ผ์ refreshToken์ด๋ผ๋ ๊ฒ์ผ๋ก ์ด์ค๋ณด์์ ํฉ๋๋ค.
์๋ฒ์์๋ jwtํ ํฐ์ ๋ฐํํ๋๋ฐ ์ฌ์ฉ์๋ฅผ ๊ตฌ๋ถ๊ฐ๋ฅํ ์ธ์ฆ์ ๋ณด๋ฅผ ์ฝ์ ํ๊ณ ์ธ์ฆ ๋ง๋ฃ๊ธฐ๊ฐ๋ฑ์ ์ ๋ณด๊ฐ ๋ค์ด๊ฐ ํ ํฐ์ ๋ฐํํฉ๋๋ค. ์ฌ๊ธฐ์ ์ํธํ๋ฅผ ์งํํฉ๋๋ค. ํด๋ผ์ด์ธํธ์์ ๋ก๊ทธ์ธ ์์ฒญ์ ํ๋ฉด jwtํ ํฐ์ ๋๊ฐ์ง ์์ฑํ refreshToken๊ณผ accessToken์ ์ ์กํฉ๋๋ค. ์ด๋ฅผ ํด๋ผ์ด์ธํธ์์ ์ ์ฅํ ํ ์ ์ฅํ accessToken์ ๊ฐ์ง๊ณ api ์์ฒญ์ ๋ํ ๋ณด์ ์ ๋ณด๋ก ์ด์ฉํฉ๋๋ค.
accessToken์ ๋ณดํต 30๋ถ ๋จ์๋ก ๋ง๋ฃ๊ฐ ๋ฉ๋๋ค. ๋ฐ๋ผ์ ์ฌ์ฉ์๊ฐ api๋ฅผ ์์ฒญํ๋ฉด ๊ถํ๋ง๋ฃ response๋ฅผ ๋ฐ๊ฒ ๋ ์ ์ฌ๋ฐ๊ธ api๋ฅผ ๋ณด๋ด๋๋ก ํด์ผํฉ๋๋ค. ์ด๋ accessToken๊ณผ refreshToken์ ๋ณด๋ด๋ฉด ์๋ฒ์์ refreshToken์ ์ธ์ฆํ๊ณ accessToken์ ์ฌ๋ฐ๊ธํด์ค๋๋ค. ์ด๋ refreshToken์ ์ธ์ฆ๊ธฐํ์ 30์ผ ์ ๋๋ค.
refresh Token์ด ๋ง๋ฃ๋๋ฉด ์ฌ์ฉ์์๊ฒ ์ฌ๋ก๊ทธ์ธ์ ํ๋๋ก ์ ๋ํด์ผํฉ๋๋ค.
๋ฌผ๋ก ์ฌ์ฉ์๊ฐ 30์ผ ์ด๋ด์ ๋ค์ ๋ก๊ทธ์ธ์ ํ๋ค๋ฉด refreshToken๋ ์ฌ๋ฐ๊ธ์ด ๋๋ ์๊ด์์ง๋ง 30์ผ ์ด์ ๋ก๊ทธ์ธ์ ํ์ง ์์๋ค๋ฉด ์ฌ์ฉ์๋ ๋ค์ ๋ก๊ทธ์ธ์ ํด์ผํฉ๋๋ค.
๋จผ์ ์น ์ดํ๋ฆฌ์ผ์ด์ ์ ๋ณด์์ทจ์ฝ์ ๋ค์ด ์ด๋ค ๊ฒ๋ค์ด ์๋์ง ์์๋ด ์๋ค.
์น ์ดํ๋ฆฌ์ผ์ด์ ์์ ๋ณด์์ ๋ํ ์ทจ์ฝ์ ์ ํฌ๊ฒ 2๊ฐ์ง ์ ๋๋ค. XSS, CSRF
๊ณต๊ฒฉ์๊ฐ ํด๋ผ์ด์ธํธ์์ (์น๋ธ๋ผ์ฐ์ ์์) javascript ์ฝ๋๋ฅผ ์ฝ์ ํด์ ์คํํ๋ ๊ฒฝ์ฐ ์ ๋๋ค. ํด๋ผ์ด์ธํธ๋ด๋ถ์ input ์ด๋ ์น๋ธ๋ผ์ฐ์ ์ ์คํฌ๋ฆฝํธ ์คํ์ฝ๋์์ ์ฌ์ฉ์์ ์ธ์ฆ ์ฝ๋๋ฅผ ๊ฐ๋ก์ฑ ์๋ฒ์์ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ์ฌ ๊ถํ์ ์์ด๋ ๋ฐฉ๋ฒ์ ๋๋ค. ์ด๋ฌํ ๊ฒฝ์ฐ ์๋ฒ๋ ํด๋ผ์ด์ธํธ์์๋ ์ด์์ ๊ฐ์ง ํ ์ ์์ต๋๋ค. ๊ณต๊ฒฉ์๊ฐ ์ฌ์ฉ์์ ์ฝ๋์ธ์ฒ ๋์ํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
์ด๊ฒฝ์ฐ๋ ์ฌ์ฉ์์ ์ธ์ฆ์ฝ๋๋ฅผ ๊ฐ๊ณ ์๋ค๋ฉด ๋ค๋ฅธ ์ฌ์ดํธ์์๋ ์๋ฒ์ api ์ฝ์ ๋ณด๋ผ์ ์๋ค๋ ์ ์ ์
์ฉํ๊ฒ ์
๋๋ค.
๊ณต๊ฒฉ์๊ฐ ๋ค๋ฅธ ์ฌ์ดํธ์์ ์ฐ๋ฆฌ ์๋ฒ์ api๋ฅผ ์์ฒญํด์ ์๋ฒ๊ฐ ์๋ต์ ํ๋ ๊ฒฝ์ฐ ์
๋๋ค. ์์ฆ ์น ์ดํ๋ฆฌ์ผ์ด์
์์๋ ์ฌ์ค https ํต์ ์ผ๋ก ๋ณด์์ ํ๊ณ ์๋ค๋ฉด ๋๋ฌธ๊ฒฝ์ฐ์ด๊ณ ์๋ฒ์ ํด๋ผ์ด์ธํธ์์ CORS ์ด์๋ฅผ ํด๊ฒฐํ๊ณ ์๋ค๋ฉด ๋น๊ต์ ์์ ํ ๊ฒฝ์ฐ ์
๋๋ค.
์น ์ดํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค๋ ํด๋ผ์ด์ธํธ์์๋ localStorage, cookie ๋ฑ์ ์ธ์ฆ์ ๋ณด๋ฅผ ์ ์ฅํ๋๋ฐ ๊ฐ๊ฐ Next js์์ ์ด๋ค๊ฒ์ด ์ด์์๋์ง ์ด์ผ๊ธฐํด๋ณด๊ฒ ์ต๋๋ค.
ํด๋ผ์ด์ธํธ์์๋ ์ฃผ์ ์ด์๊ฐ ์ด๋์ accessToken๊ณผ refreshToken์ ์ ์ฅํ๋๋์์ต๋๋ค.
refreshToken์ ์๋ฒ์์ ์ฟ ํค๋ก httponly
, secure
์ค์ ์ ํด์ ์ ์กํด์ฃผ๊ธฐ์ ํด๋ผ์ด์ธํธ์์๋ ๋ฐ๋ก ๋ง์ง ํ์๊ฐ ์์์ต๋๋ค.
httponly
์ค์ ์ ํด๋ผ์ด์ธํธ์์ ์ฟ ํค๋ฅผ ๋ฐ๋ก ๋ง์ง์ ์๋ ์ค์ ์ ๋๋ค. ์ด๋ ์น ๋ธ๋ผ์ฐ์ ์์ ์ฟ ํค๋ฅผ ๊ฑด๋ค์ด๋ ๊ถํ์ ์ฃผ์ง ์์์ผ๋ก์ XSS ๊ณต๊ฒฉ๋ฐฉ์ด๋ฅผ ํ ์ ์์ต๋๋ค.
secure
์ค์ ์ https ํต์ ์์๋ง ์ฟ ํค๋ฅผ ๋ณด๋ผ์ ์๊ฒํ๋ ์ค์ ์ผ๋ก์ CORS ์ด์๋ฅผ ํด๊ฒฐํด์ผ๋ง ์ฌ์ฉ๊ฐ๋ฅํฉ๋๋ค. ์ด๋ CSRF ๊ณต๊ฒฉ๋ฐฉ์ด๋ฅผ ํ ์ ์์ต๋๋ค.
accessToken๋ฅผ ์ด๋์ ์ ์ฅํ๋ ์ธ๋ฐ ๊ฐ๊ฐ ์์๋ด ์๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก accessToken์ react์์ ๋ก์ปฌ ๋ณ์๋ก ์ ์ฅํ๋ค๋ฉด react์์ JSX ๋ณํ๊ณผ์ ๋๋ฌธ์ ๋ธ๋ผ์ฐ์ ์ธ๋ถ์์ ๊ด๋ จ ๋ก์ง์ ์ฝ๊ฑฐ๋ ์์ ํ ์ ์์ต๋๋ค. ๋๋ฌธ์ XSS ๊ณต๊ฒฉ๋ฐฉ์ด๊ฐ ์ด๋์ ๋ ๊ฐ๋ฅํฉ๋๋ค.
์ด๋ ๋ค์ ์๋ฒ์์ ์ ์ด์ refreshToken ์ฒ๋ผ cookie์ ๋ฃ์ด์ ๋ณด๋ด์ฃผ๋๋ ์๋ฒ์์ JSON ํ์ผ๋ก accessToken์ ๋ฐ์์ ๋ค์ ๋ธ๋ผ์ฐ์ ์์ accessToken์ ์ฟ ํค์ ๋ฃ๋๋ ์ ๋๋ค.
์ด๊ฒฝ์ฐ๋ ํด๋ผ์ด์ธํธ์์ api๋ฅผ ์๋ฒ๋ก ๋ณด๋ผ๋๋ง๋ค ์ง์์ ์ผ๋ก ์ฟ ํค์ ๊ฐ์ด ๋ฃ์ด์ ธ ๋ณด๋ด์ง๋๋ฐ api ์์ฒญํค๋๊ฐ ์ปค์ง๋ ๋จ์ ์ด ์๊ณ document.cookie ๋ก accessToken์ ์ป์ ์ ์์ด ์ธ๋ถ์์ XSS ๊ณต๊ฒฉ์ ์ทจ์ฝํด์ง๋ ๋จ์ ์ด ์์ต๋๋ค. ์ด๋จ์ ์ react ๋ด๋ถ์์ ์ธ์ฝ๋ฉํจ์ผ๋ก์ ๋ณด์ํ ์ ์์ผ๋ ๊ถ๊ทน์ ์ด์ง ์์ต๋๋ค.
next js์์ ์๋ก๊ณ ์นจ์ ํ๋ฉด accessToken์ ์ฌ ์ธ์ฆํ๋ ํํ๋ก ๊ตฌํ์ ํ๋๋ฐ ์์ด ์ฟ ํค๋ฅผ ์ฝ์๋ react-cookie
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํด์ ์ธ์ฝ๋ฉ์ํ๋๋ฐ ์ด๊ณผ์ ์์ ์ค์๊ฐ์ผ๋ก accessToken์ด ์น ์ดํ๋ฆฌ์ผ์ด์
์ ๋ฐ์๋์ง ์์ ์ด์ accessToken์ผ๋ก api๋ฅผ ๋ณด๋ด๋ ๋ฌธ์ ๊ฐ ์๊ฒผ์ต๋๋ค. ์ฆ, ์๋ก๊ณ ์นจ์ ํ๋ฉด accessToken์ ์๋ก ๋ฐ๋๋ฐ ๊ทธ์ ์ ์นํ์ด์ง์ ์ปดํฌ๋ํธ๋ค์ด ์คํ๋์๋ง์ ์คํํ๋ api๋ค์ด ๋จผ์ ์คํ๋์ด ์ด์ ์ accessToken์ผ๋ก ์ธ์ฆ๋๋ ๋ฌธ์ ์
๋๋ค.
๋๊ตฐ๋ค๋ react-cookie
๋ ๋ก์ปฌ ๋ณ์๋ก์ ๋์ํ๊ธฐ์ ์ฟ ํค๊ฐ ๋ฐ๋๊ณ ๋ ํจ์๊ฐ ์คํ๋ ๋ ์ด์ ์ ์ฟ ํค๊ฐ์ ๊ฐ์ ธ์ต๋๋ค.
const [cookies, setCookie, removeCookie] = useCookies(["accessToken"]);
const grantRefresh = async () => {
console.log("grantRefresh", cookies.accessToken);
const response = await axios
.post(
"/users/auth/refresh",
{},
{
headers: {
"Content-Type": "application/json",
Authorization: cookies.accessToken,
Accept: "application/json",
},
}
)
.catch((err) => {
console.error(err, "grant access Token Fail");
console.log(err, "grant access Token Fail");
return err.response;
});
}
๋ฐ๋ผ์ ํจ์์ ๋์์ด ์ค์๊ฐ์ผ๋ก ๋ฐ๋ accessToken์ ๋ฐ์ํ๋ ค๋ฉด ์ง์ localStorage๋ ์ฟ ํค์์ ๊ฐ์ ์ฝ์ด์ผ ํ๋๋ฐ ๋๋ค ์ธ๋ถ์์ accessToken์ ์ง์ ๋ณด๋ ์ํ์ด ์์ต๋๋ค.
ํ์ง๋ง react์์๋ ๋ณ์๋ฅผ escapeํด์ฃผ๊ณ jsx ๋ณํ๊ณผ์ ์์ html์ ๋ฌธ์์ด์ ๋ชจ๋ escape ํด์ฃผ๊ธฐ ๋๋ฌธ์ xss ๊ณต๊ฒฉ์ ๋ํ ์ํ์ฑ์ด ๋ฐฉ์ง ๋ฉ๋๋ค. ๋ฐ๋ผ์ localStorage๋ ์ฟ ํค๋ฅผ ์ฌ์ฉํ ๋ api์ accessToken์ ๋ฃ์๋ ๊ทธ๋ฆฌ๊ณ localStorage๋ ์ฟ ํค์ accessToken์ ๋ฃ์ ๋ ๊ฐ๊ฐ ๋ณตํธํ ์ํธํ ๊ณผ์ ์ crpto ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํด์ ํฉ๋๋ค. ์ฌ๊ธฐ์ ํค๊ฐ์ ๋ฃ์ํ ๋ฐ ์ด๋ react ๋๋ถ์ xss๋ก ๊ณต๊ฐ๋ ์ํ์ด ๊ฐ์๋ฉ๋๋ค.
๋ฐ๋ผ์ ์ ๋ localStorage์ accessToken์ ์ ์ฅํ๋ crypto ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๋ฃ์๋์ ๊บผ๋ผ๋ ๊ฐ๊ฐ ์ํธํ ๋ณตํธํ ๊ณผ์ ์ ๋ฃ์์ผ๋ก์ ์ธ๋ถ๊ณต๊ฐ๋ฐฉ์ง๋ฅผ ํ๋ฉฐ ์ธ์ฆ์ ๊ตฌํํฉ๋๋ค.
์ด๋ ์ํธํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ก์ cryto-js
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์์ต๋๋ค.
AES ์ํธํ ๋ฐฉ๋ฒ์ผ๋ก ์๋ฐฉํฅ ์ํธํ๋ฅผ ์ฌ์ฉํ์์ต๋๋ค.
avoidLocalStorageUndefined
ํจ์๋ next๊ฐ ์ฒ์ ๋น๋ ๋ ๋ ์ปดํฌ๋ํธ ๋ก์ปฌ๋ณ์์ ์ ์ญ๊ฐ์ฒด ๊ฐ์ด ๋ค์ด๊ฐ๋ฉด window ์ ์ญ๊ฐ์ฒด๋ฅผ ์ฝ์ง ๋ชปํ๋ ์๋ฌ๋๋ฌธ์ ๋ง๋ค์์ต๋๋ค. static page๋ก ๋ก๋ ๋ ๋ window ๊ฐ undefined
๋ก ์ฝํ๋ ์๋ฌ๋ฅผ ํผํ๊ณ ์ ๋ฃ์์ต๋๋ค.
์ฒ์์ ๋ก๊ทธ์ธ์ ํ๋ฉด login
ํจ์ -> loginSuccess
ํจ์๊ฐ ์คํ๋ฉ๋๋ค. loginSuccess
์์ ๋ก๊ทธ์ธ ์ฑ๊ณต์ ๋ฐ๋ ์ ์ ๋ฐ์ดํฐ์ ๋ก๊ทธ์ธ ๋น์์ ์๊ฐ์ localStorage์ ๋ณด์์ ์ฅ์ ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๋ชจ๋ accessToken ์ธ์ฆ์ด ํ์ํ ํจ์์์ ์๋์ ๊ฐ์ด ์ง์ ๋ก์ปฌ์คํ ๋ฆฌ์ง์์ ๊ฐ์ ๋ฐ์์ ๋ณตํธํ ํ๊ฐ์ ๋ฃ์ด ๋ณด๋
๋๋ค. ๊ทธ๋ฆฌ๊ณ 401 ์๋ฌ ํธ๋ค๋ง์ ํตํด accessToken์ด ์ธ์ฆ๋ง๋ฃ์ ์ฌ ๋ฐ๊ธ ํจ์์ธ grantRefresh
๋ฅผ ํตํด ์ฌ๋ฐ๊ธ ๋ฐ์ต๋๋ค.
const getNewAlarms = async () => {
const response = await axios
.get("/users/alarm", {
headers: {
Authorization: getSecureLocalStorage("accessToken"),
},
})
.catch((err) => {
console.log(err, "์๋ก์ด ๋ฉ์ธ์ง ๋ฐ์์ค๊ธฐ ์คํจ");
return err.response;
});
if (response.status === 401) {
grantRefresh();
return;
}
accessToken์ ๋ง๋ฃ์๊ฐ์ 30๋ถ ์ ๋๋ค. ์๋ก์ด ํ์ด์ง๋ก ์ด๋ํ ๋๋ง๋ค accessToken์ ์ฌ๋ฐ๊ธ ๋ฐ์ ํ์๋ ์์ต๋๋ค. ๋ก๊ทธ์ธ์ loginTime์ ๋ก์ปฌ์คํ ๋ฆฌ์ง์ ์ ์ฅํ๋๋ฐ ๊ทธ์ด์ ๋ ์๋ก์ดํ์ด์ง์ ๋ค์ด๊ฐ๋ฉด ๋ก๊ทธ์ธ ์๊ฐ์ผ๋ก๋ถํฐ ํ์ฌ์๊ฐ์ด 30๋ถ ์ด ๋ค๋์ด๊ฐ๋ฉด accessToken์ ์ฌ๋ฐ๊ธํ๊ธฐ ์ํจ์ ๋๋ค.
๋ฐ๋ผ์ ๋ชจ๋ ํ์ด์ง์ ๊ฐ์ฅ ์์ ์ปดํฌ๋ํธ์ ์ ์ฝ๋๋ฅผ ๋ฃ์์ผ๋ก์ ์๋ก์ด ํ์ด์ง ์ง์ ์๋ง๋ค ์ฌ์ธ์ฆ ๋จ์ ์๊ฐ์ ๊ณ์ฐํ์ฌ ์๊ฐ์ด ์ฌ์ธ์ฆ ์๊ฐ์ด ๋๋ฉด ์ฌ์ธ์ฆ ํจ์๋ฅผ ์คํํฉ๋๋ค.