A JWT (JSON Web Token) is an open standard for securely transmitting information between parties as a JSON object. It contains claims — data about the user or system — that are digitally signed. This digital signature ensures the token’s content is verifiable and tamper-proof, making JWTs a popular choice for API authentication, single sign-on (SSO), and authorization in modern web applications.
JWTs(JSON Web Tokens) provide a stateless way to authenticate users and authorize access to APIs. Unlike traditional session-based authentication, where the server stores session information, JWTs store all necessary information inside the token itself.
{
“userId”: 123,
“email”: “user@example.com”,
“role”: “admin”,
“iat”: 1696286400,
“exp”: 1696290000
}
iat → issued at timestampexp → expiration timestampAuthorization: Bearer <JWT_TOKEN>JWTs can be signed using symmetric or asymmetric algorithms, and understanding the difference is key to choosing the right approach for application.
| Feature | Symmetric JWT (HS256) | Asymmetric JWT (RS256 / ES256) |
|---|---|---|
| Signing Key | Same secret key is used for both signing and verification | Private key signs the token; public key verifies it. |
| Verification | Requires the same secret key on all services that verify the token | Only the public key is needed for verification; private key stays secure |
| Security Risk | If the secret is leaked, anyone can both sign and verify tokens | Public key can be shared safely; private key stays secure, reducing risk |
| Use Case | Simple apps or single-server setups | Microservices, distributed systems, or third-party integrations |
| Key Management | Easier but less secure | Slightly more complex but more secure and scalable |
You’ll need a private key to sign tokens and a public key to verify them. Use OpenSSL to generate them:
// Generate a 2048-bit RSA private key
openssl genrsa -out private.key 2048
// Generate the corresponding public key
openssl rsa -in private.key -pubout -out public.key
Tips:
Use the private key to sign tokens. Here’s a Node.js example using jsonwebtoken
const jwt = require('jsonwebtoken');
const fs = require('fs');
// Read the private key
const privateKey = fs.readFileSync('./private.key');
// Define the payload (claims)
const payload = {
userId: 123,
email: 'user@example.com',
role: 'admin'
};
// Sign the JWT with RS256 algorithm
const token = jwt.sign(payload, privateKey, { algorithm: 'RS256', expiresIn: '1h' });
console.log('Generated JWT:', token)
Explanation:
Now that tokens are being signed with your private key, you need a way for your API to verify incoming tokens using the public key.
Instead of manually writing verification logic for every route, we’ll use the express-jwt middleware along with jwks-rsa to keep things simple and secure.
const express = require("express");
const { expressjwt } = require("express-jwt");
const jwksClient = require("jwks-rsa");
const morgan = require("morgan");
const app = express();
app.use(express.json());
app.use(morgan("dev"));
// Middleware to verify JWT using the public key from JWKS
app.use(
expressjwt({
secret: jwksClient.expressJwtSecret({
jwksUri: "http://localhost:4000/jwks.json", // JWKS endpoint serving your public key
cache: true, // Cache keys to improve performance
rateLimit: true // Prevents excessive requests to the JWKS endpoint
}),
algorithms: ["RS256"], // Must match the algorithm used to sign the token
}).unless({ path: ["/"] }) // Make the root route public
);
app.get("/", (req, res) => {
res.send({ message: "This is a public route" });
});
app.get("/protected", (req, res) => {
res.send({ message: "You have accessed a protected route!" });
});
app.listen(3000, () => console.log("API running on port 3000"));
🔑 How it works:
jwks-rsa fetches the public key from your JWKS endpoint (/jwks.json).Express-jwt automatically verifies every incoming request’s Authorization header:Authorization : Bearer <JWT_TOKEN>Once your API is set up with the express-jwt middleware (from Step 3), clients can include the JWT in each request to access protected routes.
Client-side request example:
Get /protected HTTP/1.1
Host: localhost:3000
Authorization: Bearer <JWT_TOKEN>
Authorization header asWhile symmetric JWTs are simpler to implement, asymmetric JWTs offer significant advantages, especially in distributed systems or applications with multiple services.
💻 Demo Code: GitHub Repository