
μΉ κ°λ°μ νλ€ λ³΄λ©΄ βλ‘κ·ΈμΈ μνλ₯Ό μ΄λ»κ² μ μ§ν κΉ?β λΌλ λ¬Έμ λ₯Ό λ°λμ λ§μ£Όνκ² λ©λλ€. μ΄λ μ¬μ©νλ κ²μ΄ λ°λ‘ λΈλΌμ°μ μ μ₯μ (Web Storage)μ λλ€.
μ΄λ² κΈμμλ
1οΈβ£ Local Storage, Session Storage, Cookie μ μ°¨μ΄μ
2οΈβ£ κ·Έλ¦¬κ³ μ€μ λ‘ μ κ° μ΄μ μ€μΈ Ddingsroom νλ‘μ νΈμμ μ΄λ»κ² μΈμ
μ κ΄λ¦¬νκ³ μλμ§
λ₯Ό ν¨κ» μ΄ν΄λ³΄κ² μ΅λλ€.
Local Storageλ λΈλΌμ°μ μ λ°μꡬμ μΌλ‘ λ°μ΄ν°λ₯Ό μ μ₯νλ μ μ₯μμ λλ€.
localStorage.setItem("theme", "dark");
localStorage.getItem("theme"); // "dark"
localStorage.removeItem("theme");
Session Storageλ ν λ¨μλ‘ μ μ§λλ μμ μ μ₯μμ
λλ€.
λΈλΌμ°μ νμ λ«μΌλ©΄ λ°μ΄ν°κ° μμ λ©λλ€.
sessionStorage.setItem("accessToken", "abc123");
sessionStorage.getItem("accessToken"); // "abc123"
sessionStorage.clear();
Cookieλ μλ²μ ν΄λΌμ΄μΈνΈκ° μλμΌλ‘ μ£Όκ³ λ°μ μ μλ λ°μ΄ν° μ‘°κ°μ λλ€.
max-age / expires)Secure, HttpOnly, SameSite λ±μΌλ‘ κ°ν κ°λ₯document.cookie = "user=Yujin; max-age=3600; path=/";
session_id)
κ°λ¨νκ² Local Storage, Session Storage, Cookieμ λν΄ μμλ΄€μΌλ μ΄μ μ€μ μ κ° μ΄μμ€μΈ Ddingsroom νλ‘μ νΈμμ μ΄λ»κ² μΈμ
μ μ μ§νλμ§ μ΄ν΄λ³΄λλ‘ νκ² μ΅λλ€.
Ddingsroomμ JWT κΈ°λ° ν΄λΌμ΄μΈνΈ μΈμ
κ΄λ¦¬λ₯Ό μ¬μ©νκ³ μμ΅λλ€. μ¦, μλ² μΈμ
μ΄ μλ ν ν° κΈ°λ° μΈμ¦ λ°©μμ
λλ€.
1οΈβ£ λ‘κ·ΈμΈ μ±κ³΅ μ μλ²μμ accessToken, refreshToken, userIdλ₯Ό λ°νν©λλ€.
2οΈβ£ ν΄λΌμ΄μΈνΈμμ μ΄λ₯Ό sessionStorageμ μ μ₯ν©λλ€.
3οΈβ£ Axios μΈμ€ν΄μ€κ° λͺ¨λ API μμ²μ Authorization ν€λλ₯Ό μλ μΆκ°ν©λλ€.
libs/
βββ api/
βββ instance.js β axios μΈμ€ν΄μ€ + μΈν°μ
ν°
βββ getUserId.js β ν ν° κΈ°λ° μ μ μλ³
βββ admin.js β κ΄λ¦¬μμ© API
stores/
βββ useTokenStore.js β Zustand μ μ μνλ‘ ν ν° κ΄λ¦¬
βββ useReservationStore.js
βββ useCommunityStore.js
import axios from 'axios';
const axiosInstance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
timeout: 10000,
});
axiosInstance.interceptors.request.use((config) => {
const accessToken = sessionStorage.getItem('accessToken');
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
});
-> μΈμ¦μ΄ νμν λͺ¨λ μμ²μ μλμΌλ‘ ν ν°μ΄ λΆμ΅λλ€.
axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
const { response } = error;
if (response && (response.status === 401 || response.status === 403)) {
sessionStorage.removeItem('accessToken');
}
return Promise.reject(error);
}
);
-> ν ν°μ΄ λ§λ£λλ©΄ μλμΌλ‘ μΈμ μ΄ μ’ λ£λμ΄ μ¬λ‘κ·ΈμΈμ μ λν©λλ€.
useTokenStore.js)import { create } from 'zustand';
const useTokenStore = create((set) => ({
accessToken: sessionStorage.getItem('accessToken') || '',
refreshToken: sessionStorage.getItem('refreshToken') || '',
userId: parseInt(sessionStorage.getItem('userId')) || null,
setAccessToken: (token) => {
set({ accessToken: token });
sessionStorage.setItem('accessToken', token);
},
clearTokens: () => {
set({ accessToken: '', refreshToken: '', userId: null });
sessionStorage.clear();
},
}));
-> Zustandμ sessionStorageλ₯Ό μλ°©ν₯ λκΈ°ννμ¬, μλ‘κ³ μΉ¨ν΄λ μΈμ
μ΄ μ μ§λ©λλ€.
Zustandμ rehydrate()λ‘ μΈμ
μ μ§κ° κ°λ₯ν©λλ€.[μλ²]
β (λ‘κ·ΈμΈ μλ΅)
accessToken, refreshToken, userId
β
[ν΄λΌμ΄μΈνΈ]
ββββββββββββββββββββββββββββ
β sessionStorage β
β β accessToken β
β β refreshToken β
β β userId β
ββββββββββββββββββββββββββββ
β
βΌ
[useTokenStore.js] β Zustand μ μ μν
β
βΌ
[axiosInstance] β Authorization ν€λ μλ λΆμ°©
Ddingsroomμ JWT + Session Storage κΈ°λ° μΈμ
μ μ§ κ΅¬μ‘°λ₯Ό μ¬μ©ν©λλ€.
Axios μΈν°μ
ν°λ‘ μΈμ¦ ν€λ μλ λΆμ°©Zustandλ‘ μ μ μν λκΈ°νκ·Έλ¬λ μ΅κ·Ό μ€μ μλΉμ€ μ΄μ μ€ λ€μκ³Ό κ°μ μ¬μ©μ νΌλλ°±μ΄ μ μλμμ΅λλ€.
βμκΎΈ μμ½ λ΄μμ΄ μ¬λΌμ§λλ€.
μ λ μμ½νλλ° λ€μλ λ€μ΄μ 보λ μμ½μ΄ μ λμ΄ μμ΄μ λ무 κ³€λνμ΅λλ€.
μ€λλ λ κ·Έλ¬λ€μ. μ κ·Έλ° κ±΄κ°μ?β
μ΄ νΌλλ°±μ βμμ½μ΄ μ μμ μΌλ‘ μλ£λμ§ μμ κ²μ²λΌ 보μΈλ€βλ λ΄μ©μ΄μμ§λ§,
μλ² λ‘κ·Έλ₯Ό λΆμν΄λ³΄λ μμ½ API μμ²΄κ° λ°±μλλ‘ μ μ‘λμ§ μμκ±°λ, μΈμ¦ μ€ν¨(401) λ‘ μ²λ¦¬λ κ²½μ°μμ΅λλ€.
Access Tokenμ΄ λ§λ£λ μνμμ μ¬μ©μκ° μμ½ λ²νΌμ λλ₯΄λ©΄ μμ²μ΄ μλ²λ‘ μ λ¬λμ§ μκ±°λ 401 Unauthorizedλ‘ κ±°μ λμ§λ§, UIμλ λ³λ€λ₯Έ μ€λ₯ μλ΄κ° νμλμ§ μμ μ¬μ©μλ μμ½μ΄ λ μ€ μ°©κ°νκ² λ©λλ€.
κ²°κ΅ μμ½ λ°μ΄ν°κ° DBμ μ μ₯λμ§ μμ μ± μ¬λΌμ§λ νμμ΄ λ°μν κ²μ
λλ€.
μ΄λ₯Ό ν΄κ²°νκΈ° μν΄ νμ¬
Access Token μ¬μ κ°±μ (Preemptive Refresh)HttpOnly μΏ ν€ κΈ°λ° Refresh Token 보κ΄μ΄ κ°μ μ΄ μ μ©λλ©΄, μ¬μ©μλ ν ν° λ§λ£λ₯Ό μΈμνμ§ μκ³ λ μλΉμ€ λ΄μμ λ‘κ·ΈμΈ μνκ° λκΉ μμ΄ μ μ§λ κ² μ λλ€.
π‘ λ€μ ν¬μ€νΈμμλ μ΄
Refresh Tokenλ‘μ§μ μ€μ μ½λ λ 벨μμ μ΄λ»κ² ꡬννλμ§,Axios μΈν°μ ν°μNetwork InterceptorκΈ°λ°μΌλ‘ μμΈν λ€λ€λ³΄κ² μ΅λλ€.
λ‘컬 μ€ν 리μ§, μΈμ , μΏ ν€ κ° κ°λ μ λν΄ ν·κ°λ¦΄ λκ° μ’ μ’ μμλλ°, μ μ 리ν΄μ£Όμ μ μ΄λ²μμΌλ§λ‘ νμ€ν μ΄ν΄νκ² λ κ±° κ°μμ~ λν μ€μ λ‘ νλ‘μ νΈμμ μ΄λ€ λ°©μμΌλ‘ μΈμ κ΄λ¦¬λ₯Ό νκ³ μλμ§μ λν μ€λͺ λ ν₯λ―Έλ‘μ μ΅λλ€! μ΄λ€ λ°©μμΌλ‘ λ¬Έμ λ₯Ό ν΄κ²°νμ€μ§ κΆκΈνλ€μ γ .γ μ μ½μμ΅λλ€!