Hello, I’m a mouse

Most projects are designed to manage login status, and login status is usually maintained using cookies, sessions, and Credentials. For details, see my other article on the three methods of front-end and back-end persistent access

In this paper, the client Cookie is used to maintain login status. It should be noted that no matter what Session, data transmission depends on secure network transmission channel, namely HTTPS, so the security of network transmission must be ensured in formal development.

The parts involved in this paper are as follows:

koa-session

This is a simple session management middleware that uses Cookie sessions by default and also supports server stores.

Koa-session greatly simplifies the difficulty of development, but also has its disadvantages. For me, the most important one is that it is not convenient to customize sessions, but this does not affect the convenience it brings to our development.

Semi-automated state management

Why do I call it semi-automation?

Because KOA-session simplifies the operations needed to manage sessions, we still need to manually trigger its corresponding conditions.

In KOA we can do this using ctx.session.

Generate the Session

Not every request generates a session and returns it. A session generates and returns the corresponding session ID only when a session is used to store content while processing the request.

Of course, we only need to manipulate the session and don’t care how to return the sessionID:

// ctx.session.isNew Checks whether the client carries a session
// The code always prints' New '
router.post('/test'.async (ctx, next) => {
      // ctx.session.view = 1; // Uncomment returns notNew
      // await ctx.session.save(); // Uncomment returns notNew

      if (ctx.session.isNew) console.log("New");
      else console.log("notNew")
      ctx.body = new SuccessModel("Test successful"); });Copy the code

Of course, koa-session can also be forcibly stored (even if it is not used) with the ctx.session.save() method whenever a hand is saved.

Delete the Session

This must be released manually, especially for layout requests.

However, the release of koA-session only requires ctx.session = null

router.post('/logout',
  allowCORS, 
  loggedCheck,
  async (ctx, next) => {
    // Delete session and log out
    ctx.session = null;
    ctx.body = new SuccessModel("Exit successful"); })Copy the code

Validation of the Session

In order to generate and delete sessions, we also need to check whether a session exists. As mentioned earlier, we can use ctx.session.isNew to check whether a session exists.

if (ctx.session.isNew) console.log("New");
else console.log("notNew")
Copy the code

This completes the five parts needed to manage login status (three in practice) :

  • Checking session existence
  • Generate session (and automatically generate and return the sessionID)
  • Delete session (and automatically unregister sessionID)

Of course, if external storage is involved, things get more complicated, which I will update as I see opportunity in this article. (But I’m so bad, I should just watch it for myself.)

Configuration of koa – the session

So with all that usage, let’s get down to business.

Installation and Configuration

The installation

npm install --save koa-session
Copy the code

Configuration: it needs to be placed before the router, otherwise how to use ctx.session

// ----- app.js -----
app.keys = ["SECRET_KEY"];
app.use(session(app))

// ----- middlewares/session.js -----

const session = require('koa-session');

