This article is participating in node.js advanced technology essay, click to see details.

As we know, HTTP is stateless, meaning that there is no correlation between the last request and the next. But we want to realize the function of the application, a lot of time is to have a state, for example, after logging in, and then add a shopping cart, it should identify the login user to do.

How do YOU add state to an HTTP request?

There are two solutions to this problem: session + cookie stored by the server and token stored by the client.

But neither of these solutions is good, neither is perfect.

Why do you say that? Let’s take a look at each:

Session + cookie stored on the server

To add status to HTTP, mark each request and store the data corresponding to that mark on the server. In this way, each marked request can find the corresponding data, naturally can achieve login, permission and other state storage.

This tag is supposed to be automatically tagged, so HTTP designs cookies to store data that is tagged with each request.

The corresponding data of the server to be searched according to the mark in the cookie is called session, and this mark is the ID of session.

As shown in the figure, since cookies are automatically attached to requests, the session corresponding to ID 1 can be found for both requests, and the user who is currently logged in can be naturally known, as well as other state data can be stored.

This is the session + cookie scheme for adding state to HTTP.

Do you think this is a problem?

There are problems, and there are a lot of them.

The biggest one is the notorious CSRF (Cross-site request forgery) :

CSRF

Because cookies are automatically taken with you when you request them, so if you log in to one site, and then you go to another site, if there’s a button inside that asks for the previous site, the cookie will still be taken with you. And you don’t have to log in anymore.

So what if I clicked the button and did something dangerous?

Is it dangerous?

And the general use of CSRF vulnerability of the site will be very good camouflage, so that you can see it is difficult to flaw, this site is called phishing site.

To solve this problem, we usually verify the referer, which site the request is from, and if it’s from the wrong site, block it.

But that still doesn’t solve the problem completely. What if you use a browser that has a problem and can fake the referer?

Therefore, random values are generally used to solve the problem. Each time a random value is generated and returned, subsequent requests must contain this value, otherwise it is considered illegal.

This random value is called token, and can be placed in the parameter or header, because the phishing site can not get this random value, even if the cookie does not pass the authentication of the server.

This is a drawback of the Session + cookie approach, but there is a solution.

It also has other disadvantages, such as when distributed:

Distributed session

Session stores state data on the server, so the question is, what if there are multiple servers?

When the amount of concurrency up, a single server simply cannot bear, the natural need to do cluster, also need more than one server to provide services.

And now the back end will split different functions into different services, called microservices architecture, which naturally requires multiple servers.

How do you synchronize sessions between different servers?

After login, the session is saved on a certain server, and then the user may access another server. At this time, the server does not have the corresponding session, so the corresponding functions cannot be completed.

There are two solutions to this problem:

One is session replication, that is, a mechanism that automatically replicates sessions on all machines and synchronizes each change. There are frameworks to do this, such as Java’s Spring-Session.

Session replication is performed on all servers, so you can visit any server and find the corresponding session.

Another option is to store sessions in Redis, so that every server goes to redis, and once one server logs in, the other servers can also find sessions, so there is no need to copy.

Fortunately, there is a solution to this problem when sessions are distributed.

But you you think that’s it? Session + cookie also has cross-domain issues:

Cross domain

Cookie For security, domain restrictions are made. When setting cookies, a domain is specified, and only requests from this domain will carry the cookie.

You can also set expiration time, path, and so on:

What if it’s a request from a different domain? So how do you bring cookies when you cross domains?

A.guang.com and B.guang.com are ok, as long as the domain is set to the top-level domain name guang.com, the second and third domain name can also be automatically added.

But if the top-level domain name is different, there is no way, this can only be done in the server, the two domain name unified into the same.

This is not an Ajax request. Ajax requests have additional mechanisms:

Ajax requests do not take cookies with them when they cross domains, unless the withCredentials are set to true manually.

It also requires the backend code to set the corresponding header:

Access-control-allow-origin: "current domain name "; Access-Control-Allow-Credentials: trueCopy the code

The allow Origin setting * does not work. You must specify a specific domain name to accept cross-domain cookies.

This is the third pitfall of the session + cookie approach, but fortunately there is a solution.

Let’s make a summary:

Session + cookie Adding state to HTTP is that the server saves the session data and then returns the ID in the cookie, which is automatically carried. Each request can find the corresponding session through the ID in the cookie, thus realizing the identification of the request. This solution can fulfill the requirements, but there are CSRF, distributed session, cross-domain problems, but there are solutions.

The session + cookie scheme is not perfect, so let’s look at another way:

Token stored on the client

Session + cookie scheme saves the state data in the server, and then saves the ID in the cookie to achieve. Since there are so many problems with this solution, I’m going to do the opposite and instead of saving the state on the server, I’m going to put it all in the request, and instead of putting it in the cookie, I’m going to put it in the header. Is that going to solve all the problems?

The token scheme is often stored in json format, called JSON Web Token, or JWT. Let’s take this for example.

JWT is a string stored in the Request header (for example, the header name can be called authorization), which is divided into three parts:

