next.js + sequelize 연결한 회원가입, 로그인 만들기

개발공부·2022년 12월 14일
0

* 결과

1) 회원가입

2) 로그인

3) 로그아웃 후 다른 아이디로 로그인

* node.js backend와 연결 시 주소 입력

-> axios 호출 시 여러 번 같은 주소 써야 함(redux-saga)
-> redux-saga에서 baseURL 방법

import { all } from "redux-saga/effects";
import { userSagas } from "./userSaga";
import { wordSagas } from "./wordSaga";
import { postSagas } from "./postSaga";
import { gameSaga } from "./gameSaga";
import axios from "axios";

axios.defaults.baseURL = "http://localhost:3005"; //서버 포트

export default function* rootSaga() {
  yield all([...userSagas, ...wordSagas, ...postSagas, ...gameSaga]);
}

* passport, passport-local

  • session

    npm i express-session

  • cookie-parser

    npm i cookie-parser

  • 로그인 하면 로그인을 누가 했는지 보내줘야 함

  • 로그인 관련 정보를 직접 보내주면 보안에 취약
    -> 랜덤한 값 보냄 = cookie
    -> 서버는 session과 연결(정보를 모두 들고 있으면 무거움)
    -> id만 가져오게 함(DB에서 데이터 복구함)

* back(server) 부분

[server.js]

const express = require("express");
const cors = require("cors");
const session = require("express-session");
const cookieParser = require("cookie-parser");
const passport = require("passport");
const morgan = require("morgan");
const dotenv = require("dotenv");
const hpp = require("hpp");
const helmet = require("helmet");

const db = require("./models");
const passportConfig = require("./passport");
const wordRouter = require("./routes/word");
const userRouter = require("./routes/user");

dotenv.config();
const app = express();

//시퀄라이즈 연결
db.sequelize
  .sync({ force: false }) //true면 서버 재시작 할 때마다 mysql 지워짐(default를 false로 고정할 것)
  .then(() => {
    console.log("DB 연결 성공");
  })
  .catch(console.error);

if (process.env.NODE_ENV === "production") {
  app.use(morgan("combined"));
  app.use(hpp());
  app.use(helmet());
  app.use(
    cors({
      origin: "나중에 연결할 도메인 이름"
      credentials: true,
    })
  );
} else {
  app.use(morgan("dev"));
  app.use(
    cors({
      origin: "http://localhost:3000",
      credentials: true,
    })
  );
}
 
passportConfig();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser(process.env.COOKIE_SECRET)); //쿠키 연결
app.use(
  session({ //세션 연결
    saveUninitialized: false,
    resave: false,
    secret: process.env.COOKIE_SECRET,
  })
);
app.use(passport.initialize());
app.use(passport.session());

app.get("/", (req, res) => {
  res.send("hello express");
});

app.use("/word", wordRouter);
app.use("/user", userRouter);

app.listen(3005, () => {
  console.log("The server is running at port 3005");
});

[password/index.js]

const passport = require("passport");
const local = require("./local");
const { User } = require("../models"); //sequelize로 만든 모델 User

module.exports = () => {
  passport.serializeUser((user, done) => {
    done(null, user.id); //유저 아이디만 가져옴(다 갖고오면 정보가 너무 크기 때문)
  });

  passport.deserializeUser(async (id, done) => {
    try {
      const user = await User.findOne({ where: { id } });
      done(null, user); // req.user
    } catch (error) {
      console.error(error);
      done(error);
    }
  });

  local();
};

[password/local.js]

const passport = require("passport");
const { Strategy: LocalStrategy } = require("passport-local");
const bcrypt = require("bcrypt"); //비밀번호 암호화
const { User } = require("../models");  //sequelize로 만든 모델 User

module.exports = () => {
  passport.use(
    new LocalStrategy(
      {
        usernameField: "email",
        passwordField: "password",
      },
      async (email, password, done) => {
        try {
          const user = await User.findOne({
            where: { email },
          });
          if (!user) {
            return done(null, false, { reason: "존재하지 않는 이메일입니다!" });
          }
          const result = await bcrypt.compare(password, user.password);
          if (result) {
            return done(null, user);
          }
          return done(null, false, { reason: "비밀번호가 틀렸습니다." });
        } catch (error) {
          console.error(error);
          return done(error);
        }
      }
    )
  );
};
  • 특정 에러를 띄워주고 싶으면
    에러 처리 미들웨어 별도로 분리

* 로그아웃 에러

next-dev.js?3515:20 A non-serializable value was detected in an action, in the path: payload. Value: AxiosError {message: 'Request failed with status code 401', name: 'AxiosError', code: 'ERR_BAD_REQUEST', config: {…}, request: XMLHttpRequest, …}
Take a look at the logic that dispatched this action: {type: 'user/logoutFailure', payload: AxiosError, @@redux-saga/SAGA_ACTION: true}
(See https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants)
(To allow non-serializable values see: https://redux-toolkit.js.org/usage/usage-guide#working-with-non-serializable-data)

변경 전
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(sagaMiddleware),
serializableCheck: false 

변경 후
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({ serializableCheck: false }).concat(sagaMiddleware),

* 로그아웃 에러 2(passport@0.6 version)

변경 전
router.post("/logout", (req, res) => {
  req.logout();
  req.session.destroy();
  res.send("ok");
})


변경 후
router.post("/logout", (req, res) => {
  req.logout((err) => {
    req.session.destroy();
    if (err) {
      res.redirect("/");
    } else {
      res.status(200).send("server ok: 로그아웃 완료");
    }
  });
});

* front 부분

[userSaga.js]

import axios from "axios";

function logInAPI(data) {
  return axios.post("/user/login", data);
}

function* logIn(action) {
  try {
    const data = action.payload;
    const result = yield call(logInAPI, data);
    console.log("result", result);
    yield put(loginSuccess(result.data));
  } catch (error) {
    yield put(loginFailure(error));
    console.log(error);
  }
}

function logOutAPI() {
  return axios.post("/user/logout");
}

function* logOut() {
  try {
    yield call(logOutAPI);
    yield put(logoutSuccess());
  } catch (error) {
    yield put(logoutFailure(error));
    console.log(error);
  }
}

function signUpAPI(data) {
  return axios.post("/user", data);
}

function* signUp(action) {
  try {
    const data = action.payload;
    const result = yield call(signUpAPI, data);
    console.log("result", result);
    yield put(signupSuccess());
    // yield call(signupSuccess, data);
  } catch (error) {
    yield put(signupFailure(error));
    console.log(error);
  }
}

function* login_Req() {
  yield takeLatest(loginRequest.type, logIn);
}

function* logout_Req() {
  yield takeLatest(logoutRequest.type, logOut);
}

function* signup_Req() {
  yield takeLatest(signupRequest.type, signUp);
}

export const userSagas = [fork(login_Req), fork(logout_Req), fork(signup_Req)];

[userSlice.js - redux-toolkit]

//회원가입
  signupSuccess: (state) => {
      state.me = null;
      state.signupLoading = false;
      state.signupComplete = true;
    },
//로그인
   loginSuccess: (state, action) => {
      state.loginLoading = false;
      state.loginComplete = true;
      state.me = action.payload;
      console.log("state.me", state.me);
    },
//로그아웃
 logoutSuccess: (state) => {
      state.me = null;
      state.logoutLoading = false;
      state.logoutComplete = true;
    },
profile
개발 블로그, 티스토리(https://ba-gotocode131.tistory.com/)로 갈아탐

0개의 댓글