“This is the 17th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

This article is part of the KOA dependency series, the first few of which can also be viewed on the site:

  • Koa depends on the library parseURL
  • Koa relies on the libraries Type-is and Content-Disposition
  • The Koa dependent libraries accept, Content-Type, and cache-Content-Type
  • Koa relies on the libraries encodeURL and escape- HTML
  • Statuses are the libraries that Koa relies on

When using KOA, we can use cookies under the context for cookie management. As you can see in the source code, the cookies under the context are actually an instance of cookies provided by the cookies library.

The most common methods to operate cookies are get and set. The server can add cookies to the browser through set-cookie in response header. After receiving the set-cookie header, the browser saves the Cookie content. When a user requests resources in the same domain again, the browser automatically adds the Cookie information to the Request header. Therefore, get and set in the cookies library are implemented by controlling these two headers.

The conventional cookie GET and set logic is not complicated. Like other headers, regular expressions are used to match the cookie header during GET, and parameters are processed into strings and set to set-cookie header during set. Due to the complex structure of cookies, a Cookie type is defined inside cookies to encapsulate Cookie format data:

function Cookie(name, value, attrs) {
  if(! fieldContentRegExp.test(name)) {throw new TypeError('argument name is invalid');
  }

  if(value && ! fieldContentRegExp.test(value)) {throw new TypeError('argument value is invalid');
  }

  this.name = name
  this.value = value || ""

  for (var name in attrs) {
    this[name] = attrs[name]
  }

  if (!this.value) {
    this.expires = new Date(0)
    this.maxAge = null
  }

  if (this.path && ! fieldContentRegExp.test(this.path)) {
    throw new TypeError('option path is invalid');
  }

  if (this.domain && ! fieldContentRegExp.test(this.domain)) {
    throw new TypeError('option domain is invalid');
  }

  if (this.sameSite && this.sameSite ! = =true && !SAME_SITE_REGEXP.test(this.sameSite)) {
    throw new TypeError('option sameSite is invalid')
  }
}

Cookie.prototype.path = "/";
Cookie.prototype.expires = undefined;
Cookie.prototype.domain = undefined;
Cookie.prototype.httpOnly = true;
Cookie.prototype.sameSite = false;
Cookie.prototype.secure = false;
Cookie.prototype.overwrite = false;
Copy the code

Above we can see the property information we can add when setting cookies. This will create a cookie object, which will eventually be converted to a string using the toHeader method:

Cookie.prototype.toHeader = function() {
  var header = this.toString()

  if (this.maxAge) this.expires = new Date(Date.now() + this.maxAge);

  if (this.path     ) header += "; path=" + this.path
  if (this.expires  ) header += "; expires=" + this.expires.toUTCString()
  if (this.domain   ) header += "; domain=" + this.domain
  if (this.sameSite ) header += "; samesite=" + (this.sameSite === true ? 'strict' : this.sameSite.toLowerCase())
  if (this.secure   ) header += "; secure"
  if (this.httpOnly ) header += "; httponly"

  return header
};
Copy the code

As cookies are often used to store user identity information, they have high security requirements. Cookies library provides signature-related logic, which is implemented here using keygrip library. {signed: true} to get the signature cookie. The key of the signature cookie is the original name followed by a.sig suffix.

  • If the signed cookie hash matches the first key, the original cookie value is returned.
  • If the signed cookie hash matches any other key, the original cookie value is returned and the value of the signed cookie is updated to the hash of the first key.
  • If the signed cookie hash does not match any key, nothing is returned and the cookie is deleted.
Cookies.prototype.get = function(name, opts) {
  var sigName = name + ".sig", header, match, value, remote, data, index , signed = opts && opts.signed ! = =undefined ? opts.signed : !!this.keys

  header = this.request.headers["cookie"]
  if(! header)return

  match = header.match(getPattern(name))
  if(! match)return

  value = match[1]
  if(! opts || ! signed)return value

  remote = this.get(sigName)
  if(! remote)return

  data = name + "=" + value
  if (!this.keys) throw new Error('.keys required for signed cookies');
  index = this.keys.index(data, remote)

  if (index < 0) {
    this.set(sigName, null, {path: "/".signed: false})}else {
    index && this.set(sigName, this.keys.sign(data), { signed: false })
    return value
  }
};
Copy the code

In the opts parameter of the set method, we can set the cookie property information: MaxAge, Expires, Path, Domain, Secure, httpOnly, sameSite, signed, Overwrite, etc. Cookie with Secure set to true needs to be checked to see if it is a secure environment. Cannot be sent in unsafe environments:

req.protocol === 'https' || req.connection.encrypted
Copy the code

For cookies whose signed value is set to true, a signature is added before setting the cookie, that is, a.sig suffix is added after the key, and the value is signed using keygrip.

PushCookie is called, and toHeader is used to convert the cookie object into a string. If overwrite is true, the cookie with the same name will be deleted.

Finally, call the setHeader method on Response to add set-cookie to Set cookies for the browser. Here is a version should be compatible with the judge, if the response is above the set method at this time there should be no setHeader method, this call is HTTP. OutgoingMessage. SetHeader method of prototype, OutgoingMessage is the parent of Response, and the method can be found in the Node documentation.

var setHeader = res.set ? http.OutgoingMessage.prototype.setHeader : res.setHeader
setHeader.call(res, 'Set-Cookie', headers)
Copy the code

At this point, the internal processing of the cookies library is completed. It is convenient to use cookies in the actual development, but sometimes it also brings restrictions and security problems, so the actual use needs to be combined with specific scenarios.