인증 모듈 및 JWT를 사용한 Nuxt.js 및 Express.js 인증

안녕하세요, 오늘은 Nuxt.js 및 Express.js로 인증 앱을 빌드하려고 합니다. 프론트엔드 및 nuxt auth 모듈에 중점을 두고 local 인증을 만들 것입니다.
앱에 인증을 추가하고 싶었을 때 적절한 리소스를 찾을 수 없었기 때문에 앱에 갇혀 있었던 것을 기억합니다.
그래서 다른 분들이 사용하시고 저처럼 고생하지 않으셨으면 하는 마음에 이 글을 작성하게 되었습니다.
이 기사는 풀스택 개발자와 프론트엔드 개발자를 위해 작성되었으므로 풀스택 웹 개발자가 아니더라도 걱정하지 마세요. 앱에 인증을 추가할 수 있는 모든 것을 설명하겠습니다.

이 응용 프로그램은 백엔드용 새로 고침 토큰을 지원하지 않습니다. 프론트엔드용은 지원하지만 백엔드에서 설정하지 않았기 때문에 주석 처리되었으며 프론트엔드에서 설정하면 오류가 발생합니다. 즉, 백엔드용입니다. 끝은 액세스 토큰에 관한 것입니다. 새로 고침 토큰도 갖고 싶다면 이 게시물의 마지막 줄에 주의하십시오.

시작하기 전에 다음 기술에 대해 알아야 합니다.


뷰 및 Nuxt.js


Express.js 및 Mongodb(API도 구현하려는 경우)

그래서 시작하자

Nuxt.js



1- npx create-nuxt-app front로 새 앱 만들기
2- 새 앱을 만들 때 Axios 모듈을 선택하십시오(걱정하지 않으셨다면 나중에 설치하겠습니다)
3- nuxt 모듈과 Axios를 설치하지 않은 경우 설치yarn add --exact @nuxtjs/auth-nextyarn add @nuxtjs/axios또는 npm으로npm install --save-exact @nuxtjs/auth-nextnpm install @nuxtjs/axios
그런 다음 아래와 같이 nuxt.config.js에 추가하십시오.

{
  modules: [
    '@nuxtjs/axios',
    '@nuxtjs/auth-next'
  ],
  auth: {
    // Options
  }
}


이제 auth 모듈에 옵션을 추가할 시간입니다.

  auth: {
    strategies: {
      local: {
//      scheme: "refresh",
        token: {
          property: "token", //property name that the Back-end sends for you as a access token for saving on localStorage and cookie of user browser
          global: true,
          required: true,
          type: "Bearer"
        },
        user: {
          property: "user",
          autoFetch: true
        },
//      refreshToken: {  // it sends request automatically when the access token expires, and its expire time has set on the Back-end and does not need to we set it here, because is useless
//        property: "refresh_token", // property name that the Back-end sends for you as a refresh token for saving on localStorage and cookie of user browser
//        data: "refresh_token", // data can be used to set the name of the property you want to send in the request.
//      },
        endpoints: {
          login: { url: "/api/auth/login", method: "post" },
//        refresh: { url: "/api/auth/refresh-token", method: "post" },
          logout: false, //  we don't have an endpoint for our logout in our API and we just remove the token from localstorage
          user: { url: "/api/auth/user", method: "get" }
        }
      }
    }
  },


다음은 사용하도록 권장하는 Axios의 구성입니다.

  axios: {
    baseURL: "http://localhost:8080"  // here set your API url
  },


팁: 백엔드 프로젝트에서 새로 고침 토큰을 구현한 경우 refresh 끝점의 주석 처리를 제거하고 백엔드에서 제공한 올바른 끝점으로 변경해야 하며 또한 이러한 refreshToken 개체 및 scheme: refresh의 주석 처리를 제거해야 합니다.

이제 몇 가지 구성 요소를 만듭니다.
/components/Auth/Login/index.vue
<template>
  <div>
    <form @submit.prevent="login">
      <div class="mb-3">
        <label for="email" class="form-label">Email address</label>
        <input
          type="email"
          class="form-control"
          id="email"
          v-model="loginData.email"
          aria-describedby="emailHelp"
        />
      </div>
      <div class="mb-3">
        <label for="password" class="form-label">Password</label>
        <input
          type="password"
          v-model="loginData.password"
          class="form-control"
          id="password"
        />
      </div>
      <button type="submit" class="btn btn-primary w-100">login</button>
    </form>
  </div>
