| ์ผ | ์ | ํ | ์ | ๋ชฉ | ๊ธ | ํ |
|---|---|---|---|---|---|---|
| 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 |
- react
- typescript
- ํ๋ก์ ์ํคํ ์ฒ
- BFF ์ํคํ ์ฒ
- JAVA Script
- canvas
- next.js
- cookie ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- canvas js
- ๋ฐ๋๋ผ JS๋ก ํฌ๋กฌ ์ฑ ๋ง๋ค๊ธฐ
- clone coding
- ๋ฐ๋๋ผ JS๋ก ๊ทธ๋ฆผํ ๋ง๋ค๊ธฐ
- HttyOnly Cookie
- CSS
- ์ฝ์ฝ์ํก ํด๋ก ์ฝ๋ฉ
- ์ฝ๋ฉ์ผ๊ธฐ
- BFF์ํคํ ์ฒ
- HTML
- ํ๋ก ํธ์๋
- ์ํ์ฝ๋ฉ
- ์ฝ๋ฉ๋ ํ
- cookie ๋ณด์
- ๋ ธ๋ง๋์ฝ๋
- jest
- tailwindcss
- HttpOnly Cookie
- javascript
- canvas image
- ๋ ธ๋ง๋ ์ฝ๋
- JS
- Today
- Total
Coding Archive
[Next.js] Next.js๋ก HttpOnly ์ฟ ํค + BFF(ํ๋ก์) ์ํคํ ์ฒ๋ก ์ธ์ฆ ๋ณด์/์ฑ๋ฅ ์ก๊ธฐ(๋ฒ์ธํธ) - ์ฟ ํค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋์ BFF ์ํคํ ์ฒ๋ฅผ ํํ ์ด์ ๋ณธ๋ฌธ
[Next.js] Next.js๋ก HttpOnly ์ฟ ํค + BFF(ํ๋ก์) ์ํคํ ์ฒ๋ก ์ธ์ฆ ๋ณด์/์ฑ๋ฅ ์ก๊ธฐ(๋ฒ์ธํธ) - ์ฟ ํค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋์ BFF ์ํคํ ์ฒ๋ฅผ ํํ ์ด์
์ฝ๋ฑ์ด 2025. 8. 23. 13:20
์ด์ ์ ์ฌ๋ฆฐ ๊ธ์์ Next.js BFF(Backend For Frontend, ํ๋ก์) ์ํคํ ์ฒ๋ฅผ ์ ์ฉํด, HttpOnly ์ฟ ํค ๊ธฐ๋ฐ ์ธ์ฆ์ผ๋ก ๋ณด์๊ณผ ์ฑ๋ฅ์ ๊ฐ์ ํ๋ ๊ณผ์ ์ ์๊ฐํ์ต๋๋ค.
๊ทธ๋ฐ๋ฐ ํ๋ก์ ๊ตฌ์กฐ๋ฅผ ๋์ ํ๊ธฐ ์ , ์ด๋ฐ ์๋ฌธ์ด ๋ค์์ต๋๋ค.
“๊ตณ์ด ํ๋ก์๊น์ง ์ฐ์ง ์๊ณ , ์ฟ ํค ๋ผ์ด๋ธ๋ฌ๋ฆฌ + ์ธํฐ์ ํฐ ๊ตฌ์กฐ๋ก ํด๊ฒฐํ ์ ์์ง ์์๊น?”
์ค์ ๋ก ์ด์ ์ ํ๋ก์ ํธ์์ react-cookie + Axios ์ธํฐ์ ํฐ ์กฐํฉ์ผ๋ก ์ธ์ฆ์ ๊ตฌํํ ์ ์ด ์์๊ณ , ์์ ๊ท๋ชจ์ ํ๋ก์ ํธ์์ ์ด ๋ฐฉ์์ด ๊ฝค ๋จ์ํ๊ณ ๋น ๋ฅด๊ฒ ์ ์ฉํ ์ ์์ด์ ๋์์ง ์์์ต๋๋ค.
ํ์ง๋ง, ์ฟ ํค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ง์ผ๋ก๋ HttpOnly ์ฟ ํค์ ๋ณด์ ์ฅ์ ์ ์ด๋ฆด ์ ์์์ต๋๋ค.
์ด ๊ธ์์๋ ๊ทธ ์ด์ ์ ํจ๊ป ๊ฐ ๋ฐฉ์์ ์์ธํ ๋น๊ตํด ๋ณด๊ฒ ์ต๋๋ค.
๋๋ถ๋ถ์ ํ๋ก ํธ์๋ ์ฟ ํค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ฌ์ค์ HttpOnly ์ฟ ํค๋ฅผ ๋ค๋ฃฐ ์ ์๋ค!
HttpOnly ์ฟ ํค๋ ๋ธ๋ผ์ฐ์ JS์์ ์ ๊ทผ ๋ถ๊ฐ
- ์ด๊ฑด ๋ธ๋ผ์ฐ์ ์ ๋ณด์ ์ ์ฑ (Web Standard)์ด๋ผ, ์ด๋ค JS ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๋ซ์ ์ ์์ต๋๋ค.
- react-cookie, next-cookie, universal-cookie, js-cookie ์ ๋ถ ๊ณตํต์ ์ผ๋ก HttpOnly ์ฟ ํค๋ฅผ ์ฝ๊ฑฐ๋ ์ธ ์ ์์ต๋๋ค.
์ ๋ฆฌํ๋ฉด, ์ฟ ํค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ฌ์ค์ non-HttpOnly ์ฟ ํค(=JS ์ ๊ทผ ๊ฐ๋ฅํ ์ฟ ํค)๋ฅผ ๋ค๋ฃจ๋ ๋๊ตฌ์ผ ๋ฟ์ ๋๋ค.
! ์ฌ๊ธฐ์ ์ ์ ์ฃผ์ ์ฟ ํค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ณ ์ ์ฝ์ฌํญ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ์ค์ํ ๋ด์ฉ์ ์๋๋ ๊ฒฐ๋ก ๋ง ๋ณด๊ณ ๋์ด๊ฐ๋ ์๊ด์์ต๋๋ค.
โ๏ธ ์ฃผ์ ์ฟ ํค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ณ ์ ์ฝ์ฌํญ
1. react-cookie
import { useCookies } from 'react-cookie';
function MyComponent() {
const [cookies, setCookie] = useCookies(['token']);
// โ HttpOnly ์ฟ ํค๋ ์ฝ์ ์ ์์
console.log(cookies.token); // undefined (HttpOnly์ธ ๊ฒฝ์ฐ)
// โ HttpOnly ์ฟ ํค๋ ์ค์ ํ ์ ์์ (ํด๋ผ์ด์ธํธ์์)
setCookie('token', 'value', { httpOnly: true }); // ๋ฌด์๋จ
}
- ํด๋ผ์ด์ธํธ์์ document.cookie๋ฅผ ๊ฐ์ธ๋ ๋ํผ
- SSR์์๋ req.headers.cookie๋ฅผ ํ์ฑํ๋ ๋ณด์กฐ ์ ํธ ์ ๋
2. next-cookie / cookies-next
import { getCookie, setCookie } from 'cookies-next';
// โ HttpOnly ์ฟ ํค ์ฝ๊ธฐ ๋ถ๊ฐ๋ฅ
const token = getCookie('httponly-token'); // undefined
// โ ํด๋ผ์ด์ธํธ์์ HttpOnly ์ฟ ํค ์ค์ ๋ถ๊ฐ๋ฅ
setCookie('token', 'value', { httpOnly: true }); // ๋ธ๋ผ์ฐ์ ์์ ๋ฌด์
- Next.js์ SSR/์๋ฒ ์ปดํฌ๋ํธ์์ ์ฟ ํค๋ฅผ ํธํ๊ฒ ์ฝ๊ณ ์ฐ๋ ์ฉ๋.
- ์๋ฒ ์ฝ๋์์๋ NextResponse.cookies.set()๋ก HttpOnly ๊ฐ๋ฅ
- ํ์ง๋ง ํด๋ผ์ด์ธํธ JS์์๋ ์ญ์ ๋ถ๊ฐ
3. js-cookie / universal-cookie
import Cookies from 'js-cookie';
// โ HttpOnly ์ฟ ํค ์ ๊ทผ ๋ถ๊ฐ๋ฅ
Cookies.get('httponly-token'); // undefined
// โ HttpOnly ์ต์
์์ฒด๊ฐ ์ง์๋์ง ์์
Cookies.set('token', 'value'); // ํญ์ non-HttpOnly
- ์์ ํด๋ผ์ด์ธํธ ์ฟ ํค ๋ผ์ด๋ธ๋ฌ๋ฆฌ.
- ์ญ์ HttpOnly ์ฟ ํค๋ ๋ค๋ฃฐ ์ ์์.
โ ๊ฒฐ๋ก ์ ์ผ๋ก ์ฟ ํค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋
next-cookie, react-cookie๋ HttpOnly ์ฟ ํค ์์ฒด๋ฅผ JS์์ ์ ์ดํ ์๋ ์์ต๋๋ค.
HttpOnly ์ฟ ํค๋ ์๋ฒ(Set-Cookie ํค๋)์์๋ง ์ ์ด ๊ฐ๋ฅํฉ๋๋ค.
์ฟ ํค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ด๋๊น์ง๋ non-HttpOnly ์ฟ ํค์ฉ ํธ์ ๊ธฐ๋ฅ์ด๋ผ๊ณ ๋ณด๋ฉด ๋ฉ๋๋ค.
๐ ์ฟ ํค ์ธ์ฆ ๋ฐฉ์ Trade-off ๋น๊ต
1. ์ฟ ํค ๋ผ์ด๋ธ๋ฌ๋ฆฌ + ์ธํฐ์ ํฐ ๋ฐฉ์
// Axios ์ธํฐ์
ํฐ ์์
import { getCookie } from 'cookies-next';
import axios from 'axios';
axios.interceptors.request.use((config) => {
const token = getCookie('token'); // โ ๏ธ non-HttpOnly๋ง ๊ฐ๋ฅ
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
2. BFF(ํ๋ก์) + HttpOnly ์ฟ ํค ๋ฐฉ์
// Next.js API Route
export async function POST(request) {
// โ
HttpOnly ์ฟ ํค์์ ํ ํฐ ์ฝ๊ธฐ
const token = request.cookies.get('auth-token')?.value;
if (!token) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// โ
๋ฐฑ์๋์ ์ธ์ฆ๋ ์์ฒญ ์ ๋ฌ
const response = await fetch('https://api.example.com/data', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
return NextResponse.json(await response.json());
}
| ๊ตฌ๋ถ | ์ฟ ํค ๋ผ์ด๋ธ๋ฌ๋ฆฌ + ์ธํฐ์ ํฐ ๋ฐฉ์ | BFF(ํ๋ก์) + HttpOnly ์ฟ ํค ๋ฐฉ์ |
| ๋ณด์ | โ HttpOnly ๋ถ๊ฐ → XSS ์ทจ์ฝ โ ํ ํฐ์ด JS ๋ฐํ์์ ๋ ธ์ถ โ ๏ธ CSRF ๋ฐฉ์ด ๋ฐ๋ก ํ์ |
โ
HttpOnly ์ฟ ํค ์ฌ์ฉ ๊ฐ๋ฅ โ ํ ํฐ JS ์ ๊ทผ ๋ถ๊ฐ → XSS ๋ฐฉ์ด โ CSRF ํ ํฐ ๊ฒ์ฆ์ ์๋ฒ์์ ํตํฉ ๊ด๋ฆฌ |
| ํธ์์ฑ (๊ฐ๋ฐ ๋์ด๋) | โ
๊ตฌํ ๊ฐ๋จ โ ๋น ๋ฅธ ๊ฐ๋ฐ/ํ๋กํ ํ์ ์ ํฉ โ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(document.cookie) ๊ธฐ๋ฐ |
โ ํ๋ก์ ์๋ฒ ํ์ โ ์ฝ๋ ๊ตฌ์กฐ ๋ณต์ก โ ์ด๊ธฐ ๊ฐ๋ฐ ๋น์ฉ ↑ |
| ํ์ฅ์ฑ (์ฅ๊ธฐ์ ์ด์) | โ ๋ณด์ ๋ฌธ์ ๋ก ๋๊ท๋ชจ ์๋น์ค์ ๋ถ์ ํฉ โ API Gateway ์ญํ ๋ถ๊ฐ๋ฅ |
โ
๋ณด์ ํ์ฅ์ฑ ๋์ โ ๋ก๊น /๋ ์ดํธ๋ฆฌ๋ฐ/A/B ํ ์คํธ ๋ฑ ์ ์ด ๊ฐ๋ฅ โ ์ค์๋น์ค ํ์ค ํจํด |
โ ์ ๋ฆฌ
์ฟ ํค ๋ผ์ด๋ธ๋ฌ๋ฆฌ => ๊ฐ๋จํ์ง๋ง HttpOnly ์ฟ ํค๋ฅผ ๋ชป ์ฐ๋ฏ๋ก XSS ์ทจ์ฝํฉ๋๋ค.
ํ๋ก์ ๋ฐฉ์ => ์ฝ๋๊ฐ ์ข ๋ณต์กํด์ง์ง๋ง, ๋ณด์ ์
๊ณ ํ์ค์ด๋ฉฐ ์ฅ๊ธฐ์ ์ผ๋ก๋ ํ์ฅ์ฑ๋ ์ข์ต๋๋ค.
์ด๋ฒ ๊ธ์์๋ “ํน์ ์ฟ ํค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๋ณด์ ๋ฌธ์ ๋ฅผ ๊ฐ๋จํ ํด๊ฒฐํ ์ ์์ง ์์๊น?”๋ผ๋ ์๋ฌธ์์ ์์ํด, ์ง์ ์๋ํด ๋ณธ ๊ฒฐ๊ณผ๋ฅผ ์ ๋ฆฌํ์ต๋๋ค.
๊ฒฐ๋ก ์ ๋จ์ํฉ๋๋ค.
์ฟ ํค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๊ฒฐ๊ตญ non-HttpOnly ์ฟ ํค๋ง ๋ค๋ฃฐ ์ ์๊ณ , ๋ณด์ ์ทจ์ฝ์ ์ ํผํ ์ ์์ต๋๋ค.
์ ์ญ์ ์ด์ ํ๋ก์ ํธ์์๋ react-cookie + ์ธํฐ์
ํฐ ๊ตฌ์กฐ๋ฅผ ํ์ฉํ์ง๋ง, ๊ณง ๋ณด์์์ ํ๊ณ๋ฅผ ์ฒด๊ฐํ๊ฒ ๋์๊ณ , ์ด๋ฒ์๋ ๋ณด๋ค ์์ ํ ๊ตฌ์กฐ์ธ BFF(ํ๋ก์) + HttpOnly ์ฟ ํค ์ํคํ
์ฒ๋ก ๋ฐ์ ์์ผ ๊ฐ๋ฐํ๊ฒ ๋์์ต๋๋ค.
์์ผ๋ก๋ ์ด ๊ฒฝํ์ ํ ๋๋ก ๋์ฑ ์์ ์ ์ด๊ณ ํ์ฅ์ฑ ์๋ ์ธ์ฆ/๋ณด์ ์ํคํ ์ฒ๋ฅผ ์ค๊ณํ ์ ์์ ๊ฒ ๊ฐ์ต๋๋ค.