What is JWT? This article will take you to understand JWT, introduce the application of JWT in node, and the advantages and disadvantages of JWT. I hope it will be helpful to everyone!
JWT is the abbreviation of JSON Web Token , which is an authentication solution in the network application environment. In the traditional authentication mechanism, it is nothing more than the following steps:
1. The user sends the account password to the server; 2. After the server verifies the account password, it will save some user-related information, user roles or expiration time, etc. in the current session; 3. The server gives the user a session_id and writes it into the user's cookie or the client saves it locally; 4. Every time the user requests a service, he or she needs to bring this session_id, perhaps through cookies or other methods; 5. After the server receives it, it goes back to the database to query the current session_id and verifies whether the user has permission;
An advantage of this model is that the server can terminate the user's permissions at any time, and can go to the database to modify or delete the current user's session information. But there is also a disadvantage, that is, if it is a server cluster, all machines need to share the session information to ensure that each server can obtain the same session storage information. Although these problems can be solved, the amount of work is huge.
The advantage of the JWT solution is that this information is not saved. The token data is saved on the client. Every time a request is accepted, it only needs to be verified.
Let's briefly talk about the principle of JWT. In fact, when the client sends a request for authentication, the server will generate a JSON object after authenticating the user, which probably includes information such as "who are you, what do you do, etc., expiration time " , the important thing is that there must be an expiration time; the general format is:
{ username: "thief string er", role: "World Code Farmer", endTime: "May 20, 2022" }
But it will not be passed to you in such a superficial way. It will be signed and transmitted through a reversible signature algorithm based on the specified signature algorithm and some information about the payload you submitted. I will use a picture to represent the general format:
As can be seen from the picture, the returned information is roughly divided into three parts. The left side is the result after signing, which is the result returned to the client. The right side is also the Decoded source code. The three parts are separated by "dots", respectively. There is a one-to-one correspondence between the three colors red, purple and cyan:
The first red part is the Header. The Header mainly specifies the method. The signature algorithm in the picture ( default HS256 ) is HMAC with SHA-256. It is a symmetric algorithm. Only one key is shared between the two parties, typ The field identifier is of JWT type;
The second purple part, payload, is a JSON object, which is the actual data to be transmitted. There are seven official fields that can be used:
iss (issuer): issuer
exp (expiration time): expiration time
sub (subject):subject
aud (audience): audience
nbf (Not Before): Effective time
iat (Issued At): issuance time
jti (JWT ID): number
In addition to these fields, you can also create some custom fields. Since JWT is not encrypted by default, try to be careful not to use some sensitive data when using it.
The third part is the Signature
signature. This part is a secret key that is specified by you and exists only on the server, and then uses the algorithm specified in the header to sign through the following signature method.
Let’s experience the specific use below:
Step 1: We need to build a nodejs project; initialize a project through npm init -y
; then we need to install dependencies, including express
, jsonwebtoken
and nodemon
:
$ npm i express jsonwebtoken nodemon
Then add the nodemon app.js
command in the scripts
field in package.json
:
"scripts": { "start": "nodemon app.js" },
Step 2: Initialize the node application and create the app.js file in the root directory;
// app.js const express = require("express"); const app = express(); app.use(express.json()); app.listen(3000, () => { console.log(3000 + "listening..."); // Listen to port 3000});
Step 3: Introduce jsonwebtoken
dependency and create the private key of the interface and server;
// app.js //... const jwt = require("jsonwebtoken"); const jwtKey = "~!@#$%^&*()+,"; // ...
jwtKey
here is our custom private key that is only saved in the server. After that, we started to write a /login interface for logging in, and created a local simulation database for verification, and performed it through the jwt.sign
method. Verify signature:
// app.js const database = { username: "username", password: "password", }; app.post("/login", (req, res) => { const { username, password } = req.body; if (username === database.username && password === database.password) { jwt.sign( { username, }, jwtKey, { expiresIn: "30S", }, (_, token) => { res.json({ username, message: "Login successful", token, }); } ); } });
In the above code, we created database
variable to simulate the creation of a local account and password database to verify login; then we established a /login
post
interface. After verifying that the account and password completely match, we imported it through jsonwebtoken
package. Use the sign
method under the jwt
object to sign. This method has three interface signatures:
export function sign( payload: string | Buffer | object, secretOrPrivateKey: Secret, options?: SignOptions, ): string; export function sign( payload: string | Buffer | object, secretOrPrivateKey: Secret, callback: SignCallback, ): void; export function sign( payload: string | Buffer | object, secretOrPrivateKey: Secret, options: SignOptions, callback: SignCallback, ): void;
The method of function overloading is used here to implement the interface. We will implement the last interface signature here. The first parameter can be a custom object type, a Buffer
type, or directly a string
type. Our The source code uses the object
type and customizes some fields, because JWT will also sign these data when signing. However, it is worth noting that you should try not to use sensitive data here because JWT is not encrypted by default. The core is the signature , which ensures that the data has not been tampered with, and the process of checking the signature is called verification .
Of course, you can also encrypt the original Token and then transmit it;
The second parameter: is the secret key we save on the server for signing. Usually in client-server mode, JWS uses the HS256 algorithm provided by JWA plus a key. This method strictly relies on the key. However, in a distributed scenario, multiple services may need to verify JWT. If the key is saved in each service, the security will be greatly reduced. You must know that once the key is leaked, anyone can forge JWT at will. .
The third parameter: is the signature option SignOptions
, the signature of the interface:
export interface SignOptions { algorithm?: Algorithm | undefined; keyid?: string | undefined; expiresIn?: string | number | undefined; /** expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms.js). Eg: 60, "2 days", "10h", "7d" */ notBefore?: string | number | undefined; audience?: string | string[] | undefined; subject?: string | undefined; issuer?: string | undefined; jwtid?: string | undefined; mutatePayload?: boolean | undefined; noTimestamp?: boolean | undefined; header?: JwtHeader | undefined; encoding?: string | undefined; }
Here we use the expiresIn
field to specify the aging time. Please refer to this document for usage methods;
The fourth parameter is a callback. The second parameter of the callback is token
we generated through the signature. Finally, this token
is returned to the front end so that it can be stored locally on the front end and brought to the server for verification on every request.
Next, let's verify this interface: I installed the REST Client plug-in in vscode, and then created a request.http
file in the root directory, and wrote the requested information in the file:
POST http://localhost:3000/login content-type: application/json { "username": "username", "password": "password" }
Then execute the npm run start
command on the command line to start the service, and then click the Send Request
button above the requset.http
file to send the request:
After the request is successful, you will see a response message like this:
The token
field is token
generated by our JWT;
Let's verify whether this token
is valid. We are writing an interface after login:
app.get("/afterlogin", (req, res) => { const { headers } = req; const token = headers["authorization"].split(" ")[1]; //Put the token in the authorization field of the header jwt.verify(token, jwtKey, (err, payload) => { if (err) return res.sendStatus(403); res.json({ message: "Authentication successful", payload }); }); });
In this code, token
previously generated through JWT is obtained by obtaining token
in the authorization
field in the request header. Then verify whether the token
is valid by calling jwt.verify
verification method. This method has three parameters:
// There are four interface signatures, you can check the documentation yourself export function verify( token: string, //Token that needs to be checked secretOrPublicKey: Secret | GetPublicKeyOrSecret, // The signature key defined on the server callback?: VerifyCallback<JwtPayload | string>, // Callback to obtain the verification information result): void;
Next, we copy token
we just responded to into the request header:
### GET http://localhost:3000/afterlogin authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXJuYW1lIiwiaWF0IjoxNjUyNzg5NzA3LCJleHAiOjE2NTI3ODk3Mzd9.s9fk3YLhxTUcpUgCfIK4xQ N58Hk_XEP5y9GM9A8jBbY
The previous Bearer authentication is the standard authentication method in the http protocol.
Also click Send Request
and when you see the response in the picture below, it means the response is successful:
In fact, the above are some simple uses of JWT. Next, let’s talk about the advantages and disadvantages of JWT itself.
The storage space occupied by JWT is actually not small. If we need to sign too much information, the token is likely to exceed the length limit of the cookie. For example, compare these two pictures:
Obviously, as the amount of information in the payload increases, the length of the token will also increase;
Security, in fact, if token
takes up too much space, the maximum storage space Cookie
is only 4kb. The front end can be stored in local storage such as localStorage
, but it will cause a problem. If it is not placed in the cookie, the security will be greatly reduced. There will be a risk of obtaining it through js script, which means that any hacker can do anything with it;
Inflexible timeliness. In fact, a certain aspect of JWT is that the user token
does not need to be stored persistently. Instead, token
is effectively verified using server verification. As you just saw, the signature also includes the expiration time. , if the expiration time is changed, token
will be tampered with. Since there is no way to store and manually change the expiration date, it is difficult to delete the token
immediately. If the user logs in twice and two token
are generated, then in principle, two token
will be generated. are all valid;
The above mainly talks about a few points:
The principle of JWT is mainly to use the server's private key to communicate with token
generated by the JSON signature;
It also introduces the composition of the internal data of JWT, which is used by Header to specify the signature algorithm and type, payload to transmit JSON data, and Signature to perform signature algorithm on the data and prevent tampering;
A detailed introduction is given on how to use JWT through nodejs, perform data signing through the sign
method, and perform signature verification through the verify
method;
Some disadvantages of JWT are also introduced:
One is that the storage space increases as the amount of signature data increases;
Then there is security. If the storage space is too large, it will not be stored in Cookie
with a relatively high security level, causing the script to be obtained at will;
Then there is the timeliness, which cannot flexibly control the timeliness of token
;
This is the demo source code of nodejs above for reference;