</template>
<script>
export default {
  data() {
    return {
      loginData: {
        email: "",
        password: ""
      }
    };
  },
  methods: {
    async login() {
      try {
        let response = await this.$auth.loginWith("local", {
          data: this.loginData
        });
        this.$router.push("/");
        console.log(response);
      } catch (err) {
        console.log(err);
      }
    }
  }
};
</script>
<style></style>

/components/Auth/Register/index.vue
<template>
    <div>
      <form @submit.prevent="register">
        <div class="mb-3">
          <label for="fullname" class="form-label">Full Name</label>
          <input
            type="text"
            v-model="registerData.fullname"
            class="form-control"
            id="fullname"
          />
        </div>
        <div class="mb-3">
          <label for="email" class="form-label">Email address</label>
          <input
            type="email"
            class="form-control"
            id="email"
            v-model="registerData.email"
            aria-describedby="emailHelp"
          />
        </div>
        <div class="mb-3">
          <label for="password" class="form-label">Password</label>
          <input
            type="password"
            v-model="registerData.password"
            class="form-control"
            id="password"
          />
        </div>
        <button type="submit" class="btn btn-primary w-100">Register</button>
      </form>
    </div>
  </template>
  <script>
  export default {
    data() {
      return {
        registerData: {
          fullname: "",
          email: "",
          password: ""
        }
      };
    },
    methods: {
      async register() {
        try {
          const user = await this.$axios.$post("/api/auth/signin", {
            fullname: this.registerData.fullname,
            email: this.registerData.email,
            password: this.registerData.password
          });
          console.log(user);
        } catch (err) {
          console.log(err);
        }
      }
    }
  };
  </script>
  <style></style>


그리고
/components/Home/index.vue
<template>
    <div>
      <h2>You're in home page</h2>
    </div>
  </template>
  <script>
  export default {};
  </script>
  <style></style>



그리고
/components/User/index.vue
<template>
    <div>
      Hello dear <b style="color:red">{{ getUserInfo.fullname }}</b> you're in
      profile page
      <hr />
      This is your information:
      <br /><br />
      <table class="table">
        <thead>
          <tr>
            <th scope="col">ID</th>
            <th scope="col">FullName</th>
            <th scope="col">Email</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>{{ getUserInfo.id }}</td>
            <td>{{ getUserInfo.fullname }}</td>
            <td>{{ getUserInfo.email }}</td>
          </tr>
        </tbody>
      </table>
    </div>
  </template>

  <script>
  export default {
    computed: {
      getUserInfo() {
        return this.$store.getters.getUserInfo;
      }
    }
  };
  </script>

  <style></style> 


그리고
/components/Layouts/Header/index.vue
<template>
    <div>
      <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <div class="container-fluid">
          <nuxt-link class="navbar-brand" to="/">Navbar</nuxt-link>
          <button
            class="navbar-toggler"
            type="button"
            data-bs-toggle="collapse"
            data-bs-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent"
            aria-expanded="false"
            aria-label="Toggle navigation"
          >
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
              <template v-if="!isAuthenticated">
                <li class="nav-item">
                  <nuxt-link
                    class="nav-link active"
                    aria-current="page"
                    to="/auth/login"
                    >Login</nuxt-link
                  >
                </li>
                <li class="nav-item">
                  <nuxt-link
                    class="nav-link active"
                    aria-current="page"
                    to="/auth/register"
                    >Register</nuxt-link
                  >
                </li>
              </template>
              <template v-else>
                <li class="nav-item" @click="logout">
                  <nuxt-link class="nav-link active" aria-current="page" to="#"
                    >Logout</nuxt-link
                  >
                </li>
                <li>
                  <nuxt-link
                    class="nav-link active"
                    aria-current="page"
                    to="/profile"
                  >
                    Profile
                  </nuxt-link>
                </li>
              </template>
            </ul>
          </div>
        </div>
      </nav>
    </div>
  </template>

  <script>
  export default {
    methods: {
      async logout() {
        await this.$auth.logout();  // this method will logout the user and make token to false on the local storage of the user browser
      }
    },
    computed: {
      isAuthenticated() {
        return this.$store.getters.isAuthenticated;  // it check if user isAuthenticated 
      }
    }
  };
  </script>

  <style></style>



