JWT์™€ ํ† ํฐ ๊ด€๋ฆฌ ๋ฐฉ๋ฒ•

JWT(JSON Web Token)๋Š” JSON ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋ฐ”์ผ์ด๋‚˜ ์›น์˜ ์‚ฌ์šฉ์ž ์ธ์ฆ์„ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋ฉฐ ์ •๋ณด๋ฅผ ์•ˆ์ „์„ฑ ์žˆ๊ฒŒ ์•”ํ˜ธํ™”ํ•œ ํ† ํฐ์„ ์˜๋ฏธํ•œ๋‹ค.

๐Ÿ“ Access Token

์•ก์„ธ์Šค ํ† ํฐ์€ ์ฃผ๋กœ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณดํ˜ธ๋œ ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๋ฌธ์ž์—ด์ž…๋‹ˆ๋‹ค. ์ฃผ๋กœ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜, ๋ชจ๋ฐ”์ผ ์•ฑ ๋“ฑ์—์„œ ์‚ฌ์šฉ์ž ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ์— ์“ฐ์ž…๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ์ธ์ฆ ํ”„๋กœํ† ์ฝœ์—์„œ ์‚ฌ์šฉ๋˜๋ฉฐ, ๋Œ€ํ‘œ์ ์œผ๋กœ OAuth 2.0์—์„œ ์ •์˜๋˜์–ด ์žˆ์œผ๋ฉฐ

๊ธฐ๋ณธ์ ์œผ๋กœ Access Token์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŠน์ง•์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.
  • ์œ ํšจ ๊ธฐ๊ฐ„(Expiration) : Access Token์€ ์ผ์ • ๊ธฐ๊ฐ„ ๋™์•ˆ๋งŒ ์œ ํšจํ•˜๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ์งง์€ ์‹œ๊ฐ„(์˜ˆ: 2์‹œ๊ฐ„) ๋™์•ˆ๋งŒ ์œ ํšจํ•˜๋ฉฐ, ์ด ๊ธฐ๊ฐ„์ด ์ง€๋‚˜๋ฉด ์žฌ๋ฐœ๊ธ‰์ด๋‚˜ ์ƒˆ๋กœ์šด ์ธ์ฆ์ด ํ•„์š”ํ•˜๋‹ค.
  • ๊ถŒํ•œ ๋ฒ”์œ„(๋ฒ”์œ„) : Access Token์—๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ํŠน์ • ์ž‘์—… ๋˜๋Š” ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ๊ถŒํ•œ ๋ฒ”์œ„๊ฐ€ ์ง€์ •๋œ๋‹ค. Scope๋Š” ํ•ด๋‹น ํ† ํฐ์œผ๋กœ ์–ด๋–ค ์ž‘์—…์ด ๊ฐ€๋Šฅํ•œ์ง€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.
  • ๋ฐœ๊ธ‰์ž(Issuer) : ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•œ ์ธ์ฆ ์„œ๋ฒ„(๋ฐœ๊ธ‰์ž)์˜ ์ •๋ณด๊ฐ€ ํ† ํฐ์— ํฌํ•จ๋  ์ˆ˜ ์žˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๋Š” ์ด ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ† ํฐ์˜ ์‹ ๋ขฐ์„ฑ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์„œ๋ช…(Signature) : ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•˜๊ธฐ ์œ„ํ•ด ํ† ํฐ์€ ๋ฐœ๊ธ‰์ž์— ์˜ํ•ด ์„œ๋ช…๋œ๋‹ค. ์„œ๋ช…์„ ํ†ตํ•ด ํ† ํฐ์ด ์œ ํšจํ•˜๋ฉฐ ์กฐ์ž‘๋˜์ง€ ์•Š์•˜์Œ์„ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์‚ฌ์šฉ์ž ์‹๋ณ„ ์ •๋ณด : Access Token์—๋Š” ์‚ฌ์šฉ์ž๋ฅผ ๊ณ ์œ ํ•˜๊ฒŒ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๋Š” ์ •๋ณด(์‚ฌ์šฉ์ž ID ๋“ฑ)๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„๋Š” ํŠน์ • ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“ Refresh token

