Review the content

In the last section, we talked about the Cookie+Session based authentication scheme.

Due to some disadvantages of the session-based scheme, the token based stateless Session management scheme was born. Stateless means that the server no longer stores information.

Jwt-based simple authentication process

Recommended reading for those unfamiliar with JWT:

  • Server Authentication Artifact — JWT(1)
  • JSON Web Token Introduction tutorial

Node implements JWT authentication

Technical implementation solution: Node + KOA2 + mongodb

The directory structure

Pre-development preparation

  • node
  • mongodb

Remember to start mongodb locally before starting node services

user.js

const mongoose = require("mongoose");
const { Schema } = mongoose;

const userSchema = new Schema({
  name: String.password: String.salt: String.isAdmin: Boolean.age: Number
});

module.exports = mongoose.model("User", userSchema);
Copy the code

config.js



module.exports = {
	'secret': 'ilovescotchyscotch'./ / key
	'db': 'mongodb://localhost:27017/test'
}
Copy the code

package.json


{
  "name": "token"."version": "1.0.0"."description": ""."main": "index.js"."dependencies": {
    "crypto-js": "^" 3.1.9-1."jsonwebtoken": "^ 8.5.1"."koa": "^ 2.8.2"."koa-bodyparser": "^ 2"."koa-router": "^ 7.4.0"."mongoose": "^ 5.7.3." "
  },
  "devDependencies": {
    "nodemon": "^ 1.19.3"
  },
  "scripts": {
    "start": "nodemon ./app.js"
  },
  "keywords": []."author": ""."license": "ISC"
}

Copy the code

app.js


const Koa = require("koa");
const Router = require("koa-router");
const bodyParser = require("koa-bodyparser");
const md5 = require("crypto-js/md5");
const jwt = require("jsonwebtoken");
const mongoose = require("mongoose");
const User = require("./models/user.js");
const config = require("./config.js");

const app = new Koa();
const router = new Router();

mongoose.connect(config.db, { useUnifiedTopology: true });

app.use(bodyParser());

/** * @description Create user */
router.post("/user".async (ctx, next) => {
  const { username = "", password = "", age, isAdmin } = ctx.request.body || {};
  if (username === "" || password === "") {
    ctx.status = 401;
    return (ctx.body = {
      success: false.code: 10000.msg: "Username or password cannot be empty."
    });
  }
  // Md5 the password first
  const md5PassWord = md5(String(password)).toString();
  // Generate a random salt
  const salt = String(Math.random()).substring(2.10);
  // Add salt and then md5
  const saltMD5PassWord = md5(`${md5PassWord}:${salt}`).toString();
  try {
    // Similar to user search, save operations generally we will be encapsulated in an entity, this demo is only demonstration, do not write such a production environment
    const searchUser = await User.findOne({ name: username });
    if(! searchUser) {const user = new User({
        name: username,
        password: saltMD5PassWord,
        salt,
        isAdmin,
        age
      });
      const result = await user.save();
      ctx.body = {
        success: true.msg: "Create successful"
      };
    } else {
      ctx.body = {
        success: false.msg: "Existing user with the same name"}; }}catch (error) {
    // In this case, we usually throw the exception class directly, and then have the global error handling to handle
    ctx.body = {
      success: false.msg: "serve is mistakes"}; }});/** * @description User login */
router.post("/login".async (ctx, next) => {
  const { username = "", password = "" } = ctx.request.body || {};
  if (username === "" || password === "") {
    ctx.status = 401;
    return (ctx.body = {
      success: false.code: 10000.msg: "Username or password cannot be empty."
    });
  }
  // Generally, the client needs md5 encryption to transmit the password. Here I will encrypt it myself, assuming that the client does not encrypt it.
  // Similar to user search, save operations generally we will be encapsulated in an entity, this demo is only demonstration, do not write such a production environment
  try {
    // Username is not allowed to duplicate during registration
    const searchUser = await User.findOne({ name: username });
    if(! searchUser) { ctx.body = {success: false.msg: "User does not exist"
      };
    } else {
      // Need to go to the database to verify the user password
      const md5PassWord = md5(String(password)).toString();
      const saltMD5PassWord = md5(
        `${md5PassWord}:${searchUser.salt}`
      ).toString();
      if (saltMD5PassWord === searchUser.password) {
        // Payload: indicates the Payload. It is not recommended to store sensitive information
        const payload = {
          id: searchUser._id
        };
        const token = jwt.sign(payload, config.secret, {
          expiresIn: "2h"
        });
        ctx.body = {
          success: true.data: {
            token
          }
        };
      } else {
        ctx.body = {
          success: false.msg: "Password error"}; }}}catch (error) {
    ctx.body = {
      success: false.msg: "serve is mistakes"}; }});/** * @description Obtain user information */
router.get(
  "/user".async (ctx, next) => {
    // There should be an Auth middleware
    const token = ctx.request.query.token || ctx.request.headers["token"];
    if (token) {
      jwt.verify(token, config.secret, async function(err, decoded) {
        if (err) {
          return (ctx.body = {
            success: false.msg: "Failed to authenticate token."
          });
        } else {
          ctx.decoded = decoded;
          awaitnext(); }}); }else {
      ctx.status = 401;
      ctx.body = {
        success: false.msg: "need token"}; }},async (ctx, next) => {
    try {
      const { id } = ctx.decoded;
      const { name, age, isAdmin } = await User.findOne({ _id: id });
      ctx.body = {
        success: true.data: { name, age, isAdmin }
      };
    } catch (error) {
      ctx.body = {
        success: false.msg: "server is mistakes"}; }}); app.use(router.routes()).use(router.allowedMethods()); app.on("error", (err, ctx) => {
  console.error("server error", err, ctx);
});
app.listen(3000, () = > {console.log("Server listening on port 3000");
});

Copy the code

Test all interfaces using Postman.

  • Verify the creation of the user interface

  • Go to database validation

  • Validate the business API

What problems are solved based on the JWT authentication scheme

  • The server does not need to store authentication information. The authentication information is encrypted to the token. The server only needs to read the user information contained in the token.
  • Avoids the problem that the shared Session is not easy to extend
  • Do not rely on cookies, effectively avoid CORS attacks caused by cookies
  • CORS can effectively solve cross-domain problems

Understanding of JWT and Token

Through this article about JWT discussing with Token, I have corrected some of my erroneous views. The next article is to record learning about token.

note

There are errors in welcome to be corrected, source address.

Finally interested to follow a wave of public number.