Native JavaScript + NodeJS(Express framework) makes a simple login and registration project

Posted by thenior on Fri, 21 Jan 2022 09:11:53 +0100

Use front-end and back-end separation, and the front-end file is located in front_end folder

Configure in config / default JS, of course, can be configured in specific files, but it's more convenient here

Online demo: https://auth.bilibilianime.com/

Warehouse: https://github.com/ayasa520/NJU-WEB/web_3/

Deploy (start)

  • Preview front end
    • live-server ./front_end
  • Open the rear end only
    • node ./bin/www or npm start
  • Both front and rear ends are open
    • npm run dev

Completion point

  • [x] Login
  • [x] Register
  • [x] Password strength front-end judgment (very weak, just pretend)
  • [x] token keeps login status
  • [x] Graphic verification code
  • [x] bcrypt encrypted storage password

rely on

  • express lightweight web framework
  • JSON webtoken generates a token
  • Models object model of mongodb
  • Svg captcha verification code
  • cookie-parser
  • express-session
  • bcryptjs encryption
  • mongoose operation MongoDB

About it

The front and back ends are separated and information is transmitted through json

The front end wraps XMLHttpRequest with Promise and uses chain calls to avoid callback

const _ajax = ({ url, method = "GET", data = null, contentType = false }) => {
  return new Promise((resolve, reject) => {
    const req = new XMLHttpRequest();
    req.open(method, url, true);
    req.withCredentials = true;
    req.setRequestHeader("Content-Type", contentType);
    req.setRequestHeader("Authorization", "Bearer"+" "+ window.localStorage.token);
    req.send(data);
    req.onreadystatechange = () => {
      if (req.readyState === 4) {
        if (req.status === 200) {
          resolve(req.responseText);
        } else {
          reject(req.responseText);
        }
      }
    };
  });
};

This can be done when calling

_ajax({url:xxxxx}).then(res=>{fun(res)},rej=>{fun(rej)}).then...

Use form form on html and onsubmit to send a request

const _onsubmit = (route) => {
  const username = String(document.getElementById("username").value);
  const password = String(document.getElementById("password").value);
  const captcha = String(document.getElementById("captcha").value);
  if (username.length === 0) {
    alert("User name cannot be empty");
    return false;
  }
  if (password.length === 0) {
    alert("Password cannot be empty");
    return false;
  }
  _ajax({
    url: `${url}/api/${route}`,
    method: "POST",
    contentType: "application/json",
    data: JSON.stringify({
      username: username,
      password: password,
      captcha: captcha,
    }),
  }).then(
    (resolved) => {
      alert(resolved);
      if (route === "login")
        window.localStorage.token = JSON.parse(resolved).data.token;
      window.location.href = "/";
    },
    (rejected) => {
      alert(rejected);
    }
  );
  return false;
};

server.js to write various APIs through model JS to add and query data, Some middleware are defined to verify the input user name, password and verification code

app.post("/api/register",[nameValid,pwdValid,captcha],async (req, res) => {
  console.log(req.body)
  // This step is written to the database
  try {
    const user = await User.create({
      username: req.body.username,
      password: req.body.password,
    });
    res.send({"message":"success",user:user});
  } catch (e) {
    res.status(422).send(`${req.body.username} User name already exists`);
    console.log(e)
  }
});

model.js defines the User object when server JS During the create operation, The password will be salted, hashed and stored in the database bcrypt's salt does not need to be stored The database uses MongoDB

const UserSchema = new mongoose.Schema({
  username: { type: String, unique: true,required: true },
  password: {
    type: String,
    required: true,
    set(val) {
      // Hash with bcrypt
      return bcryptjs.hashSync(val,bcryptjs.genSaltSync(10));
    },
  },
});

After the user logs in successfully, nodejs generates a token and sends it to the client, which is saved in localStorage In, when requesting some specific APIs, bring this token for back-end authentication, and delete it when logging off token. I didn't make the token expire regularly

SECRET is required for token encryption. It is better to make it into an environment variable. Here I define it as a global variable

app.post("/api/login",[nameValid,pwdValid,captcha], async (req, res) => {
  // console.log(req.body);
  const user = await User.findOne({
    username: req.body.username,
  });
  if (!user) {
    return res.status(422).send(`${req.body.username} user name does not exist`);
  }
  const valid = require("bcryptjs").compareSync(
    req.body.password,
    user.password
  );

  if (!valid) {
    return res.status(422).send("Password error");
  }
  const token = jwt.sign(
    {
      id: String(user._id),
    },
    SECRET
  );
  res.send({"message":"success", "data": { user, token: token }});
});

The verification code is generated by SVG captcha, and an img tag at the front end calls the verification code api and gets the picture, The server stores the verification code text into the session The verification code entered at the current end is transmitted, which is the same as this session Compare them in It is also a middleware

const captcha = async (req, res, next) => {
  const cap = String(req.body.captcha)
  // console.log(req.session)
  req.session.captcha===cap?next():res.status(422).send("Incorrect verification code")
};

We need to deal with the cross domain problem, because the api for generating verification code and verification code is different, so Sessions can be shared

app.all("*", function (req, res, next) {
  res.header("Access-Control-Allow-Credentials", "true");
  //Set the domain name that allows cross domain, * means that any domain name is allowed to cross domain
  res.header("Access-Control-Allow-Origin", "http://localhost:8080");
  //Allowed header types
  res.header("Access-Control-Allow-Headers", "origin, expires, content-type, x-e4m-with, authorization");
  //Cross domain allowed request mode
  res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS");
  if (req.method.toLowerCase() == "options") res.send(200);
  //Let options try to request a quick end
  else next();
});