논문 시스템의 고도화 프로젝트(1): JWT 토큰을 Localstorage에서 저장하고 Redux로 관리하기
1. 했던 것과 하려는 것
-
클라이언트에서 서버로 아이디와 비밀번호를 넘기면, mongoDB에서 조회. 정보가 일치하면, jsonwebtoken을 라이브러리를 통해 생성한 토큰이 클라이언트로 전송.
// root/api/login/route.ts ... // 토큰에는 유저 정보(id) 등이 보관 const token = jwt.sign( { id: user.id }, JWT_SECRET_KEY, { expiresIn: '1h' } ); // 성공 시, 토큰과 user.id 반환 return NextResponse.json({ type: 'success', message: '로그인 성공', id: user.id, token }); ...
-
redux-toolkit과 redux-persist로 localstorage에 토큰과 로그인 되어있는지 상태를 저장하고 관리하려고 함.
2. 구현 방법과 쟁여둘 나의 코드
순서 1. 스토어(root/store/index.ts) 설정
// root/store/index.ts
import { configureStore } from '@reduxjs/toolkit';
// 인증 상태를 관리하는 authSlice. 순서2에서 구현
import authSlice from './slices/authSlice';
// JWT 토큰을 로컬 스토리지에 저장하고, 페이지 새로고침 시에도 상태를 유지하기 위해 redux-persist를 사용
import {
persistStore,
persistReducer,
FLUSH,
REHYDRATE,
PAUSE,
PERSIST,
PURGE,
REGISTER,
} from 'redux-persist';
import storage from 'redux-persist/lib/storage'; // localStorage를 저장소로 사용
// persistConfig 객체를 통해 persistReducer에 전달할 설정을 정의
// key: 'root'는 persist된 state의 최상위 키를 의미
// storage: localStorage를 사용하여 상태를 저장
const persistConfig = {
key: 'root',
storage
};
// authSlice를 지속 가능하게 변환하기 위해 persistReducer를 사용
// persistReducer는 authSlice 리듀서를 감싸고, persistConfig 설정을 적용
const persistedReducer = persistReducer(persistConfig, authSlice);
// Redux 스토어를 생성
const store = configureStore({
reducer: {
// auth 상태를 관리하는 리듀서로 persistedReducer를 설정
auth: persistedReducer,
},
// Redux 미들웨어를 설정합니다.
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
// redux-persist 관련 액션들을 직렬화 검사에서 제외하여 경고를 방지
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
});
// persistor 객체를 생성하여, 스토어의 상태를 지속적으로 관리
// 이 객체를 사용하여 애플리케이션의 상태를 다시 로드할 때, 로컬 스토리지에 저장된 상태를 복원
export const persistor = persistStore(store);
// RootState 타입은 Redux 스토어의 전체 상태의 타입을 정의
export type RootState = ReturnType<typeof store.getState>;
// AppDispatch 타입은 Redux 디스패치 함수의 타입을 정의
export type AppDispatch = typeof store.dispatch;
export default store;
순서 2. Token과 isLogin을 관리하는 슬라이스(root/store/slices/authSlice.ts) 설정
// root/store/slices/authSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
// AuthState 인터페이스는 인증 상태를 정의
// token: JWT 토큰을 저장하는 문자열 또는 null 값
// isLogin: 사용자가 로그인했는지 여부를 나타내는 불리언 값
interface AuthState {
token: string | null;
isLogin: boolean;
}
// initialState는 AuthState 인터페이스를 기반으로 초기 상태를 정의
// token은 null로 초기화되고, isLogin은 false로 초기화
const initialState: AuthState = {
token: null,
isLogin: false
}
// createSlice는 Redux 슬라이스를 생성하는 함수
// authSlice는 'auth'라는 이름을 가진 슬라이스로, 인증 관련 상태와 리듀서를 관리
const authSlice = createSlice({
name: 'auth', // 슬라이스의 이름
initialState, // 초기 상태
reducers: {
// setToken 리듀서는 상태에 JWT 토큰을 설정하고, 토큰의 존재 여부에 따라 isLogin 값을 업데이트
setToken(state, action: PayloadAction<string | null>) {
state.token = action.payload; // 토큰을 상태에 저장
state.isLogin = !!action.payload; // 토큰이 있으면 isLogin을 true로, 없으면 false로 설정
},
// clearToken 리듀서는 상태에서 JWT 토큰을 제거하고, isLogin을 false로 설정
clearToken(state) {
state.token = null; // 토큰을 null로 설정하여 제거
state.isLogin = false; // 로그아웃 상태로 설정
}
}
})
// 슬라이스에서 생성된 액션들을 내보냄
// setToken: 토큰을 설정하는 액션
// clearToken: 토큰을 제거하는 액션
export const { setToken, clearToken } = authSlice.actions;
// 이 리듀서는 Redux 스토어에 추가되어 상태 관리를 담당하게 됨
export default authSlice.reducer;
순서 3. Redux 스토어를 React 컴포넌트에 주입
// root/store/provider.tsx
"use client";
import store, { persistor } from "./index"; // Redux 스토어 및 persistor 객체를 불러옴
import { Provider } from "react-redux"; // Redux 스토어를 React 컴포넌트 트리에 주입하기 위해 사용
import React from "react"; // React를 사용하기 위해 import
import { PersistGate } from "redux-persist/integration/react"; // PersistGate를 사용하여 Redux 상태를 복원
// ReduxProvider 컴포넌트는 자식 컴포넌트들이 Redux 스토어에 접근할 수 있도록 Provider로 감싸줌
export function ReduxProvider({ children } : { children: React.ReactNode }) {
return (
// Redux의 Provider로 감싸서 모든 하위 컴포넌트들이 Redux 스토어에 접근할 수 있게 함
<Provider store={store}>
{/* PersistGate는 Redux 상태가 로컬 스토리지에서 복원될 때까지 로딩 상태를 유지하게 함 */}
<PersistGate loading={null} persistor={persistor}>
{/* 자식 컴포넌트들이 Redux 스토어에 접근 가능 */}
{children}
</PersistGate>
</Provider>
);
}
순서 4. 서버에서 받은 JWT 토큰을 Redux 스토어에 저장
// root/app/login/page.tsx
"use client";
import { useDispatch, UseDispatch } from 'react-redux';
import { setToken } from '@/store/slices/authSlice';
export default function Home() {
const dispatch = useDispatch();
const onFinish: FormProps<FieldType>['onFinish'] = (values) => {
axios.post('/api/login', { ...values })
// 로그인 성공
.then(response => {
// JWT 토큰을 전역 상태로 저장
const token = response.data.token;
dispatch(setToken(token));
})
.catch(error => {
});
};
return ();
}
순서 5. Redux 스토에어 저장된 전역 상태 불러오기
// root/app/page.tsx
"use client";
import { useSelector } from 'react-redux';
import { RootState } from '@/store'; // Redux 상태를 안전하게 관리하기 위해 사용
export default function Home() {
const isLogin = useSelector((state: RootState) => state.auth.isLogin);
const token = useSelector((state: RootState) => state.auth.token);
useEffect(()=>{
console.log(isLogin);
console.log(token);
}, [isLogin])
return ();
}
댓글남기기