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 🚀🚀🚀🚀🚀