In the previous article , we learned about the basic knowledge and working mechanism of JWT. In this article, we will continue to learn together about the part that I think is the best when it comes to JWT, which is applying Symmetric Encryption and Asymmetric Encryption in JWT authentication.
I will use NodeJS and MongoDB to implement the encryption for JWT. As for the idea, how to apply, you can use any programming language.
JWT Authentication with Symmetric Encryption and Asymmetric Encryption
1. JWT Authentication with Symmetric Encryption
Symmetric encryption is a method of encrypting data that uses the same key to both encrypt and decrypt information.
This method is probably no stranger to you when you first come into contact with JWT.
The basic implementation is as follows:
For this implementation, we create a JWT with a payload containing information about the userid and email.
- We use the jwt method. sign () to encrypt this information with the key secretKey and the lifetime of the token is 1 hour.
- Then we use the jwt. verify () to authenticate the JWT with the key secretKey and get the information.
Please refer to the GIT source code here.
B1: Import the library and initialize secretKey
1 2 3 4 5 | <span class="token comment">// jwt.aes.js</span> <span class="token comment">// Import thư viện và khởi tạo secretKey</span> <span class="token keyword">const</span> jwt <span class="token operator">=</span> <span class="token function">require</span> <span class="token punctuation">(</span> <span class="token string">'jsonwebtoken'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> secretKey <span class="token operator">=</span> <span class="token string">'mysupersecretkey'</span> <span class="token punctuation">;</span> |
B2: Create JWT . constructor
1 2 3 4 5 6 | <span class="token comment">// Hàm tạo JWT</span> <span class="token keyword">const</span> <span class="token function-variable function">genToken</span> <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token parameter">userInfo</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> accessToken <span class="token operator">=</span> jwt <span class="token punctuation">.</span> <span class="token function">sign</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> userid <span class="token operator">:</span> userInfo <span class="token punctuation">.</span> _id <span class="token punctuation">,</span> email <span class="token operator">:</span> userInfo <span class="token punctuation">.</span> email <span class="token punctuation">}</span> <span class="token punctuation">,</span> secretKey <span class="token punctuation">,</span> <span class="token punctuation">{</span> expiresIn <span class="token operator">:</span> <span class="token string">'1h'</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> accessToken <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
B3: Create JWT . authentication function
1 2 3 4 5 6 7 8 9 10 11 | <span class="token comment">// Hàm xác thực JWT</span> <span class="token keyword">const</span> <span class="token function-variable function">validateToken</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> <span class="token parameter">accessToken</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> jwt <span class="token punctuation">.</span> <span class="token function">verify</span> <span class="token punctuation">(</span> accessToken <span class="token punctuation">,</span> secretKey <span class="token punctuation">,</span> <span class="token punctuation">(</span> <span class="token parameter">err <span class="token punctuation">,</span> decode</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> err <span class="token punctuation">)</span> <span class="token punctuation">{</span> console <span class="token punctuation">.</span> <span class="token function">error</span> <span class="token punctuation">(</span> <span class="token string">'error verify token'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> console <span class="token punctuation">.</span> <span class="token function">log</span> <span class="token punctuation">(</span> <span class="token string">'decode jwt::'</span> <span class="token punctuation">,</span> decode <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
B4: Check the results
1 2 3 4 5 6 7 8 | <span class="token comment">// Thực thi</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">runScript</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> userId <span class="token operator">=</span> <span class="token number">1</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> accessToken <span class="token operator">=</span> <span class="token function">genToken</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> _id <span class="token operator">:</span> userId <span class="token punctuation">,</span> email <span class="token operator">:</span> <span class="token string">'pdthien@gmail.com'</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">validateToken</span> <span class="token punctuation">(</span> accessToken <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">runScript</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
- Advantages :
- Easy to do because it uses a single key to both encrypt and decrypt information.
- Reduces pressure on the server side as there is no need to save any additional data for authentication.
- Cons : Not safe because when hackers steal that key, it will be easy to create fake JWTs, which can access and attack the application.
2. JWT Authentication with Asymmetric Encryption
Asymmetric encryption uses two different keys ( public key and private key ) to encrypt and decrypt information. In it, the public key is shared with everyone and the private key is kept secret.
- The private key is used to encrypt (sign) the information that generates the JWT.
- The public key is used to decrypt (verify) that JWT, not the other way around. So even if the hacker gets the Public key, he can’t create a fake JWT to access the application.
The implementation method is as follows:
For this implementation, we create a JWT with a payload containing information about the userid and email
- We use MongoDB to create a KeyToken table that stores the userId and publicKey.
- Generate 2 keys privateKey and publicKey using rsa algorithm through crypto library.
- We use the jwt method. sign () to encrypt this information with the privateKey and the token’s lifetime is 1 hour.
- We use the jwt method. verify () to authenticate the JWT with the publicKey and get the information.
Please refer to the GIT source code here.
B1: Initialize MongoDB connection
1 2 3 4 5 6 7 8 9 10 | <span class="token comment">// init.mongodb.js</span> <span class="token keyword">const</span> mongoose <span class="token operator">=</span> <span class="token function">require</span> <span class="token punctuation">(</span> <span class="token string">"mongoose"</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> connnectString <span class="token operator">=</span> <span class="token string">'mongodb://127.0.0.1:27017/jwt-auth'</span> <span class="token punctuation">;</span> mongoose <span class="token punctuation">.</span> <span class="token function">connect</span> <span class="token punctuation">(</span> connnectString <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">then</span> <span class="token punctuation">(</span> <span class="token parameter">_</span> <span class="token operator">=></span> console <span class="token punctuation">.</span> <span class="token function">log</span> <span class="token punctuation">(</span> <span class="token string">'mongoDB connected'</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">catch</span> <span class="token punctuation">(</span> <span class="token parameter">err</span> <span class="token operator">=></span> console <span class="token punctuation">.</span> <span class="token function">log</span> <span class="token punctuation">(</span> <span class="token string">'mongDB error '</span> <span class="token operator">+</span> err <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> module <span class="token punctuation">.</span> exports <span class="token operator">=</span> mongoose <span class="token punctuation">;</span> |
B2: Create a table (collection) Key to store Public key
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <span class="token comment">// keytoken.model.js</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> Schema <span class="token punctuation">,</span> model <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span> <span class="token punctuation">(</span> <span class="token string">'mongoose'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token constant">DOCUMENT_NAME</span> <span class="token operator">=</span> <span class="token string">'Key'</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token constant">COLLECTION_NAME</span> <span class="token operator">=</span> <span class="token string">'Keys'</span> <span class="token punctuation">;</span> <span class="token keyword">var</span> keyTokenSchema <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Schema</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> userid <span class="token operator">:</span> <span class="token punctuation">{</span> type <span class="token operator">:</span> Number <span class="token punctuation">,</span> required <span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> publicKey <span class="token operator">:</span> <span class="token punctuation">{</span> type <span class="token operator">:</span> String <span class="token punctuation">,</span> require <span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token punctuation">{</span> timestamps <span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">,</span> collection <span class="token operator">:</span> <span class="token constant">COLLECTION_NAME</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> module <span class="token punctuation">.</span> exports <span class="token operator">=</span> <span class="token function">model</span> <span class="token punctuation">(</span> <span class="token constant">DOCUMENT_NAME</span> <span class="token punctuation">,</span> keyTokenSchema <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
B3: Create function genToken. In the function handle 2 things: Save public key to DB + Use privateKey to encrypt to create JWT
1 2 3 4 5 6 7 | <span class="token comment">// jwt.rsa.js</span> <span class="token comment">// Import thư viện và các biến</span> <span class="token keyword">const</span> jwt <span class="token operator">=</span> <span class="token function">require</span> <span class="token punctuation">(</span> <span class="token string">'jsonwebtoken'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> crypto <span class="token operator">=</span> <span class="token function">require</span> <span class="token punctuation">(</span> <span class="token string">'crypto'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token function">require</span> <span class="token punctuation">(</span> <span class="token string">'./init.mongodb'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> keytokenModel <span class="token operator">=</span> <span class="token function">require</span> <span class="token punctuation">(</span> <span class="token string">"./keytoken.model.js"</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
1 2 3 4 5 6 7 8 9 | <span class="token comment">// Hàm xử lý lưu public key vào DB</span> <span class="token keyword">const</span> <span class="token function-variable function">createKeyToken</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> <span class="token parameter"><span class="token punctuation">{</span> userid <span class="token punctuation">,</span> publicKey <span class="token punctuation">}</span></span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> publicKeyString <span class="token operator">=</span> publicKey <span class="token punctuation">.</span> <span class="token function">toString</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> filter <span class="token operator">=</span> <span class="token punctuation">{</span> userid <span class="token operator">:</span> userid <span class="token punctuation">}</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> update <span class="token operator">=</span> <span class="token punctuation">{</span> userid <span class="token operator">:</span> userid <span class="token punctuation">,</span> publicKey <span class="token operator">:</span> publicKeyString <span class="token punctuation">}</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> options <span class="token operator">=</span> <span class="token punctuation">{</span> upsert <span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> <span class="token keyword">await</span> keytokenModel <span class="token punctuation">.</span> <span class="token function">findOneAndUpdate</span> <span class="token punctuation">(</span> filter <span class="token punctuation">,</span> update <span class="token punctuation">,</span> options <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
1 2 3 4 5 6 7 8 9 | <span class="token comment">// Hàm tạo JWT từ private key</span> <span class="token keyword">const</span> <span class="token function-variable function">createAccessToken</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> <span class="token parameter">payload <span class="token punctuation">,</span> privateKey</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> accessToken <span class="token operator">=</span> <span class="token keyword">await</span> jwt <span class="token punctuation">.</span> <span class="token function">sign</span> <span class="token punctuation">(</span> payload <span class="token punctuation">,</span> privateKey <span class="token punctuation">,</span> <span class="token punctuation">{</span> algorithm <span class="token operator">:</span> <span class="token string">'RS256'</span> <span class="token punctuation">,</span> expiresIn <span class="token operator">:</span> <span class="token string">'1h'</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> accessToken <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token comment">// Hàm genToken. Xử lý 2 việc: Lưu public key vào DB + Dùng private key để mã hóa tạo JWT.</span> <span class="token keyword">const</span> <span class="token function-variable function">genToken</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> <span class="token parameter">userInfo</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Dùng rsa để tạo privateKey và publicKey</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> privateKey <span class="token punctuation">,</span> publicKey <span class="token punctuation">}</span> <span class="token operator">=</span> crypto <span class="token punctuation">.</span> <span class="token function">generateKeyPairSync</span> <span class="token punctuation">(</span> <span class="token string">'rsa'</span> <span class="token punctuation">,</span> <span class="token punctuation">{</span> modulusLength <span class="token operator">:</span> <span class="token number">4096</span> <span class="token punctuation">,</span> publicKeyEncoding <span class="token operator">:</span> <span class="token punctuation">{</span> type <span class="token operator">:</span> <span class="token string">'pkcs1'</span> <span class="token punctuation">,</span> format <span class="token operator">:</span> <span class="token string">'pem'</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> privateKeyEncoding <span class="token operator">:</span> <span class="token punctuation">{</span> type <span class="token operator">:</span> <span class="token string">'pkcs1'</span> <span class="token punctuation">,</span> format <span class="token operator">:</span> <span class="token string">'pem'</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token comment">// Lưu userid và publicKey vào bảng KeyToken</span> <span class="token keyword">await</span> <span class="token function">createKeyToken</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> userid <span class="token operator">:</span> userInfo <span class="token punctuation">.</span> _id <span class="token punctuation">,</span> publicKey <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token comment">// Tạo accessToken với privateKey</span> <span class="token keyword">const</span> accessToken <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">createAccessToken</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> userid <span class="token operator">:</span> userInfo <span class="token punctuation">.</span> _id <span class="token punctuation">,</span> email <span class="token operator">:</span> userInfo <span class="token punctuation">.</span> email <span class="token punctuation">}</span> <span class="token punctuation">,</span> privateKey <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> accessToken <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
B4: Create validateToken function to handle validation JWT: Get publicKey from DB + Verify token from publicKey.
1 2 3 4 5 6 7 | <span class="token comment">// Hàm lấy publicKey</span> <span class="token keyword">const</span> <span class="token function-variable function">getPublicKey</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> <span class="token parameter">userid</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> filter <span class="token operator">=</span> <span class="token punctuation">{</span> userid <span class="token operator">:</span> userid <span class="token punctuation">}</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> token <span class="token operator">=</span> <span class="token keyword">await</span> keytokenModel <span class="token punctuation">.</span> <span class="token function">findOne</span> <span class="token punctuation">(</span> filter <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> token <span class="token punctuation">.</span> publicKey <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token comment">// Hàm validateToken. Xử lý 2 việc: Lấy publicKey từ DB + Xác thực (verify) token từ publicKey</span> <span class="token keyword">const</span> <span class="token function-variable function">validateToken</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> <span class="token parameter">accessToken <span class="token punctuation">,</span> userID</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Lấy publicKey trong DB</span> <span class="token keyword">const</span> publicKeyString <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getPublicKey</span> <span class="token punctuation">(</span> userID <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token comment">// Convert publicKey từ dạng string về dạng rsa có thể đọc được</span> <span class="token keyword">const</span> publicKeyObject <span class="token operator">=</span> crypto <span class="token punctuation">.</span> <span class="token function">createPublicKey</span> <span class="token punctuation">(</span> publicKeyString <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token comment">// xác thực accessToken sử dụng publicKey</span> jwt <span class="token punctuation">.</span> <span class="token function">verify</span> <span class="token punctuation">(</span> accessToken <span class="token punctuation">,</span> publicKeyObject <span class="token punctuation">,</span> <span class="token punctuation">(</span> <span class="token parameter">err <span class="token punctuation">,</span> decode</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> err <span class="token punctuation">)</span> <span class="token punctuation">{</span> console <span class="token punctuation">.</span> <span class="token function">error</span> <span class="token punctuation">(</span> <span class="token string">'error verify token'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> console <span class="token punctuation">.</span> <span class="token function">log</span> <span class="token punctuation">(</span> <span class="token string">'decode jwt::'</span> <span class="token punctuation">,</span> decode <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
B5: Check the results
1 2 3 4 5 6 7 | <span class="token comment">// Thực thi</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">runScript</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> userId <span class="token operator">=</span> <span class="token number">1</span> <span class="token punctuation">;</span> <span class="token keyword">let</span> accessToken <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">genToken</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> _id <span class="token operator">:</span> userId <span class="token punctuation">,</span> email <span class="token operator">:</span> <span class="token string">'pdthien@gmail.com'</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">validateToken</span> <span class="token punctuation">(</span> accessToken <span class="token punctuation">,</span> userId <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> |
- Cons : Having to save more Public key to the DB and access to the DB to get the Public key when needing to authenticate, leading to performance impact. However, it is possible to improve performance by using a Cache like MemoryCache or RedisCache.
- Advantages :
- Although it increases the pressure on the server when it has to save more Public keys to the DB and retrieve it when it needs to be authenticated, it helps our system increase security a lot.
(Because as I said above, even if the hacker can read the Public key information from the DB, it is not possible to create a fake JWT to access the application because the Private key is only used to encrypt (sign) the information to create the JWT, Public key is only used to decrypt JWT to get information, not vice versa . - Solve some disadvantages of JWT as I have shown you in part 1:
- Regarding the issue of security risks, when applying the asymmetric encryption method, we can be completely assured because the security has been much improved (I explained above).
- As for the issue of Can’t cancel token and No session management support, then when applying this method, since we have stored publicKey in DB, it’s better to simply cancel token or cancel user’s login session simple, we just need to delete that publicKey from the DB.
- Although it increases the pressure on the server when it has to save more Public keys to the DB and retrieve it when it needs to be authenticated, it helps our system increase security a lot.
Please refer to the GIT source code here.
Conclude
To summarize a bit, when you apply symmetric encryption to JWT, it will be easier to do and the server does not need to save any more information for authentication, thereby increasing performance, but reducing security. because hackers only need to get the sercetKey to be able to fake JWT to access the system. When applying asymmetric encryption, we need to store more publicKeys in the DB, which can reduce performance a bit, but in return it helps us to increase security a lot.
If you have any suggestions or discussions, please comment below the article. Tks all!!!