JWT認証と認証、ノード、エクスプレス、およびVue


このチュートリアルでは、以前のチュートリアルで使用してきたブログアプリケーションに認証と認証を追加します.我々は2つのオプションパスポートやJWTを使用するつもりです.今日の意志はJWTを扱います.
面白いことも私は他の人の記事を読んで何が面白いのは常に私の読書リストに保存されます.本稿ではjwtについて拡張可能について解説した.私はもっとそれを追加する必要はありません.
.

私の学習の間、私はいつも私が実際にアプリケーションに読んでいるものを実装する方法を知って好奇心旺盛です。


今まで学んだこと
  • MongoDB、Express、Vue、およびNodeのアプローチは、現在インフラストラクチャを管理することなくアプリケーションを実行しているので、Serverlessの下にあります.アトラスは私たちのすべてを管理しています.
  • AWS PostgreSQL、Express、Vue、およびノードのアプローチは、現在のEC 2でアプリケーションを実行しているので、サーバーアプローチの下で落ちます.インフラを管理しなければならない.
  • AWS MySQL、Express、Vue、およびノードのアプローチは、サーバーのアプローチの下で、現在のEC 2でアプリケーションを実行しているために該当します.インフラを管理しなければならない.
  • 起動のために、サーバーを維持することはかなり高価です、したがって、コンテナとServerlessの間の考慮は理想的です.
    フロントエンドの認証ディレクトリに次のコンポーネントを追加します.

    ログイン.Vue



    サインアップ。Vue



    確認。Vue



    最後に、記事のパブリック表示のための記事のAPI APIを許可するだけです.残りのCRUDアクションは認証後にのみ許可されます.
    始めましょう.

    バックエンド


    以前のチュートリアルに従って、現在のところ、2つのアプリケーションバックエンドがノード上で実行されていることを知っています.
    我々は認証と認証をこのアプリケーションに追加します.以下に仮定する.
  • 私たちは、以下のルートを市民に得るのを許したいです.
  • 私たちは管理者の役割を削除します.他のすべては管理者かユーザによって評価されます.
  • 下記は私たちのブログです.ルートフォルダのjs
    const express = require("express")
    const router = express.Router()
    const blog = require("../controller/blog.controller");
    const { auth_jwt_token } = require("../authentication");
    
    // /api/blog: GET, POST, DELETE
    // /api/blog/:id: GET, PUT, DELETE
    // /api/blog/published: GET
    
    // Create a new blog
    router.post("/", [auth_jwt_token.verifyToken], blog.create);
    
    // Retrieve all blog
    router.get("/", blog.findAll);
    
    // Retrieve all published blog
    router.get("/published", blog.findAllPublished);
    
    // Retrieve a single blog with id
    router.get("/:id", blog.findOne);
    
    // Update a blog with id
    router.put("/:id", [auth_jwt_token.verifyToken], blog.update);
    
    // Delete a blog with id
    router.delete("/:id", [auth_jwt_token.verifyToken, auth_jwt_token.isAdmin], blog.delete);
    
    // Create a new blog
    router.delete("/", [auth_jwt_token.verifyToken, auth_jwt_token.isAdmin], blog.deleteAll);
    
    module.exports = router
    
    私たちのブログには2つの役割が必要です.ユーザと管理者.
    トークンの場合は、JSONWebTokenまたはExpressを使用できます.日本気象協会BcryptJSを我々のパスワードとJSONWebTokenのハッシュにインストールしましょう.
    yarn add jsonwebtoken bcryptjs
    

    インデックス.js


    インデックスで.私たちのバックエンドアプリケーションの負荷時にJSファイルは、役割が正しく設定されている場合、私たちのデータベースをチェックしたい場合は、空の場合はロールを作成する必要があります.ロールのチェックを処理する初期化関数を持ちましょう.
    const Role = db.role // reference the Role DB
    function initialize() {
        Role.estimatedDocumentCount((err, count) => {
          if (!err && count === 0) {
            new Role({
              name: "user"
            }).save(err => {
              if (err) {
                console.log("error", err);
              }
              console.log("added 'user' to roles collection");
            });
    
            new Role({
              name: "admin"
            }).save(err => {
              if (err) {
                console.log("error", err);
              }  
              console.log("added 'admin' to roles collection");
            });
          }
        });
      }
    
    以下は、最初にバックエンドを実行したときの結果です.ロールテーブルを初期化し、新しいロールを追加しました.

    路線


    Authという新しいルートを追加します.ルートフォルダのJSとブログのルートを更新します.
    Authjs
    このルートは2つの関数signupとsigninを扱います.電子メールが重複を避ける前に登録されているかどうかを確認するために、関数verifychen usersen電子メールを支援します.
    const { verify_user_email } = require("../authentication");
    const express = require("express")
    const router = express.Router()
    const auth = require("../controller/auth.controller");
    
    router.post("/signin", auth.signin);
    
    router.post("/signup", 
      [
          verify_user_email.checkDuplicateUsernameOrEmail,
          verify_user_email.checkRolesExisted
      ],
      auth.signup
    )
    
    module.exports = router
    
    ブログ.js
    私は我々のブログの上で共有しました.JSルートフォルダは.
    これはルートフォルダで行う必要があります.次に、インデックスを更新する必要があります.JSファイルと私たちのルートをインポートします.急行で.アプリケーションレベルとルータレベルのミドルウェアをロードすることができます.また、マウントポイントでミドルウェアシステムのサブスタックを作成するミドルウェア機能のシリーズを読み込むことができます.
    インデックス.js
    // routes
    const blog = require('./app/routes/blog') // blog routes
    const auth = require('./app/routes/auth') // user authentication
    
    app.use('/api/blog',blog, function(req, res, next){
      res.header(
        "Access-Control-Allow-Headers",
        "x-access-token, Origin, Content-Type, Accept"
      );
      next();
    }) // user authorization
    app.use('/api/auth', auth, function(req, res, next){
      res.header(
        "Access-Control-Allow-Headers",
        "x-access-token, Origin, Content-Type, Accept"
      );
      next();
    }) // auth authentication
    
    私は、我々がこの段階で同じページにいることを望みます.キープクローズ

    スキーマ


    ユーザーとロールのスキーマを定義します.これは、私たちがブログスキーマを持っていたモデルのフォルダで行われます.
    役割モデル.js
    我々の役割は、名前とIDを持ちます.
    module.exports = mongoose => {
        const Role = mongoose.model(
          "Role",
          mongoose.Schema(
            {
              name: String,
            },
            { timestamps: true }
          ) 
        );
        return Role;
      };
    
    ユーザ.モデル.js
    ユーザーモデルでは、ユーザー名、メール、パスワード、およびユーザーのロールを追加します.デフォルトでは、ユーザはユーザロールを持ち、その後管理者にアップグレードします.
    我々が役割の正しいIDを得ることができるように、我々は役割を参照したことに注意してください.
    module.exports = mongoose => {
        const User = mongoose.model(
          "User",
          mongoose.Schema(
            {
              username: String,
              email: String,
              password: String,
              roles: [
                {
                  type: mongoose.Schema.Types.ObjectId,
                  ref: "Role"
                }
              ]
            },
            { timestamps: true }
          )
        );
        return User;
      };
    

    コントローラ


    コントローラのフォルダで認証を処理するコントローラを追加しましょう
    Authコントローラ.js
    signup関数は新しいユーザを作成しますが、署名機能はユーザが存在することを確認します.それから、ユーザペイロードは秘密鍵によって署名されます、そして、トークンは生成されます.トークンを検証するには、署名を検証してJWTでデコードするか、JWTトークンをデコードするだけです.両方のシナリオを処理します.
    const crypto = require('crypto');
    const db = require("../models");
    const User = db.user;
    const Role = db.role;
    
    var jwt = require("jsonwebtoken");
    var bcrypt = require("bcryptjs");
    
    exports.signup = (req, res) => {
      const user = new User({
        username: req.body.username,
        email: req.body.email,
        password: bcrypt.hashSync(req.body.password, 8)
      });
    
      user.save((err, user) => {
        if (err) {
          res.status(500).send({ message: err });
          return;
        }
    
        if (req.body.roles) {
          Role.find(
            {
              name: { $in: req.body.roles }
            },
            (err, roles) => {
              if (err) {
                res.status(500).send({ message: err });
                return;
              }
    
              user.roles = roles.map(role => role._id);
              user.save(err => {
                if (err) {
                  res.status(500).send({ message: err });
                  return;
                }
    
                res.send({ message: "User was registered successfully!" });
              });
            }
          );
        } else {
          Role.findOne({ name: "user" }, (err, role) => {
            if (err) {
              res.status(500).send({ message: err });
              return;
            }
    
            user.roles = [role._id];
            user.save(err => {
              if (err) {
                res.status(500).send({ message: err });
                return;
              }
    
              res.send({ message: "User was registered successfully!" });
            });
          });
        }
      });
    };
    
    exports.signin = (req, res) => {
      User.findOne({
        username: req.body.username
      })
        .populate("roles", "-__v")
        .exec((err, user) => {
    
          if (err) {
            res.status(500).send({ message: err });
            return;
          }
    
          if (!user) {
            return res.status(404).send({ message: "User Not found." });
          }
    
          var passwordIsValid = bcrypt.compareSync(
            req.body.password,
            user.password
          );
    
          if (!passwordIsValid) {
            return res.status(401).send({
              accessToken: null,
              message: "Invalid Password!"
            });
          }
    
          const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', {
            namedCurve: 'sect239k1'
          });
    
          // generate a signature of the payload
          const sign = crypto.createSign('SHA256');
          sign.write(`${user}`);
          sign.end();
          var signature = sign.sign(privateKey, 'hex');
          console.log(signature)
    
    
          // sign username
          var token = jwt.sign({ id: user.id }, signature, {
            expiresIn: 86400 // 24 hours
          });
    
          var authorities = [];
    
          for (let i = 0; i < user.roles.length; i++) {
            authorities.push("ROLE_" + user.roles[i].name.toUpperCase());
          }
          res.status(200).send({
            id: user._id,
            username: user.username,
            email: user.email,
            roles: authorities,
            accessToken: token, // access token
            signature: signature // signature
          });
        });
    };
    
    最後に、アプリケーションフォルダに認証フォルダーを作成します.
    touch /app/authentication
    
    次に、3つのファイルインデックスを作成します.JS , Authjsと確認します.js検証する.JSは認証中にユーザメールの検証を処理します.JSはユーザトークンの検証を処理し、ユーザが管理者である場合.

    Authjs


    const jwt = require("jsonwebtoken");
    const db = require("../models");
    const User = db.user;
    const Role = db.role;
    
    verifyToken = (req, res, next) => {
      let token = req.headers["x-access-token"];
      let secret = req.headers["x-access-signature"];
    
      if (!token) {
        return res.status(403).send({ message: "No token provided!" });
      }
    
    
      // Prints: true
      jwt.verify(token, secret, (err, decoded) => {
        if (err) {
          return res.status(401).send({ message: "Unauthorized!" });
        }
        req.userId = decoded.id;
        next();
      });
    };
    
    isAdmin = (req, res, next) => {
      User.findById(req.userId).exec((err, user) => {
        if (err) {
          res.status(500).send({ message: err });
          return;
        }
    
        Role.find(
          {
            _id: { $in: user.roles }
          },
          (err, roles) => {
            if (err) {
              res.status(500).send({ message: err });
              return;
            }
    
            for (let i = 0; i < roles.length; i++) {
              if (roles[i].name === "admin") {
                next();
                return;
              }
            }
    
            res.status(403).send({ message: "Require Admin Role!" });
            return;
          }
        );
      });
    };
    
    const authJwt = {
      verifyToken,
      isAdmin,
    };
    module.exports = authJwt;
    

    検証する。js


    const db = require("../models");
    const ROLES = db.ROLES;
    const User = db.user;
    
    checkDuplicateUsernameOrEmail = (req, res, next) => {
      // Username
      User.findOne({
        username: req.body.username
      }).exec((err, user) => {
        if (err) {
          res.status(500).send({ message: err });
          return;
        }
    
        if (user) {
          res.status(400).send({ message: "Failed! Username is already in use!" });
          return;
        }
    
        // Email
        User.findOne({
          email: req.body.email
        }).exec((err, user) => {
          if (err) {
            res.status(500).send({ message: err });
            return;
          }
    
          if (user) {
            res.status(400).send({ message: "Failed! Email is already in use!" });
            return;
          }
    
          next();
        });
      });
    };
    
    checkRolesExisted = (req, res, next) => {
      if (req.body.roles) {
        for (let i = 0; i < req.body.roles.length; i++) {
          if (!ROLES.includes(req.body.roles[i])) {
            res.status(400).send({
              message: `Failed! Role ${req.body.roles[i]} does not exist!`
            });
            return;
          }
        }
      }
    
      next();
    };
    
    const verifySignUp = {
      checkDuplicateUsernameOrEmail,
      checkRolesExisted
    };
    
    module.exports = verifySignUp;
    
    インデックスを通して認証ファイルのすべての内容をエクスポートします.フォルダ内のjs.

    インデックス.js


    const auth_jwt_token = require("./auth");
    const verify_user_email = require("./verify");
    
    module.exports = {
      auth_jwt_token,
      verify_user_email
    };
    
    我々のバックエンドをテストして、我々が正しくすべてを構成したことを確認しましょう.Postmanを使ってテストを行います.
  • 管理者アクセスなしでユーザーをサインアップします.
  • ユーザーには
  • に署名します.
  • ブログを作成します(トークンが必要です)
  • ブログを削除します(トークンと管理者のアクセスが必要です)
  • フロントエンド


    フロントエンドを設定し、2つの間の通信をリンクします.Authというファイルを作成しましょう.スクリプト.コンポーネントディレクトリのjs.
    import axios from "axios";
    
    export const signup = async item => {
      let data = {
        username: item.username,
        email: item.email,
        password: item.password,
        roles: ["user"]
      };
      let request = {
        url: "http://localhost:3000/api/auth/signup", // should be replaced after going to production with domain url
        method: "post",
        headers: {
          "Content-type": "application/json"
        },
        data: JSON.stringify(data)
      };
    
      const response = await axios(request);
      return response;
    };
    
    export const login = async item => {
      let data = {
        username: item.username,
        password: item.password
      };
      let request = {
        url: "http://localhost:3000/api/auth/signin", // should be replaced after going to production with domain url
        method: "post",
        headers: {
          "Content-type": "application/json"
        },
        data: JSON.stringify(data)
      };
    
      const response = await axios(request);
      return response;
    };
    
    
    成功した場合は、ユーザーの詳細を安全に保存する必要があります.ここで安全にあなたのペイロードを安全に保存する方法についての記事です.
    ログイン機能は、ストレージをクリアし、ログインページまたはホームページにユーザーをリダイレクトする必要があります.

    サインアップ


    コンポーネントで、次の関数をメソッドのセクションに追加し、ユーザーに送信します.
    // import the signup function from auth.script.js
    
    // sibmit signup
        async submit() {
          this.loading = true;
          const response = await signup(this.item);
          if (response === "User was registered successfully!") {
            // DO NOT USE LOCAL STORAGE
            localStorage.setItem("user", JSON.stringify(response.data));
            this.item = {
              username: "",
              email: "",
              password: "",
              roles: ["user"]
            };
            this.loading = false;
            this.$router.push("/dashboard");
          } else {
            // error
            console.log("Error", response);
            setTimeout(() => {
              this.loading = false;
            }, 1000);
          }
        }
    

    ログイン


    ログインコンポーネントで、次の関数をメソッドのセクションに追加し、ユーザーに送信します.
    // import the login function from auth.script.js
    
    // sibmit login
        async submit() {
          this.loading = true;
          const response = await login(this.item);
          if (response.data.accessToken) {
             // DO NOT USE LOCAL STORAGE
            localStorage.setItem("user", JSON.stringify(response.data));
            this.item = {
              username: "",
              password: ""
            };
            this.loading = false;
            this.$router.push("/dashboard");
          } else {
            // error
            console.log("Error", response);
          }
        }
    

    ログアウト


    ダッシュボード更新プログラムでは、保存したユーザー情報をクリアする方法を追加することにより、ログアウト機能です.
    // DO NOT USE LOCAL STORAGE
    localStorage.removeItem("user")
    
    ノート
    認証を必要とするブログルート内のすべての要求に対して、次のようなヘッダが表示されます.
    headers: {
          "Content-type": "application/json",
          'x-access-token': item.accessToken,
          'x-access-signature': item.signature
        },
    
    最後に、あなたのVUEアプリケーションのすべてのルートをガードガード.ルータフォルダでインデックスを更新します.jsファイルは次のようになります.
    const router = new VueRouter({
      routes: [
        {
          path: '/dashboard',
          component: Dashboard,
          // save you have a means of updating isAuthenticated
          beforeEach((to, from, next) => {
             if (to.name !== 'Login' && !isAuthenticated) next({ name: 
               'Login' })
              else next()
          })
        }
      ]
    })
    
    Vueアプリケーションhttps://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guardsでルートを保護する方法についての詳細を読む

    MySQL


    MySQLでは、変更する必要のあるファイルを提供します.MySQLの記事はこちら

    インデックス.js


    const Role = db.role // reference the Role DB
    function initialize() {
      Role.create({
        id: 1,
        name: "user"
      });
    
      Role.create({
        id: 3,
        name: "admin"
      });
    }
    

    役割モデル.js


    module.exports = (sequelize, Sequelize) => {
        const Role = sequelize.define("roles", {
        id: {
          type: Sequelize.INTEGER,
          primaryKey: true
        },
        name: {
          type: Sequelize.STRING
        }
      });
      };
    

    ユーザ。モデル.js


    const User = sequelize.define("users", {
        username: {
          type: Sequelize.STRING
        },
        email: {
          type: Sequelize.STRING
        },
        password: {
          type: Sequelize.STRING
        }
      });
    
      return User;
    

    /モデル/インデックス。js


    db.user = require("../models/user.model.js")(sequelize, Sequelize);
    db.role = require("../models/role.model.js")(sequelize, Sequelize);
    
    db.role.belongsToMany(db.user, {
      through: "user_roles",
      foreignKey: "roleId",
      otherKey: "userId"
    });
    db.user.belongsToMany(db.role, {
      through: "user_roles",
      foreignKey: "userId",
      otherKey: "roleId"
    });
    
    db.ROLES = ["user", "admin"];
    
    私が上で詳述したように、他のすべてはとどまります.次のファイルを編集します.

    PostgreSQL


    PostgreSQLの場合、変更するファイルを提供します.PostgreSQLの記事はこちら

    インデックス.js


    const Role = db.role // reference the Role DB
    function initialize() {
      Role.create({
        id: 1,
        name: "user"
      });
    
      Role.create({
        id: 3,
        name: "admin"
      });
    }
    

    役割モデル.js


    module.exports = (sequelize, Sequelize) => {
        const Role = sequelize.define("roles", {
        id: {
          type: Sequelize.INTEGER,
          primaryKey: true
        },
        name: {
          type: Sequelize.STRING
        }
      });
      };
    

    ユーザ。モデル.js


    const User = sequelize.define("users", {
        username: {
          type: Sequelize.STRING
        },
        email: {
          type: Sequelize.STRING
        },
        password: {
          type: Sequelize.STRING
        }
      });
    
      return User;
    

    /モデル/インデックス。js


    db.user = require("../models/user.model.js")(sequelize, Sequelize);
    db.role = require("../models/role.model.js")(sequelize, Sequelize);
    
    db.role.belongsToMany(db.user, {
      through: "user_roles",
      foreignKey: "roleId",
      otherKey: "userId"
    });
    db.user.belongsToMany(db.role, {
      through: "user_roles",
      foreignKey: "userId",
      otherKey: "roleId"
    });
    
    db.ROLES = ["user", "admin"];
    
    私が上で詳述したように、他のすべてはとどまります.次のファイルを編集します.

    確認用のコード送信


    別の製品を使用してこれを実装することができますが、私はAWSのSESをお勧めします.私はかつてAWS SESを設定する方法について話しました.全体のロジック私はコースで詳細になります.始めから終わりまで.キープクローズ
    私はこのチュートリアルがJWTを実装する方法に役立つことを願っています.私は記事をJWTについての詳細を理解するために提供している.どのように安全にそれらに.
    ありがとう