| ์ผ | ์ | ํ | ์ | ๋ชฉ | ๊ธ | ํ |
|---|---|---|---|---|---|---|
| 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 |
- BFF์ํคํ ์ฒ
- JS
- canvas image
- JAVA Script
- HTML
- clone coding
- ๋ฐ๋๋ผ JS๋ก ๊ทธ๋ฆผํ ๋ง๋ค๊ธฐ
- HttpOnly Cookie
- next.js
- canvas js
- ๋ฐ๋๋ผ JS๋ก ํฌ๋กฌ ์ฑ ๋ง๋ค๊ธฐ
- ์ฝ๋ฉ์ผ๊ธฐ
- canvas
- ์ฝ๋ฉ๋ ํ
- ์ํ์ฝ๋ฉ
- ๋ ธ๋ง๋์ฝ๋
- ํ๋ก ํธ์๋
- javascript
- HttyOnly Cookie
- ํ๋ก์ ์ํคํ ์ฒ
- cookie ๋ณด์
- jest
- tailwindcss
- typescript
- ์ฝ์ฝ์ํก ํด๋ก ์ฝ๋ฉ
- cookie ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- CSS
- BFF ์ํคํ ์ฒ
- ๋ ธ๋ง๋ ์ฝ๋
- react
- Today
- Total
Coding Archive
[Next.js] Next.js๋ก HttpOnly ์ฟ ํค + BFF(ํ๋ก์)์ํคํ ์ฒ๋ก ์ธ์ฆ ๋ณด์/์ฑ๋ฅ ์ก๊ธฐ - ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ์ธ์ฆ์ ๋ณด์ ๋ฌธ์ ์ HttpOnly ์ฟ ํค๋ฅผ ํ์ฉํ ๊ฐ์ ๋ฐฉ๋ฒ ๋ณธ๋ฌธ
[Next.js] Next.js๋ก HttpOnly ์ฟ ํค + BFF(ํ๋ก์)์ํคํ ์ฒ๋ก ์ธ์ฆ ๋ณด์/์ฑ๋ฅ ์ก๊ธฐ - ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ์ธ์ฆ์ ๋ณด์ ๋ฌธ์ ์ HttpOnly ์ฟ ํค๋ฅผ ํ์ฉํ ๊ฐ์ ๋ฐฉ๋ฒ
์ฝ๋ฑ์ด 2025. 8. 22. 18:41
Next.js ํ๋ก์ ํธ๋ฅผ ๋ฆฌํฉํ ๋งํ๋ ๊ณผ์ ์์ ์์์น ๋ชปํ ๋ณด์ ์ด์๋ฅผ ๋ฐ๊ฒฌํ๊ฒ ๋์์ต๋๋ค. ๋ฐ๋ก ๋ธ๋ผ์ฐ์ ์ ๊ฐ๋ฐ์ ๋๊ตฌ์์ ์ฟ ํค์ ์ ์ฅ๋ ํ ํฐ์ด ๊ทธ๋๋ก ๋ ธ์ถ๋๊ณ ์์๋ค๋ ์ ์ ๋๋ค. ํ ํฐ์ ์ฌ์ฉ์ ์ธ์ฆ๊ณผ ๋ณด์์ ์ํด ๊ฐ์ฅ ์ค์ํ ๊ฐ ์ค ํ๋์ธ๋ฐ, ์ด ๊ฐ์ด ๊ทธ๋๋ก ํด๋ผ์ด์ธํธ ์ธก์์ ํ์ธ ๊ฐ๋ฅํ๋ค๋ฉด XSS(๊ต์ฐจ ์ฌ์ดํธ ์คํฌ๋ฆฝํธ ๊ณต๊ฒฉ)๋ ํ ํฐ ํ์ทจ ๋ฑ์ ๋ณด์ ์ทจ์ฝ์ ์ผ๋ก ์ด์ด์ง ์ ์์ต๋๋ค.
์ด๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํด ๊ธฐ์กด ์ฝ๋ ๊ตฌ์กฐ์ ์ธ์ฆ ๋ฐฉ์์ ๋ค์ ์ ๊ฒํ๊ฒ ๋์๊ณ , ํนํ ํ ํฐ์ ์ด๋์ ์ ์ฅํ๊ณ , ์ด๋ป๊ฒ ์ ๋ฌํ ๊ฒ์ธ๊ฐ๋ผ๋ ์ฃผ์ ์ ์ง์คํ์ต๋๋ค. ํ๋ก ํธ์๋์ ๋ฐฑ์๋ ๊ฐ ํต์ ๊ตฌ์กฐ, ์ฟ ํค ์ต์ ์ค์ (HttpOnly, Secure ๋ฑ), ํ๋ก์ ์๋ฒ์ ์ญํ ๊น์ง ์ ๋ฐ์ ์ผ๋ก ์ดํด๋ณด๋ฉด์ ์ฝ๋๋ฅผ ์์ ํด ๋๊ฐ์ต๋๋ค.
์ด๋ฒ ๊ธ์์๋ ์ ๊ฐ ๊ฒช์ ๋ฌธ์ ์ํฉ๊ณผ, ๊ทธ๊ฒ์ ์ด๋ป๊ฒ ๊ฐ์ ํ๋์ง์ ๋ํ ๊ณผ์ ์ ์ ๋ฆฌํด ๋ณด๋ ค๊ณ ํฉ๋๋ค.
๊ธฐ์กด ์ฝ๋: ํด๋ผ์ด์ธํธ(JS)์์ ํ ํฐ์ ์ง์ ์ ์ฅ/์ฃผ์ → HttpOnly ์ฌ์ฉ ๋ถ๊ฐ, XSS/ํ์ทจ ์ํ.
๋ฆฌํฉํ ๋ง: Next.js ์๋ฒ๊ฐ ํ ํฐ์ HttpOnly ์ฟ ํค๋ก๋ง ๊ด๋ฆฌํ๊ณ , ํด๋ผ์ด์ธํธ ๋ณดํธ API๋ ํ๋ก์(BFF)๋ก ํธ์ถ.
๐ ๊ธฐ์กด ์ฝ๋ ๋ฌธ์ ์ง์ด๋ณด๊ธฐ
1๏ธโฃ XSS ๊ณต๊ฒฉ์ ์์ ํ ๋ ธ์ถ๋ ํ ํฐ
// ๊ธฐ์กด AuthService.ts
class AuthService {
private static userAccount: string | null = StorageService.get('userAccount');
private static token: string | null = StorageService.get('token');
static login(userAccount: string, token: string): void {
this.userAccount = userAccount;
this.token = token;
StorageService.setStorageAdapter('localStorage');
StorageService.set('userAccount', userAccount);
StorageService.setStorageAdapter('cookieStorage');
StorageService.set('token', token, {
expires: 3,
path: '',
});
}
static getToken(): string | null {
return this.token || StorageService.get('token');
}
}
// LoginPage์์์ ์ฌ์ฉ
const onSubmit = async (data: ILoginRequest) => {
try {
const response = await login(data);
const { token, accountname } = response.user;
if (token && accountname) {
AuthService.login(accountname, token); // ํด๋ผ์ด์ธํธ์์ ์ง์ ์ ์ฅ
goTo('/feed');
}
} catch (error) {
console.error('๋ก๊ทธ์ธ ์๋ฌ:', error);
}
};
- JavaScript๋ก ์ฟ ํค๋ฅผ set/get ํ๋ฉด HttpOnly๋ฅผ ์ฌ์ฉํ ์ ์์ด XSS๊ฐ ํฐ์ง๋ฉด ํ ํฐ ํ์ทจ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
- ํ ํฐ์ด ์ผ๋ฐ ์ฟ ํค๋ localStorage์ ์ ์ฅ๋์ด JavaScript๋ก ์ ๊ทผ์ด ๊ฐ๋ฅํฉ๋๋ค.
- ์๋์ ๊ฐ์ด XSS ๊ณต๊ฒฉ์๊ฐ ๋จ ํ ์ค์ JavaScript ์ฝ๋๋ก ์ฌ์ฉ์์ ์ธ์ฆ ํ ํฐ์ ํ์ทจํ ์ ์์ต๋๋ค.
// ์
์ฑ ์คํฌ๋ฆฝํธ๊ฐ ์ฝ๊ฒ ํ ํฐ์ ํ์ทจํ ์ ์์
console.log(document.cookie); // ํ ํฐ์ด ๊ทธ๋๋ก ๋
ธ์ถ
console.log(localStorage.getItem('token')); // ํ ํฐ ์ง์ ์ ๊ทผ ๊ฐ๋ฅ
2๏ธโฃ CSRF ๊ณต๊ฒฉ ๋ฐฉ์ด ๋ถ์กฑ
// ๊ธฐ์กด ์ฝ๋ - SameSite ๋ณดํธ ์์
StorageService.set('token', token, {
expires: 3,
path: '', // SameSite, Secure ์ต์
์์
});
- ์ผ๋ฐ ์ฟ ํค๋ SameSite ์ค์ ์ด ์์ผ๋ฉด ๋ชจ๋ ๋๋ฉ์ธ์์ ์๋์ผ๋ก ์ ์ก๋ฉ๋๋ค.
- ์ ์ฑ ์ฌ์ดํธ์์ ์ฌ์ฉ์ ๋ชจ๋ฅด๊ฒ ์์ฒญ์ ๋ณด๋ด๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ ์๋์ผ๋ก ํ ํฐ์ ํฌํจํด์ ์ ์กํฉ๋๋ค.
3๏ธโฃ ๋ณต์กํ ์ ์ฅ์ ๊ด๋ฆฌ ๋ก์ง
// ์ ์ฅ์ ํ์
์ ๊ณ์ ๋ฐ๊พธ๋ ๋ณต์กํ ๋ก์ง
StorageService.setStorageAdapter('localStorage');
StorageService.set('userAccount', userAccount);
StorageService.setStorageAdapter('cookieStorage');
StorageService.set('token', token, { expires: 3, path: '' });
- localStorage์ cookieStorage๋ฅผ ๋ฒ๊ฐ์ ์ฌ์ฉํ๋ ๋ณต์กํ ๋ก์ง์ผ๋ก ์ธํ ๋ฒ๊ทธ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค.
- ์ง์ ์ ์ธ ๋ณด์ ์ํ์ ์๋์ง๋ง, ๋ณต์ก์ฑ์ผ๋ก ์ธํด ๋ค๋ฅธ ๋ณด์ ๋ฒ๊ทธ๊ฐ ๋ฐ์ํ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค.
๋ฌธ์ ํ๋ฆ์ ์ ๋ฆฌํด ๋ณด๋ฉด
AuthService.login()์ด localStorage + cookieStorage์ ํ ํฐ์ ์ ์ฅ
StorageService๋ JS์์ ์ฟ ํค๋ฅผ ์ง์ set() (HttpOnly ์ค์ ๋ถ๊ฐ)
Axios ์ธ์คํด์ค/์ธํฐ์ ํฐ๋ ํด๋ผ์ด์ธํธ์์ ํ ํฐ์ ํค๋์ ๋ถ์ฌ ์ ์กํ๋ ํ๋ฆ
๐ ์ง๊ธ ๊ตฌ์กฐ๋ ๊ตฌํ์ ๊ฐ๋จํ์ง๋ง, ์ค์๋น์ค ๋ณด์ ๊ธฐ์ค(ํนํ XSS/ํ ํฐ ๋ณดํธ)์์ ์ทจ์ฝํฉ๋๋ค.
๐ก Next.js HttpOnly ์ฟ ํค๋ฅผ ํ์ฉํ ๋ณด์ ๊ฐ์ ํ๊ธฐ
1. ๊ฐ์ ๋ชฉํ: ํ๋ก์ ํจํด์ผ๋ก ๊ธฐ์กด ์ฝ๋ ์ต๋ํ ๋ณด์กดํ๋ฉฐ ๋ฆฌํฉํ ๋งํ๊ธฐ
๊ธฐ์กด ์ฝ๋์ 95%๋ฅผ ๊ทธ๋๋ก ์ ์งํ๋ฉด์ ๋ณด์๋ง ํฌ๊ฒ ๊ฐ์ ํ๋ ๋ฐฉ๋ฒ ์ฌ์ฉํ์์ต๋๋ค.
๊ธฐ์กด: ํด๋ผ์ด์ธํธ → ์ธ๋ถ API → ์๋ต → ํด๋ผ์ด์ธํธ์์ ์ฟ ํค ์ ์ฅ
๊ฐ์ : ํด๋ผ์ด์ธํธ → Next.js API Route → ์ธ๋ถ API → ์๋ฒ์์ HttpOnly ์ฟ ํค ์ ์ฅ
// ํด๋ ๊ตฌ์กฐ
frontend/
โโโ app/
โ โโโ api/ # โ
Next.js API Route Handlers (BFF)
โ โ โโโ user/
โ โ โ โโโ login/
โ โ โ โโโ route.ts # POST /api/user/login
โ โ โโโ post/
โ โ โโโ feed/
โ โ โโโ route.ts # GET /api/post/feed
โ โโโ (auth)/
โ โ โโโ login/page.tsx
โ โโโ feed/page.tsx
โ
โโโ src/
โ โโโ api/
โ โ โโโ apiRequests/ # โ
ํด๋ผ์ด์ธํธ์์ ํธ์ถํ๋ ํจ์ ๋ชจ์
โ โ โ โโโ auth.ts # login, signup ๋ฑ
โ โ โ โโโ post.ts # postList ๋ฑ
โ โ โโโ index.ts # axios instance
โ โ
โ โโโ components/ # ๊ณตํต ์ปดํฌ๋ํธ
โ โโโ services/ # AuthService, StorageService ๋ฑ (ํด๋ผ ์ ์ฉ)
โ โโโ queries/ # react-query hooks
โ
โโโ .env.local # BACKEND_BASE_URL ๋ฑ ํ๊ฒฝ๋ณ์
2. ๊ฐ์ ๋ฐฉํฅ: Next BFF(ํ๋ก์) + HttpOnly ์ฟ ํค
- ๋ก๊ทธ์ธ: Next ์๋ฒ ๋ผ์ฐํธ๊ฐ ๋ฐฑ์๋ ๋ก๊ทธ์ธ API๋ฅผ ํธ์ถ → ์๋ต๋ฐ์ ํ ํฐ์ HttpOnly ์ฟ ํค๋ก ์๋ฒ๊ฐ ์ค์ .
- ๋ณดํธ API ํธ์ถ: ํด๋ผ์ด์ธํธ๋ ๋ฐฑ์๋๋ก ์ง์ ๊ฐ์ง ์๊ณ Next ํ๋ก์๋ก ํธ์ถ → ํ๋ก์๊ฐ ์ฟ ํค์์ ํ ํฐ์ ๊บผ๋ด Authorization ํค๋๋ฅผ ์๋ฒ์์๋ง ์ฃผ์ .
- ์ธ์ ํ์ธ: ์๋ฒ ๋ผ์ฐํธ์์ ์ฟ ํค ํ ํฐ์ผ๋ก ํ์ธ → ํด๋ผ์๋ “์ธ์ฆ๋จ/์๋ + ์ต์ ์ ๋ณด”๋ง ์ ๋ฌ.
- ํด๋ผ์ด์ธํธ๋ ํ ํฐ์ ์์ ๋ชจ๋ฅธ๋ค. (์ด๊ฒ ํฌ์ธํธ!)
3. ๋จ๊ณ๋ณ ๋ฆฌํฉํ ๋ง
ํด๋ผ์ด์ธํธ์์ ํ ํฐ ๋ค๋ฃจ๋ ์ฝ๋ ์ ๊ฑฐ → Next ๋ผ์ฐํธ/ํ๋ก์์์๋ง ์ฟ ํค/ํ ํฐ ์ ์ด
1๏ธโฃ ๋ณด์ ๊ฐํ๋ ๋ก๊ทธ์ธ API Route ์์ฑ
// app/api/auth/login/route.ts (BFF, Next.js ์๋ฒ์์๋ง ์คํ)
import { cookies } from 'next/headers'; // (์๋ฒ ์ปดํฌ๋ํธ๋ Route Handler(app/api)์์๋ง ํธ์ถ ๊ฐ๋ฅํ ์๋ฒ ์ปดํฌ๋ํธ ์ ์ฉ API)
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const body = await request.json();
// 1. ๊ธฐ์กด ์ธ๋ถ API ํธ์ถ
const response = await fetch(
`${process.env.NEXT_PUBLIC_BASE_URL}/user/login`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
},
);
const data = await response.json();
if (response.ok && data.user?.token) {
// 2. ๐ก๏ธ HttpOnly ์ฟ ํค๋ก ๋ณด์ ์ ์ฅ
cookies().set('token', data.user.token, {
httpOnly: true, // XSS ๊ณต๊ฒฉ ๋ฐฉ์ด
secure: process.env.NODE_ENV === 'production', // HTTPS์์๋ง ์ ์ก
sameSite: 'strict', // CSRF ๊ณต๊ฒฉ ๋ฐฉ์ด
maxAge: 60 * 60 * 24 * 3, // 3์ผ
path: '/',
});
// 3. ๐ ํด๋ผ์ด์ธํธ์๋ ํ ํฐ ์์ด ์์ ํ ์๋ต
return NextResponse.json({
...data,
user: {
...data.user,
token: undefined, // ํ ํฐ ์ ๊ฑฐ๋ก ํด๋ผ์ด์ธํธ ๋
ธ์ถ ๋ฐฉ์ง
},
});
}
return NextResponse.json(data, { status: response.status });
}
- ๋ก๊ทธ์ธ ์์ฒญ ํ ํ ํฐ์ HttpOnly + Secure ์ฟ ํค์ ์ ์ฅ
- HttpOnly ์ฟ ํค๋ฅผ ์ฝ๊ฑฐ๋ ์ธํ ํ ์ ์์
- ๋ฐฑ์๋ API์ ์์ฒญ์ ๋๋ฆฌ๋ก ๋ณด๋
2๏ธโฃ Axios ์ธ์คํด์ค ํ๋ก์๋ฅผ ๊ธฐ๋ณธ baseURL๋ก
// src/api/index.ts
import axios, { AxiosInstance } from 'axios';
import { handleRequest, handleRequestError } from './helper/requestHandlers';
import { handleResponse, handleResponseError } from './helper/responseHandlers';
const instance: AxiosInstance = axios.create({
baseURL: '/api', // Next.js์ API ๋ผ์ฐํธ ์ฌ์ฉ (๊ธฐ์กด: process.env.NEXT_PUBLIC_BASE_URL)
withCredentials: true, // ์ฟ ํค ์๋ ์ ์ก
});
instance.interceptors.request.use(handleRequest, handleRequestError);
instance.interceptors.response.use(handleResponse, handleResponseError);
export default instance;
3๏ธโฃ AuthService/StorageService ์ ๋ฆฌ ๋ฐ ๋ก๊ทธ์ธ ํ์ด์ง
// services/AuthService.ts
import StorageService from './StorageService';
class AuthService {
private static userAccount: string | null = StorageService.get('userAccount');
static login(userAccount: string): void {
this.userAccount = userAccount;
StorageService.setStorageAdapter('localStorage');
StorageService.set('userAccount', userAccount);
}
...
}
export default AuthService;
- ํ ํฐ์ ์ ์ฅ/์กฐํํ๋ ๋ชจ๋ ๋ฉ์๋ ์ญ์
4๏ธโฃ ํผ๋ ๋ผ์ฐํธ ์์ฑ (ํค๋์ ํ ํฐ ์ฃผ์ )
// app/api/post/feed/route.ts
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const limit = searchParams.get('limit');
const skip = searchParams.get('skip');
const token = cookies().get('token')?.value;
const res = await fetch(
`${process.env.BACKEND_BASE_URL}/post/feed/?limit=${limit}&skip=${skip}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
cache: 'no-store', // ํญ์ ์ต์ ๋ฐ์ดํฐ
},
);
const data = await res.json();
return NextResponse.json(data);
}
- ํด๋ผ์ด์ธํธ → BFF ์์ฒญ ์ ์ฟ ํค ํฌํจ (withCredentials: true)
- BFF → ๋ฐฑ์๋ ์์ฒญ ์ ์ฟ ํค์์ token ์ถ์ถ → Authorization ํค๋๋ก ๋ณํ
๐ ํด๋ผ์ด์ธํธ๋ ๋จ์ํ /api/... ๋ง ํธ์ถํ๋ฉด ๋๊ณ , ๋ณด์/ํ ํฐ ๊ด๋ฆฌ๋ ์ ๋ถ Next.js ์๋ฒ (app/api)๊ฐ ์ฒ๋ฆฌํฉ๋๋ค.
๐ ์ต์ข ๊ตฌ์กฐ ํ๋ฆ
[ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ]
โ
โผ
๋ก๊ทธ์ธ ์์ฒญ (email, password)
โ
โผ
Axios → /api/auth/login
โ
โผ
[Next.js Route Handler (/api/auth/login)]
- ๋ฐฑ์๋๋ก ๋ก๊ทธ์ธ ์์ฒญ ์ ๋ฌ
- ์ฑ๊ณต ์ JWT ํ ํฐ ์์
- Response ์ฟ ํค(token=..., HttpOnly)๋ก ์ ์ฅ
โ
โผ
๋ธ๋ผ์ฐ์ ์ ์ฅ์
- HttpOnly ์ฟ ํค(token) ์๋ ๋ณด๊ด
- JS์์ ์ง์ ์ ๊ทผ ๋ถ๊ฐ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
์ดํ API ์์ฒญ ํ๋ฆ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
ํด๋ผ์ด์ธํธ์์ postList(limit, skip) ํธ์ถ
โ
โผ
Axios → /api/post/feed
โ
โผ
[Next.js Route Handler (/api/post/feed)]
- cookies() ๋ก HttpOnly ํ ํฐ ๊บผ๋
- Authorization: Bearer <token> ํค๋ ๋ถ์
โ
โผ
[๋ฐฑ์๋ API ์๋ฒ ํธ์ถ]
- ํ ํฐ ๊ฒ์ฆ
- ๋ฐ์ดํฐ ๋ฐํ
โ
โผ
Next.js Route Handler → ํด๋ผ์ด์ธํธ ์๋ต
๐ก๏ธ ๋ณด์ ๊ฐ์ ํจ๊ณผ
Before vs After ๋น๊ต


| ๋ณด์ ์์ | ๊ธฐ์กด ๋ฐฉ์ | ๊ฐ์ ๋ ๋ฐฉ์ |
| XSS ๋ฐฉ์ด | โ JavaScript๋ก ํ ํฐ ์ ๊ทผ ๊ฐ๋ฅ | โ HttpOnly๋ก JavaScript ์ ๊ทผ ์ฐจ๋จ |
| CSRF ๋ฐฉ์ด | โ SameSite ์ค์ ์์ | โ SameSite=strict๋ก ์๋ฒฝ ์ฐจ๋จ |
| HTTPS ๊ฐ์ | โ HTTP์์๋ ํ ํฐ ์ ์ก | โ Secure ์์ฑ์ผ๋ก HTTPS๋ง ํ์ฉ |
| ํ ํฐ ๋ ธ์ถ | โ ๊ฐ๋ฐ์ ๋๊ตฌ์์ ์์ ๋ ธ์ถ -> ํ์ทจ ๊ฐ๋ฅ | โ ์๋ฒ์์๋ง ์ ๊ทผ ๊ฐ๋ฅ -> ํ์ทจ ๋ถ๊ฐ |
| ์ฝ๋ ๋ณต์ก์ฑ | โ ๋ณต์กํ ์ ์ฅ์ ๋ก์ง | โ ๊ฐ๋จํ๊ณ ๋ช ํํ ๊ตฌ์กฐ |
๐ ๏ธ ์ดํ ๊ฐ์ ๋ฐฉํฅ
ํ์ฌ๋ ๊ฐ API ๋ผ์ฐํธ์์ ์ฟ ํค๋ฅผ ์ง์ ์ฝ๊ณ , ์์ฒญ๋ง๋ค Authorization ํค๋๋ฅผ ๋ถ์ฌ์ฃผ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํ๋์ด ์์ต๋๋ค. ๊ธฐ๋ฅ์ ์ผ๋ก๋ ๋ฌธ์ ์์ง๋ง, ํ ํฐ์ด ํ์ํ API๊ฐ ๋ง์์ง์๋ก ์ค๋ณต ์ฝ๋๊ฐ ๋์ด๋๋ค๋ ๋จ์ ์ด ์์ต๋๋ค.
๊ทธ๋์ ๋ค์ ๋จ๊ณ์์๋ ๋ฒ์ฉ ํ๋ก์ ๊ตฌ์กฐ๋ก ๋ฆฌํฉํฐ๋ง ํ ์์ ์ ๋๋ค.
- ํ๋ก ํธ์๋์์๋ ๋จ์ํ /app/api/... ๋ก ์์ฒญ
- ํ๋ก์ ๋ ์ด์ด๊ฐ ์๋์ผ๋ก ์ฟ ํค์์ ํ ํฐ์ ์ฝ์ด Authorization ํค๋๋ฅผ ๋ถ์ด๊ณ ๋ฐฑ์๋์ ์ ๋ฌ
- API ์๋ํฌ์ธํธ๊ฐ ์์ญ, ์๋ฐฑ ๊ฐ๋ก ๋์ด๋๋๋ผ๋ ํ๋ก์ ์ฝ๋ ํ๋๋ง ์ ์งํ๋ฉด ์ ์ฒด๊ฐ ๋์
๐ ์ค๋ณต ์ฝ๋ ์ ๊ฑฐ + ์ ์ง๋ณด์์ฑ ํฅ์ + ๋ณด์ ์ผ๊ด์ฑ ๊ฐํ
์ฒ์์๋ “ํ ํฐ์ด ๋ณด์ด๋ฉด ์ ๋๊ฒ ๋ค”๋ ๋จ์ํ ๋ฌธ์ ์์์์ ์ถ๋ฐํ์ง๋ง, HttpOnly ์ฟ ํค์ ํ๋ก์ ๋ผ์ฐํ ์ ์ ์ฉํ๋ฉด์ ๋ณด์๋ฟ๋ง ์๋๋ผ ์ฝ๋ ๊ตฌ์กฐ๊ฐ ํจ์ฌ ๊ฐ๊ฒฐํด์ง๊ณ , ๊ฒฐ๊ณผ์ ์ผ๋ก ๊ฐ๋ฐ ํ๋ฆ๋ ๋งค๋๋ฌ์์ก์ต๋๋ค.
๊ฒฐ๊ตญ ์ด๋ฒ ์์ ์ผ๋ก ๋จ์ํ ๋ฒ๊ทธ ์์ ์ด ์๋๋ผ, ์๋น์ค๋ฅผ ๋ ์์ ํ๊ฒ ๋ง๋ค๊ณ ๊ฐ๋ฐ ํ๊ฒฝ๊น์ง ๊ฐ์ ํ ์ ์์์ต๋๋ค.
์์ผ๋ก๋ ์ด๋ฐ ์์ ๊ฐ์ ๋ค์ ๊พธ์คํ ์์๊ฐ๋ฉด์ ๋ ์์ ์ ์ด๊ณ ์ฆ๊ฒ๊ฒ ๊ฐ๋ฐํ ์ ์๋ ํ๊ฒฝ์ ๋ง๋ค์ด๊ฐ๊ณ ์ถ์ต๋๋ค.
๋์ผ๋ก ์ด ๊ธ์ด ๋น์ทํ ๊ณ ๋ฏผ์ ํ๊ณ ๊ณ์ ๋ถ๋ค๊ป ์์ ๋์์ด ๋๊ธธ ๋ฐ๋๋๋ค!