Usage description of egg-oauth2-server password mode

Official document of egg oauth2 server: https://github.com/Azard/egg-oauth2-server

Implementation examples provided in the official document of egg-oauth2-server: https://github.com/Azard/egg-oauth2-server/blob/master/test/fixtures/apps/oauth2-server-test/app/extend/oauth.js

Example explanation of egg-oauth2-server official document (not the latest, only for reference): https://cnodejs.org/topic/592b2aedba8670562a40f60b

What is egg oauth2 server for? In short, when you send a request to the server, let the server know who you are and whether it is a legal request, so that the server can safely return the data to you. In the past, the back-end developers may agree with the front-end personnel by customizing the token to add the token information to the request header that needs to be verified. In fact, egg-oauth2-server is the same principle, but the token information does not need to be customized by the back-end developers, and egg-oauth2-server will be generated automatically.

The official website introduces two modes of egg oauth2 server: 1 password mode 2. authorization_ Code mode, this article only introduces the password mode (because I have studied this mode for 3 days, I am ashamed to understand it.)

Here are the implementation steps:

1. Install egg-oauth2-server module:

npm i egg-oauth2-server --save (cnpm can be faster, no, I take back what I said, cnpm will be very fast);

2. Create OAuth. In App - > extend directory JS file

3. In config - > config default. JS file, add

config.oauth2Server = {
    grants: [ 'password' ],
  };

4. In config - > plugin JS file

oAuth2Server: {
    enable: true,
    package: 'egg-oauth2-server',
  }

5. After the above configuration, you can write the specific function implementation code. First, we need to understand the steps of password Mode authentication:

OAuth built above JS file, the simple code is as follows:

'use strict';

// need implement some follow functions
module.exports = app => {  
  class Model {
    constructor(ctx) {}
    async getClient(clientId, clientSecret) {}
    async getUser(username, password) {}
    async saveToken(token, client, user) {}
    async getAccessToken(bearerToken) {}
  }  
  return Model;
};

In this file, four methods need to be implemented (only password mode needs to implement these four methods). The specific steps are as follows:

When the current end user needs to obtain a token, the system will automatically execute the methods getClient -- > getUser -- > saveToken step by step. When the server needs to verify a token, the system will automatically execute the method getAccessToken.

So what are the steps to trigger the client to obtain the token and how the server verifies the token? Router in code JS file, the code is as follows:

module.exports = app => {
  const { router, controller } = app;
  router.get('/api/currentUser', controller.home.currentUser);
  router.get('/api/getUser', app.oAuth2Server.authenticate(),controller.admin.getUserList);
  app.all('/api/user/token', app.oAuth2Server.token());

};

The back-end routing interface can be written in the form of router without authentication Get ('/ api/currentUser', controller. Home. Currentuser), that is, when the front end sends the request api/currentUser, the back end returns the result through the code execution in the controller. When adding authentication, the front end needs to obtain the token information first, app all('/api/user/token', app.oAuth2Server.token()); This line of code is the route that the front end obtains the token through the API / user / token interface. The interface can be customized, but the app oAuth2Server. Token () is a fixed writing method. Whenever the API / user / token interface is called, the system will automatically execute OAuth JS, getClient -- > getUser -- > saveToken. The specific implementation contents are shown below.

router.get('/api/getUser', app.oAuth2Server.authenticate(),controller.admin.getUserList); When the current end calls the API / getuser 'interface, the server will compare the token in the request header with the token information previously returned to the front end. If it is consistent, the verification passes. app. oAuth2Server. Authenticate () is a fixed notation.

6. Next, let's take a look at how the front end sends a request to obtain a token.

In my project, I put the request to get the token in the click method of the login button

export async function fakeAccountLogin(params: LoginParamsType) {
  return request('/api/user/token', {
    method: 'POST',
    data: qs.stringify({username:params.userName,password:params.passWord, grant_type: "password"}),
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      Authorization:
      "Basic bXlfYXBwOm15X3NlY3JldA=="
    },
  });
}

When I click the login button, I will get the user name and password in the login box, that is, username and password, and then take these two fields as the requested parameters. In addition to these two parameters, I also need to send a grant_type parameter, because this example uses password authentication mode, so the parameter is password;

When passing parameters, I used qs Stringify for conversion. qs I quote from the query string module. Students who develop this front-end should understand it better. I won't repeat it here. Why use this conversion? Because of the content type in the header, we must pass the value of application/x-www-form-urlencoded, and this parameter passing method cannot be recognized by the background without qs conversion. (I don't know why I can't recognize it for the time being);

The header of the request to obtain the token is special and needs to be set separately here. The value of "content type" must be "application/x-www-form-urlencoded", while the value in Authorization comes from the agreement with the back-end developer, and the fixed writing method is: "64 bit conversion value of the value of Basic agreement". What does this mean?