access ํ† ํฐ์ด ๋งŒ๋ฃŒ๊ฐ€ ๋์„ ๊ฒฝ์šฐ access ํ† ํฐ์„ ์žฌ๋ฐœ๊ธ‰ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ์„œ๋ฒ„์— ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋‹ค. fresh token์€ access token์„ ์žฌ๋ฐœ๊ธ‰๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” token์ด๋‹ค. ์ด token์€ ์„œ๋ฒ„์— ์ €์žฅ๋˜๊ธฐ ๋•Œ๋ฌธ์—(stateful) refresh token์ด ํ•ด์ปค์— ์˜ํ•ด ํƒˆ์ทจ๋‹นํ–ˆ๋‹ค๊ณ  ํŒ๋‹จ๋˜์—ˆ์„ ๋•Œ ์„œ๋ฒ„์—์„œ refresh token์„ ์‚ญ์ œํ•จ์œผ๋กœ์จ ๊ฐ•์ œ ๋กœ๊ทธ์•„์›ƒ์„ ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

์ด๋Ÿฐ ํŠน์ง•์„ ์ด์šฉํ•ด์„œ access token + refresh token์˜ ์กฐํ•ฉ์„ ๊ตฌ์„ฑํ•˜๋ฉด access token์˜ ๊ฒฝ์ œ์ ์ธ ์žฅ์ ๊ณผ refresh token์˜ ๋ณด์•ˆ์ ์ธ ์žฅ์ ์„ ๋‘˜ ๋‹ค ์ฑ™๊ธธ ์ˆ˜ ์žˆ๋‹ค. access token์€ ๋ณด์•ˆ ์ ์œผ๋กœ ์ทจ์•ฝํ•˜๋‹ˆ 2์‹œ๊ฐ„ ์ •๋„๋กœ ์งง๊ฒŒ ๊ฐ€์ ธ๊ฐ€๊ณ , refresh token์€ ์ฒ˜๋ฆฌ ๋น„์šฉ์ด ๋งŽ์ด ๋“ค๊ธฐ ๋•Œ๋ฌธ์— 2์ฃผ ์ •๋„๋กœ ๊ธธ๊ฒŒ ๊ฐ€์ ธ๊ฐ€๋Š” ๋ฐฉ์‹์„ ์ฃผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.
client.interceptors.request.use(
    function (config) {
        const user = localStorage.getItem('user');
        if (!user) {
            config.headers["accessToken"] = null;
            config.headers["refreshToken"] = null;
            return config
        }
        const { accessToken, refreshToken } = JSON.parse(user)
        config.headers["accessToken"] = accessToken;
        config.headers["refreshToken"] = refreshToken;
        return config
    }
)
reqeust๋ฅผ ๋ณด๋‚ผ๋•Œ localStorage์— token ์ •๋ณด๊ฐ€ ์žˆ๋‹ค๋ฉด ํ—ค๋”์— ํ† ํฐ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ์—†๋‹ค๋ฉด null๋กœ ์ฒ˜๋ฆฌ๋ฅผ ํ•œ๋‹ค.
client.interceptors.response.use(
    function (response) {
        return response
    },
    async function (error) {
      if (error.response && error.response.status === 403) {
          try {
              const originalRequest = error.config;
              const data = await client.get('auth/refreshtoken')
              if (data) {
                  const {accessToken, refreshToken} = data.data
                  localStorage.removeItem('user')
                  localStorage.setItem('user', JSON.stringify(data.data, ['accessToken', 'refreshToken']))
                  originalRequest.headers['accessToken'] = accessToken;
                  originalRequest.headers['refreshToken'] = refreshToken;
                  return await client.request(originalRequest);
                  }
          } catch (error){
              localStorage.removeItem('user');
              console.log(error);
          }
          return Promise.reject(error)
      }
      return Promise.reject(error)
    }
)
response๋ฅผ ๋ฐ›์•˜์„ ๋•Œ, error๊ฐ€ ๋ฐœ์ƒํ–ˆ๊ณ  ํ•ด๋‹น error์˜ status๊ฐ€ 403์ด๋ผ๋ฉด ๊ธฐ์กด์˜ originalRequest๋ฅผ auth/refreshtoken ์œผ๋กœ ์ „๋‹ฌํ•ด ํ† ํฐ์„ ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๋„๋ก ํ•œ๋‹ค.

์—ฌ๊ธฐ์„œ 403 ์ด์™ธ์˜ ์˜ค๋ฅ˜๊ฐ€ ๋“ค์–ด์˜จ๋‹ค๋ฉด ํ† ํฐ ์žฌ๋ฐœ๊ธ‰์— ์‹คํŒจํ•œ๊ฒƒ์œผ๋กœ ์ฒ˜๋ฆฌ๋ฅผ ํ•œ๋‹ค.

์žฌ๋ฐœ๊ธ‰ ๋ฐ›์€ ํ† ํฐ์„ ๋‹ค์‹œ ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€์— ์ €์žฅ ํ•˜๊ณ  ํ—ค๋” ๋ถ€๋ถ„์— ํ† ํฐ ์ •๋ณด๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ  ๋‹ค์‹œ originalRequest๋ฅผ ๋ณด๋‚ธ๋‹ค.
import axios from 'axios';

