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 ();
}

태그:

카테고리:

업데이트:

댓글남기기