module.exports = (app) = > {
  return session(
  {
    key: "koa:sess".maxAge: 24 * 60 * 60 * 1000.httpOnly: true.rolling: true
    // store: redis() or other
  }
  , app);
Copy the code

Explain the various parts:

  • App. keys: used to encrypt cookies, signed signature is true. If there is more than one item in the array, it is used for key rotation.

  • Key: the format of the sessionId in the cookie. The default value is koa.sess

  • MaxAge: indicates the maximum duration of a session. The unit is ms. The default value is one day.

  • AutoCommit: the default value is true. The session and sessionID are automatically submitted to the header and returned to the client. Invalid when manuallyCommit is triggered.

  • Overwrite: the default is true, whether overwriting is allowed.

  • HttpOnly: the default is true to prevent XSS attacks and malicious scripts from hijacking sessions.

  • Signed: the default is true, which automatically adds a sha256 signature to the cookie to prevent tampering and forging cookies.

  • Rolling: The default is false. Refresh the session validity period each time.

  • Renew: default false, renew the validity period when a session expires.

  • Secure: the default is false and transfers only over HTTPS.

  • SameSite: Null by default

In addition, there are stores for setting up external storage.

To deal with cross domain

In particular, additional cross-domain processing is required before a request can be processed

async (ctx, next) => {
  ctx.set("Access-Control-Allow-Origin"."http://127.0.0.1:5500");
  ctx.set("Access-Control-Allow-Methods"."OPTIONS, GET, PUT, POST, DELETE");
  ctx.set("Access-Control-Allow-Credentials"."true");
  ctx.set("Access-Control-Allow-Headers"."x-requested-with, accept, origin, content-type");
  await next();
}
Copy the code

As above, specific will not say ~

A few extra details

About the sessionId

Normally we cannot get the sessionID because the KOA-Session is generated at the end of the request.

So we need to use some special tools:

In order to let us get on schedule, we can use the CTX. Session. ManuallyCommit () manually submit the set – cookies

After this operation, the autoCommit will be cancelled and it will be difficult to maintain this session, but:

External storage

Refer to Jerry’s bosses of koa – simple to use the second | of the session

To set up the external storage object sessionStore, you need to implement three methods:

  • get(key, maxAge, { rolling })
  • set(key, sess, maxAge, { rolling, changed })
  • destroy(key)

This corresponds to the session fetch, set, delete operations, and assign an object instance to the Store option.

Ioredis – based implementation example:

// Generate a key for redis by sid
function getRedisSessionID(sid) {
    return `ssid:${sid}`
}

class RedisSessionStore {
    constructor(store) {
        this.store = store;
    }

    // Save session. TTL is the saving time
    async set(sid, session, maxAge=600000) {
        const id = getRedisSessionID(sid);
        try {
            const sessStr = JSON.stringify(session);  // Two methods related to JSON serialization need to be used (try... catch...) Handle exceptions
            await this.store.setex(id, maxAge, sessStr); //ioredis provides a method for adding key-value pairs
            //await this.store.set(id, sessStr); // Use set() when no expiration time is set
        } catch (err) {
            console.error(err); }}/ / read the session
    async get(sid) {
        const id = getRedisSessionID(sid);
        let value = await this.store.get(id); Ioredis provides a method for reading key-value pairsif(! value) {return null
        }
        try {
            const result = JSON.parse(value);
            return result
        } catch (err) {
            console.error(err); }}/ / delete the session
    async destroy(sid) {
        const id = getRedisSessionID(sid);
        await this.store.del(id);  //ioredis provides a method to delete key-value pairs}}module.exports = RedisSessionStore
Copy the code

code

Front-end core code


// Manage the div that displays login status
const {isLogged, isNotLogged} = controllStatusDiv(document.getElementById('status'))

loginBtn.onclick = (e) = > {
  let formdata = new FormData(form);
  let res = "";
  formdata.forEach((value, key) = > {
    res += `${key}=${value}& `;
  })

  let xhr = new XMLHttpRequest();
  xhr.open("POST"."http://127.0.0.1:3000/user/login".true);
  xhr.setRequestHeader("Content-type"."application/x-www-form-urlencoded");
  xhr.withCredentials = true; // Carry cookies across domains

  xhr.onload = () = > {
    isLogged(); // Change the state and render the corresponding component
  }
  xhr.send(res);
  return false; // Block the default event
}

logoutBtn.onclick = (e) = > {
  let xhr = new XMLHttpRequest();

  xhr.open("POST"."http://127.0.0.1:3000/user/logout".true);
  xhr.withCredentials = true;

  xhr.onload = () = > {
      isNotLogged();
  }
  xhr.send();
  return false;
}
Copy the code

Back-end core code

// ----- router/user.js -----

router.post('/login',
  allowCORS,
  login
);
 
router.post('/logout',
  allowCORS, 
  loggedCheck,
  async (ctx, next) => {
    ctx.session = null;
    ctx.body = new SuccessModel("Exit successful"); })// ----- controller/user.js
const crypto = require('crypto'); // Encryption module
const userModel = require('.. /models/userModel'); // mongoDB data model
const { SuccessModel, ErrorModel } = require('.. /utils/resModel'); // Reply model


async function login (ctx, next) {
  const hash = crypto.createHash("sha256"."MY_SECRET_KEY");
  const {username: name, password: pswd} = ctx.request.body;
  const res = await userModel.findOne({username:name,  password: hash.update(pswd).digest("hex")});
  if (res) {
    console.log(ctx.session.isNew)
    let n = (ctx.session.views || 0);
    ctx.session.views = n++;
    ctx.body = new SuccessModel( "Login successful", ctx.session.view);
    ctx.response.status = 200;
  } else {
    ctx.body = new ErrorModel("Login failed");
    ctx.response.status = 401; }}Copy the code

conclusion

I hope my article can bring you harvest, and your thumbs up is the best support for me.

If you have any questions, you are welcome to discuss them

This is mouse, come on 🚀🚀🚀🚀🚀