그리고/layouts/default.vue
<template>
    <div>
      <layouts-header />
      <div class="container">
        <br />
        <Nuxt />
      </div>
    </div>
  </template>

  <script>
  export default {};
  </script>

  <style>
  @import url("https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.0.2/css/bootstrap.min.css");
  </style>


그리고
/pages/index.vue
<template>
    <div>
      <home />
    </div>
  </template>

  <script>
  export default {};
  </script>


그리고
/pages/auth/login/index.vue
<template>
    <div>
      <auth-login />
    </div>
  </template>

  <script>
  export default {};
  </script>

  <style></style>



그리고
/pages/auth/register/index.vue
<template>
    <div>
      <auth-register />
    </div>
  </template>

  <script>
  export default {};
  </script>

  <style></style>



그리고
/pages/profile/index.vue
<template>
    <div>
      <user />
    </div>
  </template>

  <script>
  export default {
    middleware: "isAuthenticated"  // it will use `isAuthenticated` middleware
  };
  </script>

  <style></style>



사용자 인증 여부를 확인하기 위한 미들웨어:

export default function({ store, redirect }) {
    if (!store.state.auth.loggedIn) {
      return redirect("/auth/login");
    }
}


그리고 마침내 vuex store:

export const getters = {
    isAuthenticated(state) {
      return state.auth.loggedIn; // auth object as default will be added in vuex state, when you initialize nuxt auth
    },
    getUserInfo(state) {
      return state.auth.user;
    }
  };


익스프레스.js



이것이 우리의 nuxt 코드였으며 이제 API를 만들 차례입니다.

먼저 디렉토리를 만들고 터미널/Cmd에 이 명령을 입력하고 npm init -y그럼 npm install express body-parser bcryptjs jsonwebtoken mongoose그리고npm install --save-dev nodemon nodemon을 dev 종속성으로 추가합니다.


주의 "main" 파일에서 "main":"index.js"package.json 였다면 "main": "app.js"로 변경

이제 몇 가지 파일을 만들 차례입니다.
nodemon.js root direcotry that you just made
{
  "env": {
    "MONGO_USER": "mohammadali", // your cluster name
    "MONGO_PASS": "20212021", // your cluster password
    "MONGO_DB": "auth" // your db name
  }
}

on package.json will be on root directory add these lines of codes on scripts like as I did in below
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start-server": "node app.js",
    "dev": "nodemon app.js"
  },

app.js on root direcotry that you just made
const express = require("express");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");

// routes
const authRouter = require("./routes/authRouter");

const app = express();

app.use(bodyParser.json());

app.use((req, res, next) => {
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader(
    "Access-Control-Allow-Methods",
    "OPTIONS, GET, POST, PUT, PATCH, DELETE"
  );
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  next();
});

app.use("/api/auth/", authRouter);

app.use((error, req, res, next) => {
  console.log(error);
  const status = error.statusCode || 500;
  const message = error.message;
  const data = error.data;
  res.status(status).json({ message: message, data: data });
});

// connect to db
const MONGOOSE_URI = `mongodb+srv://${process.env.MONGO_USER}:${process.env.MONGO_PASS}@cluster0.4r3gv.mongodb.net/${process.env.MONGO_DB}`;
mongoose
  .connect(MONGOOSE_URI)
  .then((result) => {
    app.listen(process.env.PORT || 8080);
  })
  .catch((err) => console.log(err));



그리고
/controllers/authController.js
const bcrypt = require("bcryptjs");
const userModel = require("../models/userModel");
const jwt = require("jsonwebtoken");