JWT is composed of header, payload, and Verify signature.

The header part stores the current encryption algorithm. The payload part is the stored data. The Verify Signature part is generated after encrypting the header, payload, and salt. (Salt, salt, is an arbitrary string, added randomness)

These three parts will be Base64 respectively, and then connected together to form the header of JWT, and put into a header such as authorization:

authorization: barer xxxxx.xxxxx.xxxx
Copy the code

The server can parse the header, payload, and verify signature, and then encrypt the header, payload, and salt according to the algorithm in the header. If the result is the same as Verify Signature, accept the token.

It implements stateful HTTP by storing state data in the payload section:

This method does not have session + cookie problems.

CSRF: Because the automatic cookie is not used to associate the state saved by the session on the server, there is no CSRF problem and cookie attack is not possible.

Distributed session: Since the state is not stored on the server, it doesn’t matter which server you visit, as long as the state data can be resolved from the token.

Cross-domain: Since it is not a cookie, there is no cross-domain restriction, just manually bring the JWT header.

It seems like the perfect way, right?

No, JWT has JWT problems:

security

Because JWT puts the data directly into the header after Base64, it is easy for others to retrieve status data, such as sensitive information such as user names, from it, and to falsify requests based on this JWT.

So JWT should be used with HTTPS so that no one else can get the header.

performance

JWT stores all the state data in the header, which will be carried with each request. Compared with cookies that only store an ID, the content of the request will be larger and the performance will be worse.

So don’t save too much data in JWT either.

There’s no way to disable JWT

Session is stored on the server, so we can invalidate it at any time, but JWT is not, because it is stored on the client, so we cannot invalidate it manually.

For example, kicking people, logging out of the login, changing the password after logging out of this function can not be implemented.

However, it can also cooperate with Redis to solve the problem. Record the effective status of each token and check whether the JWT is available each time. In this way, the JWT can be invalidated.

Therefore, although JWT solution solves many problems of session + cookie, it is not perfect.

Summary:

The scheme of JWT is to save the status data in the header, which needs to be carried manually for each request. There are no CSRF, distributed and cross-domain problems of session + cookie scheme, but there are also security, performance and uncontrollable problems.

Having said all that, I feel more comfortable writing code:

Nest.js implements two schemes

Let’s use Nest.js to implement the next two solutions, not just on paper.

Start by quickly creating a Nest.js project with @nest/cli

npx nest new status
Copy the code

Generates basic code for module, Controller, and Service:

Let’s implement session + cookie first:

session + cookie

The underlying layer of Nest.js is Express, which only provides some additional architectural division, so it is still the same as the Express scheme for session implementation:

Install Express-Session and its TS type definition:

npm install express-session @types/express-session
Copy the code

Then enable it in the entry module:

Just specify a password to encrypt the cookie.

The session object can then be injected into the Controller:

I put a count variable in the session, increment by one per access, and then the body returns that count.

This allows you to determine whether the HTTP request has a state.

Let’s test it out:

As you can see, each request returns different data and a cookie is connect.sid, which is the id of the corresponding session.

Because cookies are automatically attached to requests, you can implement request identification and add state to HTTP requests.

Session + cookie is easy to use. Let’s look at JWT:

jwt

JWT needs to import the @nestjs/ JWT package and then import the JwtModule in the entry Module:

When introduced, specify the password, which is used to add salt to the JWT, as well as the token expiration time.

Since we introduced JwtModule, dependency injection can be implemented in the Controller:

Declare a dependency on JwtService, and Nest.js will automatically inject the corresponding object

Then define a Controller method that sets the authorization header through the Resonse object:

JwtService generates a token, records count, returns it in the header, and also in the body.

The following request is to retrieve the header, retrieve the data in it, and then +1 to put it back:

This also implements the need to add state to HTTP, but saves the data in the header.

Let’s take the Postman test:

The first request returns an authorization header with the body of 1:

Add this header manually to the request header and ask again:

The body becomes 2, and a new authorization header is returned.

Put the new authorization in the request header and ask again:

The body becomes 3, and a new authorization header is returned.

What if I use the old header instead of the new one?

That will report an error:

The JWT generation can only be used once, and this one time feature is also one of its characteristics.

In this way, we use Nex.js to implement session + cookie and JWT respectively to save HTTP state.

The code is uploaded to github: github.com/QuarkGluonP…

conclusion

HTTP is stateless, meaning there is no relationship between requests, but many of our implementations require state preservation.

There are two ways to add state to HTTP:

Session + cookie: The status data is saved to the server, and the session ID is returned in the cookie. In this way, the cookie is added to each request, and the corresponding session can be found based on the ID. There are CSRF, distributed session, and cross-domain issues.

JWT: To store the state in a JSON-formatted token and put it into the header, you need to manually add it. There are no problems with cookie + session, but there are also security, performance, uncontrollability, and failure to use it once.

Neither of these solutions is perfect, but there are solutions to those problems.

This is often the case in the software world, where a solution solves some problems but also introduces new ones. There is no silver bullet, or to be familiar with their characteristics, according to different needs flexible selection.