What is JWT authentication?
JSON Web Tokens (JWT) provide a secure and efficient way to transmit information between parties as a JSON object. They are widely used for authentication and authorization in modern web applications, especially in a stateless API context like with Node.js backend services.
What is JWT?
JWT is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with HMAC algorithm) or a public/private key pair (using RSA or ECDSA).
How JWT Authentication Works
- User Login: The user sends their credentials (username/password) to the server.
- Token Issuance: The server verifies the credentials. If valid, it generates a JWT containing user information (payload) and signs it with a secret key. This JWT is then sent back to the client.
- Client Storage: The client typically stores the JWT in browser storage (e.g., localStorage, sessionStorage, or a cookie).
- Subsequent Requests: For every subsequent request to protected routes, the client includes the JWT, usually in the
Authorizationheader as a Bearer token. - Token Verification: The server receives the JWT, verifies its signature using the same secret key. If valid, it trusts the payload and grants access to the requested resource. It also checks for token expiration.
Components of a JWT
A JWT is a string that consists of three parts, separated by dots (.): Header, Payload, and Signature. Example: header.payload.signature
- Header: Typically consists of two parts: the type of the token (JWT) and the signing algorithm being used (e.g., HMAC SHA256 or RSA). It's Base64Url encoded.
- Payload (Claims): Contains the actual data (claims) about the entity (typically the user) and additional data. Claims can be registered (e.g.,
iss,exp,sub), public, or private. It's Base64Url encoded. - Signature: Created by taking the encoded header, the encoded payload, a secret, and the algorithm specified in the header. This signature is used to verify that the sender of the JWT is who it says it is and to ensure that the message hasn't been altered along the way.
Advantages and Disadvantages
Advantages
- Stateless: The server doesn't need to store session information, making it scalable for distributed systems.
- Compact: Due to its smaller size, JWTs can be sent through URL, POST parameter, or inside an HTTP header. The data in a JWT is self-contained.
- Self-contained: The payload contains all the necessary user information, reducing the need for multiple database queries.
- Mobile Friendly: Well-suited for mobile applications where traditional sessions might be problematic.
Disadvantages
- Token Invalidation: Revoking a JWT before its expiration can be complex, often requiring a blacklist mechanism on the server side.
- Security Risks: If a JWT is compromised, it remains valid until expiration. Storing JWTs in
localStoragecan make them vulnerable to XSS attacks. - Payload Size: Storing too much data in the payload can increase the token size, affecting performance.
- No built-in encryption: JWTs are encoded, not encrypted by default. Sensitive data should not be stored in the payload.
JWT in Node.js
In Node.js, the jsonwebtoken library is the most popular choice for working with JWTs. It provides simple methods for signing, verifying, and decoding tokens. Middleware like express-jwt can be used to protect routes in Express applications.
const jwt = require('jsonwebtoken');
const secret = process.env.JWT_SECRET || 'supersecretkey';
// --- On User Login (Server Side) ---
const user = { id: 'user123', email: 'user@example.com' };
const token = jwt.sign(user, secret, { expiresIn: '1h' });
console.log('Generated Token:', token);
// Send this token to the client (e.g., in a response body or HttpOnly cookie)
// --- On Subsequent Request (Server Side - Middleware) ---
const verifyToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
if (!authHeader) return res.sendStatus(401); // No token provided
const token = authHeader.split(' ')[1]; // Expects 'Bearer TOKEN'
jwt.verify(token, secret, (err, user) => {
if (err) return res.sendStatus(403); // Invalid token (e.g., expired, malformed)
req.user = user; // Attach user payload to request
next();
});
};
// Example usage in an Express route:
// app.get('/protected', verifyToken, (req, res) => {
// res.json({ message: 'Accessed protected data', user: req.user });
// });