Login and registration, user authentication

preface

User authentication is the most important part of a system project. Almost all requirements are designed around the user system. If you look at a lot of projects out there, there’s not one that’s built on a user base: blogging, e-commerce, tools, management systems, music, games, etc. So we put user authentication in the first interface to implement.

  • Use of the Egg-JWT plugin
  • Egg middleware programming
  • Token authentication

What is user authentication

The definition of “user authentication” is quoted from Baidu Baike:

User authentication, a method used in a communications network to authenticate users attempting to access services from a service provider. When a user logs in to the DSMP or uses data services, the service gateway or Portal sends this message to the DSMP to check the validity and validity of using data services (whether the user is in the active state).

The above explanation is too official, and I like to simplify complicated things. In my opinion, authentication is to establish a mutual trust mechanism between the web page and the user in an agreed way when the user browns the web page or App, and then return the information the user needs.

There are four authentication mechanisms:

  • HTTP Basic Authentication
  • session-cookie
  • Token Token
  • OAuth(Open License)

The authentication mode used in this booklet is token token mode. Token can be used in web pages, clients, applets, browser plug-ins, etc. If cookie authentication is used, clients and small programs cannot use this interface, because they do not have the concept of domain, and cookies need to exist in a certain domain.

Register interface implementation

The whole registration process is roughly as follows:

Note that the database name in the config.default.js database configuration item is changed, because the previous section created a new database:

exports.mysql = {
  // Configure single database information
  client: {
    // host
    host: 'localhost'./ / the port number
    port: '3306'./ / user name
    user: 'root'./ / password
    password: ' '.// The password is not set
    // Database name
    database: ' '.// Database name
  },
  // Whether to load to app, enabled by default
  app: true.// Whether to load it to the agent
  agent: false};Copy the code

As we all know, when users register on the web side, they will report two parameters, “username” and “password”, and then need to get these two parameters in the server code.

Create user.js in the Controller directory to write user-specific code as follows:

'use strict';

const Controller = require('egg').Controller;