const client = axios.create({
    baseURL: 'http://localhost:4000/'
})

client.interceptors.request.use(
    function (config) {
        const user = localStorage.getItem('user');
        if (!user) {
            config.headers["accessToken"] = null;
            config.headers["refreshToken"] = null;
            return config
        }
        const { accessToken, refreshToken } = JSON.parse(user)
        config.headers["accessToken"] = accessToken;
        config.headers["refreshToken"] = refreshToken;
        return config
    }
)

client.interceptors.response.use(
    function (response) {
        return response
    },
    async function (error) {
      if (error.response && error.response.status === 403) {
          try {
              const originalRequest = error.config;
              const data = await client.get('auth/refreshtoken')
              if (data) {
                  const {accessToken, refreshToken} = data.data
                  localStorage.removeItem('user')
                  localStorage.setItem('user', JSON.stringify(data.data, ['accessToken', 'refreshToken']))
                  originalRequest.headers['accessToken'] = accessToken;
                  originalRequest.headers['refreshToken'] = refreshToken;
                  return await client.request(originalRequest);
                  }
          } catch (error){
              localStorage.removeItem('user');
              console.log(error);
          }
          return Promise.reject(error)
      }
      return Promise.reject(error)
    }
)

export default client;

๋ณด์•ˆ

CSRF(Cross Site Request Forgery)

CSRF๋Š” Cross-Site Request Forgery์˜ ์•ฝ์ž๋กœ, ํ•œ ์‚ฌ์ดํŠธ์—์„œ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ์„ ๊ฐ€๋กœ์ฑ„์–ด ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ์—์„œ ๊ณต๊ฒฉ์ž๊ฐ€ ์˜๋„ํ•œ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ํ•˜๋Š” ๊ณต๊ฒฉ์ด๋‹ค. ์ด ๊ณต๊ฒฉ์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ž์‹ ์˜ ์˜์ง€์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ๊ณต๊ฒฉ์ž๊ฐ€ ์˜๋„ํ•œ ์š”์ฒญ์„ ์•…์˜์ ์ธ ์›น์‚ฌ์ดํŠธ๋ฅผ ํ†ตํ•ด ์ „์†กํ•จ์œผ๋กœ์จ ์ด๋ฃจ์–ด์ง„๋‹ค.

๐Ÿ“ CSRF ์ž‘๋™ ์›๋ฆฌ

  • ์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • ์›น์‚ฌ์ดํŠธ์— ๋กœ๊ทธ์ธํ•˜์—ฌ ์ธ์ฆ์„ ๋ฐ›๋Š”๋‹ค.
  • ์ด ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค๋ฅธ ์›น์‚ฌ์ดํŠธ(๊ณต๊ฒฉ์ž์˜ ์‚ฌ์ดํŠธ)๋ฅผ ๋ฐฉ๋ฌธํ•œ๋‹ค.
  • ๊ณต๊ฒฉ์ž์˜ ์‚ฌ์ดํŠธ์—์„œ๋Š” ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ์œผ๋กœ ํŠน์ • ๋™์ž‘(์˜ˆ: ๊ธ€์“ฐ๊ธฐ, ๊ณ„์ • ๋ณ€๊ฒฝ ๋“ฑ)์„ ์š”์ฒญํ•˜๋Š” HTTP ์š”์ฒญ์„ ์ƒ์„ฑํ•œ๋‹ค.
  • ์ด๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด ๊ณต๊ฒฉ์ž๋Š” ์ด๋ฏธ ๋กœ๊ทธ์ธ๋œ ์‚ฌ์šฉ์ž์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์•…์˜์ ์ธ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ๋Š”๋‹ค.

