본문 바로가기

React

[React] Mongo + Node + React 회원가입, 로그인

반응형

 

Mongo, NodeJs, React를 이용해서 회원가입 로그인을 만들어보쟈

 

최종 목표는 유튜브 클론코딩이다!

 

2020.10.15 - [React] - React와 Express연동하기 1

 

React와 Express연동하기 1

준비 과정 1. CRA npx create-react-app tistory(파일명) 2. npm start Express 설치 1. Express설치 npm install express --save --save는 안붙혀도 된다. --save의 뜻은 종속 항목 목록에 저장한다는 뜻이다. --..

coding-hyeok.tistory.com

2020.10.17 - [React] - React - Mysql 연동하기, 연결(데이터 저장)

 

React - Mysql 연동하기, 연결(데이터 저장)

1. 준비 mysql mysql workbench를 사용할 것이다. 2. database 만들기 및 express에 연결하기 database와 table을 만들어주자. 나는 tistory라는 database와 test라는 table을 만들거다. CREATE DATABASE tistory;..

coding-hyeok.tistory.com

 

이 두개는 기본이고 몽고까지 연결하고 와야 이해가 될 것이다

 


폴더구조

 

server/index.js

// server/index.js
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser");
const config = require("./config/key");

const { auth } = require("./middleware/auth");
const { User } = require("./models/User");

//서버에서 가져온 데이터를 파싱해서 가져와준다.
app.use(bodyParser.urlencoded({ extended: true }));

app.use(bodyParser.json());
app.use(cookieParser());

const mongoose = require("mongoose");
mongoose
  .connect(config.mongoURI, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    //useCreateIndex: true,
    //useFindAndModify: false,
  })
  .then(() => console.log("MongoDB Connected ... "))
  .catch((err) => console.log(err));

app.get("/", (req, res) => {
  res.send("Hello World!");
});

//회원가입
app.post("/api/users/register", (req, res) => {
  const user = new User(req.body);

  user.save((err, userInfo) => {
    if (err) return res.json({ success: false, err });
    return res.status(200).json({
      success: true,
    });
  });
});

//로그인
app.post("/api/users/login", (req, res) => {
  //1. 요청된 이메일이 데이터베이스에 있는 지 찾는다.
  User.findOne({ email: req.body.email }, (err, user) => {
    if (!user) {
      return res.json({
        loginSuccess: false,
        message: "이메일에 해당하는 유저가 없습니다.",
      });
    }
    //2. 1조건 충족 시, 비번 맞는 지 확인
    user.comparePassword(req.body.password, (err, isMatch) => {
      if (!isMatch)
        return res.json({
          loginSuccess: false,
          message: "비밀번호가 틀렸습니다.",
        });
      //3. 2조건 충족 시, 토큰 생성
      user.generateToken((err, user) => {
        if (err) return res.status(400).send(err);
        //토큰을 저장한다. where? 쿠키 OR 로컬 스토리지 OR 세션스토리지
        //쿠키 name : value
        res
          .cookie("x_auth", user.token)
          .status(200)
          .json({ loginSuccess: true, userId: user._id });
      });
    });
  });
});

//Authentication 자격 (관리자화면, 비회원화면)
//auth는 미들웨어
app.get("/api/users/auth", auth, (req, res) => {
  //여기까지 오면 미들웨어를 통과했다는 거임
  //그렇다면 Auth가 True라는 뜻
  res.status(200).json({
    _id: req.user._id,
    // 0> 일반 유저 ^ 나머지 관리자
    isAdmin: req.user.role === 0 ? false : true,
    isAuth: true,
    email: req.user.email,
    name: req.user.name,
    lastname: req.user.lastname,
    role: req.user.role,
    image: req.user.image,
  });
});

//LogOut
app.get("/api/users/logout", auth, (req, res) => {
  User.findOneAndUpdate({ _id: req.user._id }, { token: "" }, (err, user) => {
    if (err) return res.json({ success: false, err });
    return res.status(200).send({ success: true });
  });
});

