What
JWT - JSON web tokens are JSON Objects that are used to securly transmit information between 2 parties. Commonly used for authentication and information exchange.
JWTs are commonly used to authenticate users in web applications. When a user logs in, the server generates a JWT and sends it to the client. The client stores the token (typically in localStorage or a cookie) and includes it in the Authorization header for subsequent requests. The server verifies the token to authenticate the user.
Example:
JWT tokens are secure because they can be signed with a secret key. While the token itself can be decoded by anyone, the signature ensures that the data has not been tampered.
How
To use this we need to install jsonwebtoken
package.
It allow us to create a JWT token with a time validity & also verify them.
npm install jsonwebtoken
const jwt = require("jsonwebtoken");
const SECRET_KEY = "your_secret_key";
// creating and signing the token
const token = jwt.sign({ id: user.id }, SECRET_KEY, { expiresIn: "1h" });
// verify the token , It accepts a callback.
jwt.verify(token, SECRET_KEY, (err, decoded) => {
if (err) return res.status(401).send("Invalid token");
res.send("Protected resource accessed");
});
Implementation
import express from "express";
import { z } from "zod";
import jwt, { decode } from "jsonwebtoken";
import { PrismaClient } from "@prisma/client";
const app = express();
app.use(express.json());
const JWT_SECRET = "mysecretpassword";
const prisma = new PrismaClient();
const userSchema = z.object({
email: z.string().email(),
username: z
.string()
.min(1, "Username is required")
.max(40, "Username is too long"),
password: z
.string()
.min(8, "Password is too short")
.max(40, "Password is too long"),
});
const userLoginSchema = z.object({
email: z.string().email(),
password: z
.string()
.min(8, "Password is too short")
.max(40, "Password is too long"),
});
app.get("/", (req, res) => {
res.send("Hello World");
});
app.get("/api/v1/signup", async (req, res) => {
const validatedData = userSchema.safeParse(req.body);
if (!validatedData.success) {
return res.status(400).json(validatedData.error);
}
const { email, username, password } = validatedData.data;
try {
const newUser = await prisma.user.create({
data: {
email,
username,
password,
},
});
const token = jwt.sign({ userId: newUser.id }, JWT_SECRET);
return res.json({ jwtToken: token });
} catch (error) {
console.error(error);
}
});
app.post("/api/v1/signin", async (req, res) => {
const validatedData = userLoginSchema.safeParse(req.body);
if (!validatedData.success) {
return res.status(404).json(validatedData.error);
}
const { email, password } = validatedData.data;
try {
const user = await prisma.user.findUnique({
where: {
email,
password,
},
});
if (!user) {
throw new Error("User Not Found");
}
const token = jwt.sign({ userId: user.id }, JWT_SECRET);
return res.json({ jwtToken: token });
} catch (error) {
return res.status(404).json({ message: "User not found" });
}
});
app.get("/api/v1/protectedRoute", (req, res) => {
const header = req.headers.authorization;
const token = header?.split(" ")[1];
if (!token) {
return res.status(401).json({ message: "Unauthorized" });
}
jwt.verify(token, JWT_SECRET, (err, decoded) => {
if (err) {
return res.status(401).json({ message: "Unauthorized" });
}
return res.json({ message: "Welcome to protected route" });
});
});
app.listen(3000, () => {
console.log("Listening on PORT 3000");
});
Frontend
const data = await fetch("https://localhost:3000/api/v1/signup",{
method:"POST",
headers:{
'Content-Type': 'application/json',
}
body:JSON.stringify({
username:name,
email,
password
})
})
const res = await data.json();
if(res.token){
localStorage.token = res.token;
}
Disadvantages of Using This Strategy
-
Security Risks with LocalStorage: Storing sensitive information, such as JWT tokens, in
LocalStorage
is not recommended. This is becauseLocalStorage
is accessible to any script running on the same origin, making it vulnerable to cross-site scripting (XSS) attacks. Additionally,LocalStorage
data is accessible from any website you visit, posing further security risks. Instead, consider using:- Cookies: Store JWT tokens in HTTP-only cookies to enhance security. HTTP-only cookies are not accessible via JavaScript, reducing the risk of XSS attacks.
- Sessions: Use server-side sessions to store user authentication data. The session ID can be stored in a cookie, while sensitive information remains on the server.
-
Token Invalidity: JWTs are difficult to invalidate if they are compromised. To mitigate this issue:
- Short-Lived Tokens: Use short expiration times for JWTs to limit their validity period.
- Refresh Tokens: Implement a refresh token mechanism to issue new JWTs. This allows users to obtain a new JWT without requiring re-authentication, while the short-lived JWT reduces the impact of a potential compromise.