bXlfYXBwOm15X3NlY3JldA = = this value is actually the value after Base64 bit conversion. Students who don't know how to convert can directly convert Baidu Base64 online. This value is in the form of clientId:clientSecret. These two values are in OAuth My definition is in my definition file, JS_ app:my_ Secret. Note that the base64 conversion is an overall conversion, that is, my_ app:my_ The whole secret is converted to bxlfyxbwm15x3nly3jlda==

7. After the current end sends the request to obtain the token, the server will execute the three methods according to the steps. The specific implementation code is as follows:

'use strict';
module.exports = app => {
  class Model {
    constructor(ctx) {
      this.ctx = ctx;
    }
    async getClient(clientId, clientSecret) {
      if (
        clientId !== 'my_app' &&
        clientSecret !== 'my_secret'
      ) {
        return;
      }
      return { clientId, clientSecret, grants: [ 'password' ] };
    }
    async getUser(username1, password) {
      const user = await app.mysql.get('user', { username: username1 });
      if (!user) {
        return;
      }
      if (user.password === password) {
        return { id: user.uid };
      }
      return;
    }
    async saveToken(token, client, user) {
      console.log(token);
      console.log(user);
      const _token = Object.assign({}, token, { user }, { client });
      const updateToken = await app.mysql.update('user',
        {
          accessToken: token.accessToken,
          expires: token.accessTokenExpiresAt,
          clientId: client.clientId,
        }, {
          where: {
            uid: user.id,
          },
        });
      if (updateToken) {
        return _token;
      }
    }
    async getAccessToken(bearerToken) {
      console.log(bearerToken);
      const data = await app.mysql.get('user', { accessToken: bearerToken });
      if (data) {
        const user = { username: data.username, id: data.uid };
        let token = {};
        token.user = user;
        token.client = {
          clientId: 'my_app',
          clientSecret: 'my_secret',
          grants: [ 'password' ],
        };
        token.accessTokenExpiresAt = new Date(data.expires);
        token.refreshTokenExpiresAt = new Date(data.expires);
        return token;
      }
      return false;
    }
  }
  return Model;
};

When executing the getClient method, there are two parameters ClientID and clientsecret. These two parameters are the Authorization value (after Basic) in the header of the token request automatically obtained by the system, and convert the base64 bit value into a value that we can understand (ha ha, so it's a layman, but it's easy to understand), that is, my_app and my_secret. Compare the obtained value with the previously set value. If it is consistent, return {clientId, clientSecret, grants: ['password ']}; Then the system automatically enters the second step and executes getUser.

getUser also has two parameters, username and password. These two parameters come from the username and password passed by the user when calling the token interface, that is, the user name and password of logging in to the system. In the getUser method, I query the database to compare whether the password passed from the front end is consistent with that in the database. If it is consistent, return {ID: user. Uid}; (uid is the primary key of the user table in my database.) Then automatically enter the third step: saveToken.

saveToken has three parameters (token, client and user). The value of the first parameter token contains the token value to be returned by the system to the front end, that is, when this step is carried out, the value of token has been generated, and the style of the value returned to the front end is as follows:

{"access_token":"059a38f92e64dcc39589032fa4a96afbd9248627","token_type":"Bearer","expires_in":3599,"refresh_token":"4780c55d39bfa6a5facef48485ad162fd604fabb"}

What we use in the front end is access_token.

The second parameter client is the return value in the first step, and the third parameter user is the return value in the second step. In the saveToken method, I wrote a logic to save the token to the database for comparison with the token in the user's request.

8. After obtaining the token, the current end user can save the token in a cookie or local storage, and then add the token information in the header when sending other requests to the back end. In the request method encapsulated with UMI request, I use the interceptor to set the token of each request

request.interceptors.request.use((url, options) => {
  if(!url.includes('api/user/token')){
    return (
      {
        options: {
          ...options,headers: {
            Authorization: 'Bearer ' + Cookies.get('SESSION_TOKEN'),
          },
        },
      }
    );
  }
  
});

The meaning of the code is that except for the api/user/token interface for obtaining the token, all other interfaces should be added with authorization: 'Bearer' + cookies get('SESSION_TOKEN'). (Bearer is a fixed writing method, followed by a space. Cookie.get ('session_token ') is to obtain the token value saved in the cookie)

9. In the last step, when the back-end service receives a request containing a token, it will automatically call the getAccessToken method, which has a parameter, that is, the token value in the request, which is automatically read by the system. Then compare the token value with the token value in the database. The above is the whole process of password Mode authentication. If you encounter problems in the debugging process, please leave a message or pay attention to the message "web front-end DreamWorks" official account. I will answer for you in time.

Added by hossein2kk on Sun, 20 Feb 2022 21:11:40 +0200