exports.postSignin = async (req, res, next) => {
  const { fullname, email, password } = req.body;
  try {
    const exsitUser = await userModel.findOne({ email: email });
    if (exsitUser) {
      const error = new Error(
        "Eamil already exist, please pick another email!"
      );
      res.status(409).json({
        error: "Eamil already exist, please pick another email! ",
      });
      error.statusCode = 409;
      throw error;
    }

    const hashedPassword = await bcrypt.hash(password, 12);
    const user = new userModel({
      fullname: fullname,
      email: email,
      password: hashedPassword,
    });
    const result = await user.save();
    res.status(200).json({
      message: "User created",
      user: { id: result._id, email: result.email },
    });
  } catch (err) {
    if (!err.statusCode) {
      err.statusCode = 500;
    }
    next(err);
  }
};

let loadedUser;
exports.postLogin = async (req, res, next) => {
  const { email, password } = req.body;

  try {
    const user = await userModel.findOne({ email: email });

    if (!user) {
      const error = new Error("user with this email not found!");
      error.statusCode = 401;
      throw error;
    }
    loadedUser = user;

    const comparePassword = bcrypt.compare(password, user.password);

    if (!comparePassword) {
      const error = new Error("password is not match!");
      error.statusCode = 401;
      throw error;
    }
    const token = jwt.sign({ email: loadedUser.email }, "expressnuxtsecret", {
      expiresIn: "20m", // it will expire token after 20 minutes and if the user then refresh the page will log out
    });
    res.status(200).json({ token: token });
  } catch (err) {
    if (!err.statusCode) {
      err.statusCode = 500;
    }
    next(err);
  }
};

exports.getUser = (req, res, next) => { // this function will send user data to the front-end as I said above authFetch on the user object in nuxt.config.js will send a request and it will execute
  res.status(200).json({
    user: {
      id: loadedUser._id,
      fullname: loadedUser.fullname,
      email: loadedUser.email,
    },
  });
};


그리고
/middleware/isAuth.js
const jwt = require("jsonwebtoken");

module.exports = (req, res, next) => {
  const authHeader = req.get("Authorization");
  if (!authHeader) {
    const error = new Error("Not authenticated.");
    error.statusCode = 401;
    throw error;
  }
  const token = authHeader.split(" ")[1];
  let decodedToken;
  try {
    decodedToken = jwt.verify(token, "expressnuxtsecret");
  } catch (err) {
    err.statusCode = 500;
    throw err;
  }
  if (!decodedToken) {
    const error = new Error("Not authenticated.");
    error.statusCode = 401;
    throw error;
  }
  req.userId = decodedToken.userId;
  next();
};



그리고
/models/userModel.js
const express = require("express");
const mongoose = require("mongoose");
const Schema = mongoose.Schema;

const UserSchema = new Schema(
  {
    fullname: {
      type: String,
      required: true,
    },
    email: {
      type: String,
      required: true,
    },
    password: {
      type: String,
      required: true,
    },
  },
  { timestamps: true }
);

module.exports = mongoose.model("User", UserSchema);



그리고
/routes/routes.js
const express = require("express");
const router = express.Router();
const authController = require("../controllers/authController");

router.post("/signin", authController.postSignin);
router.post("/login", authController.postLogin);
router.get("/user", authController.getUser);

module.exports = router;



이것은 테스트 프로젝트였으며 일반적으로 클라이언트 측 앱 입력과 서버 측 앱에 대한 일부 유효성 검사를 추가해야 합니다.



마침내 완료되었습니다. 즐겁게 즐기셨기를 바랍니다. 소스 코드를 찾을 수 있는 제 Github 링크가 있습니다.

유용할 수 있는 몇 가지 제안:

애플리케이션에 대해 (갱신 토큰과 액세스 토큰) 둘 다 갖고 싶어하고 MongoDB 대신 Postgresql을 사용하려는 사람들을 위해 this GitHub 저장소를 제안합니다.

그리고 애플리케이션에 대해 (갱신 토큰과 액세스 토큰) 모두를 갖고 싶어하고 MongoDB 대신 Postgresql을 사용하고 백엔드 애플리케이션에도 Typescript를 사용하려는 사람들에게는 this GitHub 저장소를 제안합니다.

좋은 웹페이지 즐겨찾기