๐Ÿ“ ์˜ˆ์‹œ ์‹œ๋‚˜๋ฆฌ์˜ค

  • ์‚ฌ์šฉ์ž A๊ฐ€ ์€ํ–‰ ์›น์‚ฌ์ดํŠธ์— ๋กœ๊ทธ์ธํ•œ๋‹ค.
  • ์‚ฌ์šฉ์ž A๊ฐ€ ์•…์˜์ ์ธ ์ด๋ฉ”์ผ ๋งํฌ๋ฅผ ํด๋ฆญํ•˜์—ฌ ๊ณต๊ฒฉ์ž์˜ ์›น์‚ฌ์ดํŠธ์— ์ ‘์†ํ•œ๋‹ค.
  • ํ•ด๋‹น ์›น์‚ฌ์ดํŠธ์—์„œ๋Š” ์‚ฌ์šฉ์ž A์˜ ๊ถŒํ•œ์œผ๋กœ ์€ํ–‰ ์›น์‚ฌ์ดํŠธ์— ์ž๋™์œผ๋กœ ์ž๊ธˆ ์ด์ฒด๋ฅผ ์š”์ฒญํ•˜๋Š” HTTP ์š”์ฒญ์„ ์ƒ์„ฑํ•œ๋‹ค.
  • ์ด๋ฅผ ํ†ตํ•ด ์€ํ–‰ ์›น์‚ฌ์ดํŠธ๋Š” ์‚ฌ์šฉ์ž A์˜ ๊ถŒํ•œ์œผ๋กœ ์ด์ฒด๋ฅผ ์‹คํ–‰ํ•˜๊ฒŒ ๋œ๋‹ค.

XSS(Cross Site Scripting)

XSS๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” ์ด์œ ๋Š” CSS๊ฐ€ ์ด๋ฏธ ์•ฝ์ž๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๊ณ  code injection attack์ด๋ผ๊ณ ๋„ ํ•œ๋‹ค. XSS๋„ ๋‹ค์–‘ํ•œ ๊ณต๊ฒฉ ๋ฐฉ๋ฒ•์ด ์žˆ๋Š”๋ฐ ์šฐ์„ ์€ ๊ณต๊ฒฉ์ž๊ฐ€ ์˜๋„ํ•˜๋Š” ์•…์˜์ ์ธ js ์ฝ”๋“œ๋ฅผ ํ”ผํ•ด์ž์˜ ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰์‹œํ‚ค๋Š” ๊ฒƒ ์ •๋„๋กœ ์•Œ๊ณ  ์žˆ์œผ๋ฉด ๋œ๋‹ค. ์ด ๋ฐฉ๋ฒ•์œผ๋กœ ํ”ผํ•ด์ž ๋ธŒ๋ผ์šฐ์ €์— ์ €์žฅ๋œ ์ค‘์š” ์ •๋ณด๋“ค์„ ํƒˆ์ทจ ๊ฐ€๋Šฅํ•˜๋‹ค.

ํ•ด๊ฒฐ 1) localStorage์— ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•

๐Ÿ“ CSRF ๊ณต๊ฒฉ์—๋Š” ์•ˆ์ „ํ•˜๋‹ค.

๊ทธ ์ด์œ ๋Š” ์ž๋™์œผ๋กœ request์— ๋‹ด๊ธฐ๋Š” ์ฟ ํ‚ค์™€๋Š” ๋‹ค๋ฅด๊ฒŒ js ์ฝ”๋“œ์— ์˜ํ•ด ํ—ค๋”์— ๋‹ด๊ธฐ๋ฏ€๋กœ XSS๋ฅผ ๋šซ์ง€ ์•Š๋Š” ์ด์ƒ ๊ณต๊ฒฉ์ž๊ฐ€ ์ •์ƒ์ ์ธ ์‚ฌ์šฉ์ž์ธ ์ฒ™ request๋ฅผ ๋ณด๋‚ด๊ธฐ๊ฐ€ ์–ด๋ ต๋‹ค.

๐Ÿ“ XSS์— ์ทจ์•ฝํ•˜๋‹ค.

๊ณต๊ฒฉ์ž๊ฐ€ localStorage์— ์ ‘๊ทผํ•˜๋Š” js ์ฝ”๋“œ ํ•œ ์ค„๋งŒ ์ฃผ์ž…ํ•˜๋ฉด localStorage๋ฅผ ๊ณต๊ฒฉ์ž๊ฐ€ ๋‚ด ์ง‘์ฒ˜๋Ÿผ ๋“œ๋‚˜๋“ค ์ˆ˜ ์žˆ๋‹ค.

ํ•ด๊ฒฐ 2) cookie์— ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•

๐Ÿ“ XSS ๊ณต๊ฒฉ์œผ๋กœ๋ถ€ํ„ฐ localStorage์— ๋น„ํ•ด ์•ˆ์ „ํ•˜๋‹ค.