class UserController extends Controller {
  async register() {
    const { ctx } = this;
    const { username, password } = ctx.request.body; // Get the parameters required for registration}}module.exports = UserController;
Copy the code

If you have username and password, check whether the two parameters are empty. If it is empty, an error message is returned:

// call null
if(! username || ! password) { ctx.body = {code: 500.msg: 'Account password cannot be empty'.data: null};return;
}
Copy the code

At this time also need a judgment, according to the user incoming username to the database user table query, whether has been registered.

Since there is no mobile authentication SMS service, username is the only identifier.

'use strict';

const Service = require('egg').Service;

class UserService extends Service {
  // Obtain user information by user name
  async getUserByName(username) {
    const { app } = this;
    try {
      return await app.mysql.get('user', { username });
    } catch (error) {
      console.log(error);
      return null; }}}module.exports = UserService;
Copy the code
  • useasyncand awaitIs required if you want to catch errorstry... catchIf an error occurs during code execution, it will be caught by a catch.

Go back to controller/user.js and continue adding the logic. Under the “null-operation” logic, check whether the logic has been registered:

async register(){...Verify that the account name already exists in the database
const userInfo = await ctx.service.user.getUserByName(username); // Get user information

// Check whether it already exists
if (userInfo && userInfo.id) {
  ctx.body = {
    code: 500.msg: 'Account name has been registered, please re-enter'.data: null};return; }}Copy the code

Write the account and password to the database. Add the logic:

// The default avatar is placed outside of user.js to avoid repeated declarations.
const defaultAvatar = 'http://s.yezgea02.com/1615973940679/WeChat77d6d2ac093e247c361f0b8a7aeb6c2a.png';
// Call the service method to store the data to the database.
const result = await ctx.service.user.register({
  username,
  password,
  signature: 'Always be kind and clear. '.avatar: defaultAvatar,
});

if (result) {
  ctx.body = {
    code: 200.msg: 'Registration successful'.data: null}; }else {
  ctx.body = {
    code: 500.msg: 'Registration failed'.data: null}; }Copy the code

Service /user.js adds a register to write to the database:

/ / register
async register(params) {
  const { app } = this;
  try {
    return await app.mysql.insert('user', params);
  } catch (error) {
    console.log(error);
    return null; }}Copy the code

What this code does is store the user registration data into the user table in the database. By throwing the interface in router.js as follows:

'use strict';

/**
 * @param {Egg.Application} app - egg application
 */
module.exports = app => {
  const { router, controller, middleware } = app;
  router.post('/api/user/register', controller.user.register);
};
Copy the code

Open Postman and test manually to see if you can successfully store data to the database.

Check whether the database takes effect:

You can see that the registration information has entered the database, and verify that the same request is made again to see if the judgment of the server code is valid.

As expected, you will see “Account name has been registered, please re-enter”.

Generally, passwords need to be encrypted in MD5 or other forms to prevent user information from being stolen after database leaks. Encryption this piece, is a relatively deep knowledge point, in order to let everyone smoothly go through the whole project process, here is not to explain.

3. Login interface implementation

Registration is followed by the login process. The login interface is invoked with the registered “username” and “password”, and the interface returns a token. The generation and use of this token is analyzed by a flow chart:

After the web server obtains the token, it needs to save it in the browser. It has an expiration time, usually 24 hours. If it is not for some information sensitive websites or apps, such as banks and government affairs, the expiration time can be set to longer.

Each subsequent request, whether it is to obtain data or submit data, needs to carry the token, so as to identify which user the GET or POST is the behavior.

You may wonder how the server uses tokens to determine which user is making the request. If egg-JWT has encryption, it will also have decryption. Decrypt the token to get the original encrypted token information, the content of the information is roughly the user information at the time of registration. It is analyzed through a flow chart: After the web server obtains the token, it needs to save it in the browser. It has an expiration time, usually 24 hours. If it is not for some information-sensitive websites or apps, such as banks and government affairs, the expiration time can be set longer.

Each subsequent request, whether it is to obtain data or submit data, needs to carry the token, so as to identify which user the GET or POST is the behavior.

You may wonder how the server uses tokens to determine which user is making the request. If egg-JWT has encryption, it will also have decryption. Decrypt the token to get the original encrypted token information, the content of the information is roughly the user information at the time of registration. Analyze it through a flow chart:

This means that when you log in, you use:

{username: 'username ', password:'123'}Copy the code

Then the token contains the above information. When the server parses the token, the user name and password will be resolved. Knowing who initiated the request, the subsequent retrieval and storage of data for that user.

After analyzing the above authentication process, write the login interface.

First you need to install the egg-jWT plug-in under your project and do the following:

npm i egg-jwt -S
Copy the code

This is the address of its warehouse, there are some simple documents in the warehouse, many specific operations are not written in the document, I also searched a lot of relevant information, to design such a set of authentication process.

Add the plugin under config/plugin.js:

.jwt: {
  enable: true.package: 'egg-jwt',}...Copy the code

Then go to config/config.default.js and add the custom encrypted string:

config.jwt = {
  secret: 'Encrypted string'};Copy the code

Secret is an encrypted string that will later be used to generate a string of tokens combined with user information. Secret is in the server code, and ordinary users can’t find it through a browser, so don’t give it away or it could be used by malicious people.

Create a new login method under /controller/user.js and add the analysis line by line as follows:

async login() {
  // App is a global property, which means that all plug-in methods are embedded in the app object.
  const { ctx, app } = this;
  const { username, password } = ctx.request.body;
  // According to the user name, find the corresponding ID in the database operation
  const userInfo = await ctx.service.user.getUserByName(username);
  // No user is found
  if(! userInfo || ! userInfo.id) { ctx.body = {code: 500.msg: 'Account does not exist'.data: null};return;
  }
  // Find the user, and determine the input password and database user password.
  if(userInfo && password ! == userInfo.password) { ctx.body = {code: 500.msg: 'Wrong account password'.data: null};return; }}Copy the code

App is an attribute in the global context. Plugins mounted in config/plugin.js can be obtained through app. XXX, such as app.mysql and app.jwt. The attributes thrown by config/config.default.js can be obtained from app.config. XXX, for example, app.config.jwt.secret.

So continue to write the subsequent login logic, after the above judgment is passed, the subsequent code logic is as follows:

The app.jwt.sign method takes two arguments. The first is the object, which contains the content to be encrypted. The second is the encrypted string, which was mentioned above. const token = app.jwt.sign({ id: userInfo.id, username: userInfo.username, exp: Math.floor(date.now () / 1000) + (24 * 60 * 60), // Token valid for 24 hours}, app.config.jwt.secret); Ctx. body = {code: 200, MSG: 'login succeeded ', data: {token,},}; }Copy the code

Sign method and app.config.jwt.secret encrypted string (previously declared Nick) to generate a token. This token is a string of long encrypted string, such as dkadaklsfnasalkd9a9883kndlas9dfa9238jand a bunch of ciphertext.

After that, in the router.js script, throw the login interface:

'use strict';

/**
 * @param {Egg.Application} app - egg application
 */
module.exports = app => {
  const { router, controller, middleware } = app;
  router.post('/api/user/login', controller.user.login);
Copy the code

Try using Postman to test if the interface works. If it works, it should look like this:

This token can be understood as it contains two parameters, username and ID. However, when I get this token from the client, I cannot decode its internal contents. Username and ID must be resolved using an encrypted string in combination with egg-jwt. So, the user’s authority is established in this way.

In fact, cookie is also a similar principle. Every request, request header Requert will carry cookie, and the server will parse out the corresponding user information by obtaining the cookie carried in the request, and then operate the corresponding request.

So I want to verify how the information inside the token can be resolved on the server side when making a request with a token interface. In /controller/user.js, add a new validation method, test, as follows:

// Verify the method
async test() {
  const { ctx, app } = this;
  // Get user_id through token resolution
  // The request header gets the authorization attribute with a value of token
  const token = ctx.request.header.authorization;
  // Parse the token value through app.jwt.verify + encrypted string
  const decode = app.jwt.verify(token, app.config.jwt.secret);
  // Response interface
  ctx.body = {
    code: 200.msg: 'Obtain success'.data: {
      ...decode,
    },
  };
}
Copy the code

Initiate the request, through the request header header, carrying authentication information, can let the server by CTX. Request. The header. The authorization access to the token, and resolve the content returned to the client, don’t forget to go to the router. The js throw this interface:

router.get('/api/user/test', controller.user.test);
Copy the code

Test the interface to see if it works:

Notice in the request headerHeadersTo addauthorizationProperty, and the value is obtained from the previous login interfacetokenValue. After making the request, we get the return value,id = 3,username = pika. It has been proved that the authentication has basically been completed. (Note that id=3 because I have tested it several times before and the database id field is incremented by default)

4. Login authentication middleware

Middleware can be understood as A filter. For example, there are four interfaces A, B, C and D that require user permissions. If user permissions are to be determined, it is necessary to determine whether the user logs in at the control layer of these four interfaces, as shown in the following code:

A() {
    if(token && isValid(token)) {
        // do something}}B() {
    if(token && isValid(token)) {
        // do something}}C() {
    if(token && isValid(token)) {
        // do something}}D() {
    if(token && isValid(token)) {
        // do something}}Copy the code

There are two drawbacks to this operation:

1. Every time you write a new interface, you have to make a judgment call inside the method. 2. Once the authentication has been adjusted, you need to modify every code used to determine the login.

Now the concept of middleware is introduced. When the interface is requested, a layer of middleware is passed to determine whether the request is initiated in the login state. Open the middleware project, create a new folder in your app directory, and add jwterr.js to that folder, as shown below:

Add the following code to it:

'use strict';

module.exports = secret= > {
  return async function jwtErr(ctx, next) {
    const token = ctx.request.header.authorization; // If there is no token, a null string is returned
    // eslint-disable-next-line no-unused-vars
    let decode;
    if(token ! = ='null' && token) {
      try {
        decode = ctx.app.jwt.verify(token, secret); / / authentication token
        await next();
      } catch (error) {
        console.log('error', error);
        ctx.status = 200;
        ctx.body = {
          msg: 'Token has expired, please log in again'.code: 401};return; }}else {
      ctx.status = 200;
      ctx.body = {
        code: 401.msg: 'Token does not exist'};return; }}; };Copy the code

First, by default, the middleware throws a function that returns an asynchronous method called jwtErr. The jewErr method takes two arguments, CTX, to get the global object app in CTX.

First, by CTX. Request. The header. The authorization to obtain the authorization to request header properties, it is the request token value of interface is carried without carrying the token, the value is a null string. Verify method ctx.app.jwt.verify is used to verify if the token exists and is valid. If it exists and is valid, continue to execute the subsequent interface logic by verifying await next(). Otherwise, the token is invalid or does not exist.

After writing the above middleware, go to router.js to use it, as shown below:

'use strict'; /** * @param {Egg.Application} app - egg application */ module.exports = app => { const { router, controller, middleware } = app; const _jwt = middleware.jwtErr(app.config.jwt.secret); // Pass the encrypted string router.post('/ API /user/register', controller.user.register); router.post('/api/user/login', controller.user.login); router.get('/api/user/test', _jwt, controller.user.test); // Put the second parameter as a middleware filterCopy the code

Simulate a request without authorization as follows:

Uncheck the option, initiate the request, as shown above, enter the middleware and determine that the token does not exist. Write a token value in invalid case.

It can be seen that the middleware logic of login verification has basically been implemented. If you want to add some new interfaces that require user permissions later, you can add _jWT method in the second parameter of the method, so that user permissions can be judged before entering the interface logic.

In live.

The essence of the entire server content, no matter what the project, to do user permissions, this logic is unavoidable. However, the choice of authentication method depends on the needs of the project and the team. After the above authentication, the project becomes a multi-user project.