const port = 5000;

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`);
});

 

간단한 회원가입과 로그인시 아이디 검사 > 비번검사를 했다.

그리고 로그인시 쿠키생성해서 토큰을 주고 로그아웃 시에는 토큰이 없어지게 해따

 

 

 

server/models/User.js

//server/models/User.js

const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const saltRounds = 10;
const jwt = require("jsonwebtoken");

const userSchema = mongoose.Schema({
  name: {
    type: String,
    maxlength: 150,
  },
  email: {
    type: String,
    trim: true,
    unique: 1,
  },
  password: {
    type: String,
    minlength: 5,
  },
  lastname: {
    type: String,
    maxlength: 150,
  },
  role: {
    type: Number,
    default: 0,
  },
  image: String,
  token: {
    type: String,
  },
  //토큰 유효기간
  tokenExp: {
    type: Number,
  },
});

//mongoose 기능 pre  > save 전에 뭘한다
userSchema.pre("save", function (next) {
  var user = this;
  if (user.isModified("password")) {
    //비밀번호 암호화 bcrypt
    //salt 생성 (saltRounds= 10)
    bcrypt.genSalt(saltRounds, function (err, salt) {
      if (err) return next(err);
      //this.password = myPlaintextPassword
      bcrypt.hash(user.password, salt, function (err, hash) {
        // Store hash in your password DB.
        if (err) return next(err);
        user.password = hash;
        next();
      });
    });
  } else {
    next();
  }
});

userSchema.methods.comparePassword = function (plainPassword, cb) {
  // plainPassword : 123456 / 암호화된 비번 : #!@#1241@$1~!asd
  //plainPassword 암호화 해서 비교한다
  bcrypt.compare(plainPassword, this.password, function (err, isMatch) {
    if (err) return cb(err);
    cb(null, isMatch);
  });
};

userSchema.methods.generateToken = function (cb) {
  var user = this;
  //jsonwebToken을 이용하여 토큰 생성 user._id는 mongo id
  // user._id + 'secretToken' = token
  //jwt.sign(payload, secretKey)이 기대값
  //user_.id는 문자열이 아니기 때문에 .toHexString으로 24바이트 16진수 문자열로 바꿔줌?
  var token = jwt.sign(user._id.toHexString(), "secretToken");
  user.token = token;
  user.save(function (err, user) {
    if (err) return cb(err);
    cb(null, user);
  });
};

userSchema.statics.findByToken = function (token, cb) {
  var user = this;

  //token decode
  jwt.verify(token, "secretToken", function (err, decoded) {
    //유저 아이디를 이용해서 유저를 찾은 다음에
    //클라이언트에서 가져온 토큰과 디비에 보관된 토큰이 일치하는지 확인
    user.findOne({ _id: decoded, token: token }, function (err, user) {
      if (err) return cb(err);
      cb(null, user);
    });
  });
};

const User = mongoose.model("User", userSchema);

module.exports = { User };

bcrypt라이브러리를 사용하여 사용자 정보를 저장하기 전에 비번을 암호화 했다. (pre부분)

 

그리고 findByToken은 유저 아이디를 이용해서 유저를 찾고 클라이언트에서 가져온 토큰과 디비에 보관된 토큰이 일치하는 지 확인해서

유저가 로그인했는지 안했는지를 판단한다.

 

generateToken : 토큰 생성 

jwt.token(payload, secretKey)가 기대값이다

user_.id는 문자열이 아니기 때문에 .toHexString()으로 문자열로 변환시켜줘서 토큰을 생선한다

(toHexString 없으면 오류납뉘당)

 

comparePassword : 암호화해서 비밀번호가 맞는지 확인하는 부분

 

 

 

server/middleware/auth.js

const { User } = require("../models/User");

let auth = (req, res, next) => {
  //인증 처리를 하는 곳

  //1. 클라이언트 쿠키에서 토큰을 가져온다.
  let token = req.cookies.x_auth;

  //2. 토큰을 복호화한다 > 유저를 찾는다.
  User.findByToken(token, (err, user) => {
    if (err) throw err;
    if (!user) return res.json({ isAuth: false, error: true });

    req.token = token;
    req.user = user;
    next();
  });
  //3. 2조건 만족시 Okay

  //4. 2조건 불만족시 NO
};

module.exports = { auth };

인증처리하는 부분이답

 

 

 

 

여기까지가 서버부분(백엔드)이다.


이제 클라이언트(프론트엔드)로 가보쟈

 

폴더 및 파일 구조는 요렇다

 

registerpage.js

import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { registerUser } from "../../../_actions/user_actions";
import { useNavigate } from "react-router-dom";

function RegisterPage(props) {
  const dispatch = useDispatch();
  const navigate = useNavigate();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [name, setName] = useState("");
  const [confirmPassword, setConfirmPassword] = useState("");

  const onEmailHandler = (e) => {
    setEmail(e.target.value);
  };
  const onNamedHandler = (e) => {
    setName(e.target.value);
  };
  const onPasswordHandler = (e) => {
    setPassword(e.target.value);
  };
  const onConfirmPasswordHandler = (e) => {
    setConfirmPassword(e.target.value);
  };
  const onSubmitHandler = (e) => {
    //새로고침 방지
    e.preventDefault();
    if (password !== confirmPassword) {
      return alert("비밀번호와 비밀번호확인은 같아야 합니다.");
    }
    let body = {
      email: email,
      name: name,
      password: password,
    };
    dispatch(registerUser(body)).then((response) => {
      if (response.payload.success) {
        navigate("/login");
      } else {
        alert("Failed to sign up");
      }
    });
  };
  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        width: "100%",
        height: "100vh",
      }}
    >
      <form
        style={{ display: "flex", flexDirection: "column" }}
        onSubmit={onSubmitHandler}
      >
        <label>Email</label>
        <input type="email" value={email} onChange={onEmailHandler} />

        <label>Name</label>
        <input type="text" value={name} onChange={onNamedHandler} />

        <label>Password</label>
        <input type="password" value={password} onChange={onPasswordHandler} />

        <label>Password Confirm</label>
        <input
          type="password"
          value={confirmPassword}
          onChange={onConfirmPasswordHandler}
        />

        <br />
        <button type="submit">Sign UP</button>
      </form>
    </div>
  );
}

export default RegisterPage;

 

보면 알겟지만 redux를 사용했다

 

client/src/_action/user_actions.js

import axios from "axios";
import { LOGIN_USER, REGISTER_USER, AUTH_USER } from "./types";

//로그인
export function loginUser(dataTosubmit) {
  const request = axios
    .post("/api/users/login", dataTosubmit)
    .then((response) => response.data);
  return {
    //
    type: LOGIN_USER,
    payload: request,
  };
}

//회원가입
export function registerUser(dataTosubmit) {
  const request = axios
    .post("/api/users/register", dataTosubmit)
    .then((response) => response.data);
  return {
    type: REGISTER_USER,
    payload: request,
  };
}

//인증처리
export function auth() {
  const request = axios
    .get("/api/users/auth")
    .then((response) => response.data);
  return {
    type: AUTH_USER,
    payload: request,
  };
}

 

axios를 통해 서버와 통신했다

 

client/src/_reducers/user_reducers.js

import { LOGIN_USER, REGISTER_USER, AUTH_USER } from "../_actions/types";

export default function (state = {}, action) {
  switch (action.type) {
    case LOGIN_USER:
      return { ...state, loginSuccess: action.payload };
      break;
    case REGISTER_USER:
      return { ...state, register: action.payload };
      break;
    case AUTH_USER:
      return { ...state, userData: action.payload };
      break;
    default:
      return state;
  }
}

 

 

마지막으로 로그인

import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { loginUser } from "../../../_actions/user_actions";
import { useNavigate } from "react-router-dom";

function LoginPage(props) {
  const dispatch = useDispatch();
  const navigate = useNavigate();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const onEmailHandler = (e) => {
    setEmail(e.target.value);
  };
  const onPasswordHandler = (e) => {
    setPassword(e.target.value);
  };
  const onSubmitHandler = (e) => {
    //새로고침 방지
    e.preventDefault();
    let body = {
      email: email,
      password: password,
    };
    dispatch(loginUser(body)).then((response) => {
      if (response.payload.loginSuccess) {
        navigate("/");
      } else {
        alert("ERROR");
      }
    });
  };
  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        width: "100%",
        height: "100vh",
      }}
    >
      <form
        style={{ display: "flex", flexDirection: "column" }}
        onSubmit={onSubmitHandler}
      >
        <label>Email</label>
        <input type="email" value={email} onChange={onEmailHandler} />

        <label>Password</label>
        <input type="password" value={password} onChange={onPasswordHandler} />

        <br />
        <button type="submit">Login</button>
      </form>
    </div>
  );
}

export default LoginPage;

 

 

요렇게하면

 

회원가입
mongo DB

서버에 잘 저장 되는 걸 볼 수 있다.

 

로그인
mongo DB

로그인하면 토큰이 잘 생성되는 것을 볼 수 있따!

 

 

 

끝!!

 

 

 

 

 

 


 

 

 

취업하고 싶다ㅏ~~~

취준생들 화팅~! 모두다 열코염

반응형