A Probe into koa-session Source Code

Introduction

Now that you've dug a hole, you have to fill it in. In the previous article on Authentication of Next, we talked about how to use koa-session, mainly configuring and changing the content of session. Let's review it. This is the configuration file.

app.use(
  session(
    {
      key: "lxg",
      overwrite: true, //Overwrite Cookie
      httpOnly: true, //Allowed to change through JS
      renew: true,//Renewal of the session as it nears its expiration date keeps the user logged in all the time
      store: new MongooseStore({
        createIndexes: "appSessions",
        connection: mongoose,
        expires: 86400, // Default one day
        name: "AppSession"
      }) //Pass in an external storage for session s, and I'm using mongodb here.
    },
    app
  )

This is the login interface, using koa-session to modify session

 static async login(ctx) {
    let { passwd, email } = ctx.request.body;
    let hasuser = await UserModel.findOne({ email: email, passwd: md(passwd) });

    if (!hasuser) {
      return ctx.error({});
    } else {
      let userid = hasuser._id;
      const { session } = ctx;
      session.userid = userid;
      return ctx.success({ data: { userid: userid } });
    }
  }

It can be seen that it is still very simple to configure and modify sessions. Of course, it is not enough. We also know why, so we can go to the source code to explore the workflow of koa-session.

0. Two storage methods

In the source code, we can clearly see that the whole process is divided into using and not using external storage. When there is no store, all session data is encoded and stored in cookies by user browser. After setting up the store, the data is stored in the external storage of the server, and cookies are stored only for one use. External Key, koa-session only needs to take this key to find data in external storage. There are two advantages of using store storage over using cookie directly to store data.

  • Data size is unrestricted

    • Using cookies imposes strict limits on the size of cookies, and a little more data can't fit in.
  • Data security

    • When using Cookie, the data is stored in cookie after simple coding. It is easy to decode the real data, and it is stored locally with the user. It is easy to be stolen by other programs.

store is more recommended in practical applications. Of course, data is very simple and cookie s are not required to be used confidentially.

1. Default parameter processing

Understand some of the slightly higher-level JS knowledge required in this section. If you don't understand the code, you can first understand these knowledge points. Of course, koa-related concepts also need to understand a little.

Sentence Source knowledge points
getter/setter ES5
Object.defineProperties/Object.hasOwnProperty Method of Object Object
symbol ES6

Open the index.js file under the koa-session folder in node_modules, and you will see that the main process function receives an app(koa instance) and opt (configuration file) as parameters.

The first function to be called is this, and the input parameter is opt.

This function replaces the default configuration with the configuration set by the user.

2. Create session objects

Next is it. The incoming parameters are instance context and configuration parameters.

What this function does is create a new session if the current context does not set sessions. Using getter, a ContextSession object is created when the property is first called by the outside world. From the reference relationship of attributes, we can see that the ctx.session we use directly is actually a ContextSession object.

3. Initialization of external storage

This step is only achieved by using external storage sessions, which are stored in external storage such as databases, caches and even files. Cookies are only responsible for storing a unique user identifier. koa-session uses this identifier to find data in external storage. If no external storage is used, all session data is simply encoded and stored in Co. In okie, this limits both storage capacity and security. Let's look at the code:

The first line of this function creates a ContextSession object called sess.

Generally speaking, it is to judge whether there is an externalKey or not, and if not, build a new one. This externalKey is a string stored in cookie that uniquely identifies the user. koa-session uses this string to find the corresponding user session data in external storage.

The key point is to save the current seed by hash coding, compare the hash at the last time, and save the session if it changes, so that the initialization is completed and the initial state of the session is saved.

4. Initialize cookie s

In the main process, we don't see how to initialize session without using external storage. In fact, initialization in this case occurs after session is operated in business logic, for example:

 const { session } = ctx;
 session.userid = userid;

The session attribute interceptor of CTX is triggered. ctx.session is actually the return value of the get method of sess:

Finally, the session initialization operation is performed in the get method of the ContextSession object:

You can see that this.initFromCookie() is executed without external storage.

 initFromCookie() {
    debug('init from cookie');
    const ctx = this.ctx;
    const opts = this.opts;

    const cookie = ctx.cookies.get(opts.key, opts);
    if (!cookie) {
      this.create();
      return;
    }

    let json;
    debug('parse %s', cookie);
    try {
      json = opts.decode(cookie);
    } catch (err) {
      // backwards compatibility:
      // create a new session if parsing fails.
      // new Buffer(string, 'base64') does not seem to crash
      // when `string` is not base64-encoded.
      // but `JSON.parse(string)` will crash.
      debug('decode %j error: %s', cookie, err);
      if (!(err instanceof SyntaxError)) {
        // clean this cookie to ensure next request won't throw again
        ctx.cookies.set(opts.key, '', opts);
        // ctx.onerror will unset all headers, and set those specified in err
        err.headers = {
          'set-cookie': ctx.response.get('set-cookie'),
        };
        throw err;
      }
      this.create();
      return;
    }

    debug('parsed %j', json);

    if (!this.valid(json)) {
      this.create();
      return;
    }

    // support access `ctx.session` before session middleware
    this.create(json);
    this.prevHash = util.hash(this.session.toJSON());
  }

The main logic is to create a Session object and initialize it without finding an existing session.

If it is the first time to access the server, set isNew to true.

4. Save changes

When we have finished our business logic, we call the sess.commit function to save:

It is mainly based on the hash value to determine whether the session has been changed. If the session has been changed, it calls this.sava to save, which is the real saving function.

async save(changed) {
    const opts = this.opts;
    const key = opts.key;
    const externalKey = this.externalKey;
    let json = this.session.toJSON();
    // set expire for check
    let maxAge = opts.maxAge ? opts.maxAge : ONE_DAY;
    if (maxAge === 'session') {
      // do not set _expire in json if maxAge is set to 'session'
      // also delete maxAge from options
      opts.maxAge = undefined;
      json._session = true;
    } else {
      // set expire for check
      json._expire = maxAge + Date.now();
      json._maxAge = maxAge;
    }

    // save to external store
    if (externalKey) {
      debug('save %j to external key %s', json, externalKey);
      if (typeof maxAge === 'number') {
        // ensure store expired after cookie
        maxAge += 10000;
      }
      await this.store.set(externalKey, json, maxAge, {
        changed,
        rolling: opts.rolling,
      });
      if (opts.externalKey) {
        opts.externalKey.set(this.ctx, externalKey);
      } else {
        this.ctx.cookies.set(key, externalKey, opts);
      }
      return;
    }

    // save to cookie
    debug('save %j to cookie', json);
    json = opts.encode(json);
    debug('save %s', json);

    this.ctx.cookies.set(key, json, opts);
  }

You can see here that _expire and maxAge, which are two fields related to session aging, are saved into session. Where _expire is used to determine whether the session expires the next time you visit the server, and _maxAge is used to save the expiration time.
External Key is then used to determine whether external storage is used to enter different storage processes.

summary

Here's a loan. This article Flow charts used

It shows the whole logic process very well.

Welcome to my Blog

Tags: Javascript Session JSON Mongoose MongoDB

Posted on Thu, 10 Oct 2019 05:39:40 -0700 by ankurcse