I T H

[MySchedule project] 7. JWT(json web token) / 로그인 로직(2) 본문

React + Node.js

[MySchedule project] 7. JWT(json web token) / 로그인 로직(2)

thdev 2024. 1. 30. 15:27
이번 챕터는 로그인 로직 2번째 - 백앤드로부터 받아온 토큰을 headers의 Authorization에 넣어 백앤드로 보내고 백앤드에서 지정해놓은 secret key를 이용해 유효한 토큰인지 확인한 후 데이터를 프론트로 전달하는 단계까지 하도록 하겠다.
- 로그인 뿐만 아니라 인증이 필요한 경우가 많으므로, 로그인 후에 인증요청시 토큰을 보내서 백앤드에서 유효한 토큰인지 확인하는 과정이다.

 

- Frontend

[ App.tsx ]

 

- redux store에 저장된 데이터를 가져오기 위해 useSelector를 사용한다. 타입을 미리 지정해놓았기 때문 useAddSelector를 통해 isAuth를 불러와서 사용하도록 하였다.

- isAuth 부분이 true가 되었다면 로그인이 완료되었다는 뜻이므로, 로그인이 정상적으로 되었다면 useEffect부분을 탈수 있도록 의존성배열 [ ] 에 isAuth를 추가해준다.

- pathName은 useLocation에서 불러온 현재주소 경로인데 주소가 바뀔때마다 authCheck()를 타게 의존성배열에 추가해주었다.

  const isAuth = useAddSelector((state) => state.user?.isAuth);
  const role = useAddSelector((state) => state.user?.userData?.role);

  const dispatch = useAddDispatch();
  const { pathname } = useLocation(); //현재주소 경로
  //인증이 잘되었는지 체크
  useEffect(() => {
    if (isAuth) {
      dispatch(authCheck());
    }
  }, [isAuth, dispatch, pathname]);

 

[ src / utils / axios.js ]

 

- thunkFunction으로 넘어가기에 앞서 axiosInstance부분에서 config.headers의 Authorization에 토큰을 넣는 부분을 추가해 준다.

- 토큰이 만료되었을때 처리하는 로직도 넣어주었음.

axiosInstance.interceptors.request.use(
  //전처리
  function (config) {
    config.headers.Authorization =
      "Bearer " + localStorage.getItem("accessToken");

    return config;
  },
  function (error) {
    return Promise.reject(error);
  }
);

//토큰이 만료되었을때 처리하는 로직
axiosInstance.interceptors.response.use(
  //후처리
  function (response) {
    return response;
  },
  function (error) {
    if (error.response.data === "jwt expired") {
      //jwt expired => 토큰이 만료되었다면
      window.location.reload(); //페이지 리프레시
    }
    return Promise.reject(error);
  }
);

 

 

[ src / store / thunkFunction.tsx ]

 

- 백앤드로 headers의 Authorization에 넣은 토큰을  전달해준다.

//인증이 되어있는지 체크하는 로직
//인증된 토큰이면 백앤드에서 미들웨어 auth를 만들어서 유저데이터를 사용할수있도록 처리
export const authCheck = createAsyncThunk(
  "user/authCheck",
  async (_, thunkAPI) => {
    try {
      const response = await axiosInstance.get(`/authCheck`);

      return response.data;
    } catch (error: any) {
      console.log(error);
      return thunkAPI.rejectWithValue(error.response.data || error.message);
    }
  }
);

 

- Backend

[ src / index.js]

 

- 경로 추가

//route start

app.use("/authCheck", require("./routes/auth"));

//route end

 

[ src / middleware / auth.js]

 

- front에서 headers에 넣은 토큰이 유효한 토큰인지 확인해주는 미들웨어

const jwt = require("jsonwebtoken");
const User = require("../models/User");

let auth = async (req, res, next) => {
  //토큰을 request headers에서 가져오기
  const authHeader = req.headers["authorization"];

  //Bearer eyJhbGci.......

  const token = authHeader && authHeader.split(" ")[1];
  if (token === null) {
    return res.sendStatus(401);
  }
  try {
    //토큰이 유효한 토큰인지 확인
    const decode = jwt.verify(token, process.env.JWT_SECRET); // 백앤드에서 설정한 SECRET키와 토큰에 있는 SECRET키와 같은지 확인
    const user = await User.findOne({ userId: decode.userId });
    if (!user) {
      return res.status(400).send("없는 유저입니다.");
    }
    req.user = user; //토큰이 유효하면 req.user에 데이터를 넣어준다.
    next();
  } catch (error) {
    next(error);
  }
};

module.exports = auth;

 

[ routes / auth.js]

 

- 위에서 만들어놓은 미들웨어 auth를 넣어주었다. 미들웨어 부분에서 토큰이 유효하다면 req.user에 해당 유저데이터를 넣어주었기에 아래와 같이 json 형식으로 데이터를 front 쪽으로 보낼수 있다.

const express = require("express");
const auth = require("../middleware/auth");
const router = express.Router();

//토큰이 올바른 토큰인지 확인 -> auth미들웨어를 통해 확인 -> auth에서 유저데이터를 담아줌
router.get("/", auth, async (req, res, next) => {
  return res.json({
    userId: req.user.userId,
    userName: req.user.userName,
    userPhone: req.user.userPhone,
    userEmail: req.user.userEmail,
    userImage: req.user.userImage,
    role: req.user.role,
  });
});

module.exports = router;

 

- Frontend

[ src/ store / userSlice.tsx ]

 

- 백앤드에서 받아온 데이터를 리덕스 스토어에 넣어주는 부분

.addCase(authCheck.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(authCheck.fulfilled, (state, action) => {
        state.isLoading = false;
        state.userData = action.payload;
        state.isAuth = true;
      })
      .addCase(authCheck.rejected, (state, action: any) => {
        state.isLoading = false;
        state.error = action.payload;
        state.isAuth = false;
        localStorage.removeItem("accessToken"); //토큰지워주기
      })

 

[ 결과화면 ]

토큰을 headers에 넣어서 백앤드로 보내는 과정

 

백앤드에서 보낸 데이터를 리덕스 스토어에 정상 저장됨.