์ฟ ํ‚ค์˜ httpOnly ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด Js์—์„œ ์ฟ ํ‚ค์— ์ ‘๊ทผ ์ž์ฒด๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.
๊ทธ๋ž˜์„œ XSS ๊ณต๊ฒฉ์œผ๋กœ ์ฟ ํ‚ค ์ •๋ณด๋ฅผ ํƒˆ์ทจํ•  ์ˆ˜ ์—†๋‹ค.(httpOnly ์˜ต์…˜์€ ์„œ๋ฒ„์—์„œ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Œ) ํ•˜์ง€๋งŒ XSS ๊ณต๊ฒฉ์œผ๋กœ๋ถ€ํ„ฐ ์™„์ „ํžˆ ์•ˆ์ „ํ•œ ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค.
httpOnly ์˜ต์…˜์œผ๋กœ ์ฟ ํ‚ค์˜ ๋‚ด์šฉ์„ ๋ณผ ์ˆ˜ ์—†๋‹ค ํ•ด๋„ js๋กœ request๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ž๋™์œผ๋กœ request์— ์‹ค๋ฆฌ๋Š” ์ฟ ํ‚ค์˜ ํŠน์„ฑ ์ƒ ์‚ฌ์šฉ์ž์˜ ์ปดํ“จํ„ฐ์—์„œ ์š”์ฒญ์„ ์œ„์กฐํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
๊ณต๊ฒฉ์ž๊ฐ€ ๊ท€์ฐฎ์„ ๋ฟ์ด์ง€ XSS๊ฐ€ ๋šซ๋ฆฐ๋‹ค๋ฉด httpOnly cookie๋„ ์•ˆ์ „ํ•˜์ง„ ์•Š๋‹ค.

๐Ÿ“ CSRF ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•˜๋‹ค.

์ž๋™์œผ๋กœ http request์— ๋‹ด์•„์„œ ๋ณด๋‚ด๊ธฐ ๋•Œ๋ฌธ์— ๊ณต๊ฒฉ์ž๊ฐ€ request url๋งŒ ์•ˆ๋‹ค๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ๊ด€๋ จ link๋ฅผ ํด๋ฆญํ•˜๋„๋ก ์œ ๋„ํ•˜์—ฌ request๋ฅผ ์œ„์กฐํ•˜๊ธฐ ์‰ฝ๋‹ค.

ํ•ด๊ฒฐ 3 : refresh token ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” refresh token์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค. CSRF ๊ณต๊ฒฉ์œผ๋กœ๋ถ€ํ„ฐ ์•ˆ์ „ํ•œ ํ™˜๊ฒฝ์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์ฟ ํ‚ค์— SameSite ์†์„ฑ์„ ์„ค์ •ํ•˜๊ณ , ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์„ ํ™œ์šฉํ•˜์—ฌ ์š”์ฒญ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ๋ฐฑ์—”๋“œ api ๊ฐœ๋ฐœ์ž์™€ ์†Œํ†ต์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด refresh token์„ httpOnly ์ฟ ํ‚ค๋กœ ์„ค์ •ํ•˜๊ณ  url์ด ์ƒˆ๋กœ๊ณ ์นจ ๋  ๋•Œ๋งˆ๋‹ค refresh token์„ request์— ๋‹ด์•„ ์ƒˆ๋กœ์šด accessToken์„ ๋ฐœ๊ธ‰ ๋ฐ›๋Š”๋‹ค.

๋ฐœ๊ธ‰ ๋ฐ›์€ accessToken์€ js private variable์— ์ €์žฅํ•œ๋‹ค.

์ด๋Ÿฐ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, refresh token์ด CSRF์— ์˜ํ•ด ์‚ฌ์šฉ๋œ๋‹ค ํ•˜๋”๋ผ๋„ ๊ณต๊ฒฉ์ž๋Š” accessToken์„ ์•Œ ์ˆ˜ ์—†๋‹ค.

CSRF๋Š” ํ”ผํ•ด์ž์˜ ์ปดํ“จํ„ฐ๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์š”์ฒญ์„ ์œ„์กฐํ•˜์—ฌ ํ”ผํ•ด์ž๊ฐ€ ์˜๋„ํ•˜์ง€ ์•Š์€ ์„œ๋ฒ„ ๋™์ž‘์„ ์ผ์œผํ‚ค๋Š” ๊ณต๊ฒฉ๋ฐฉ๋ฒ•์ด๊ธฐ ๋•Œ๋ฌธ์— refresh token์„ ํ†ตํ•ด ๋ฐ›์•„์˜จ response(accessToken)๋Š” ๊ณต๊ฒฉ์ž๊ฐ€ ํ™•์ธํ•  ์ˆ˜ ์—†๋‹ค.

๋”ฐ๋ผ์„œ ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ XSS๋ฅผ ๋ง‰๊ณ  refresh token ๋ฐฉ์‹์„ ์ด์šฉํ•˜์—ฌ CSRF๋ฅผ ๋ง‰์„ ์ˆ˜ ์žˆ๋‹ค.