DEV Community

Cover image for Auth with JWT + localStorage
Jayant
Jayant

Posted on

Auth with JWT + localStorage

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:

Login
Login

Auth Request
Auth Request

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
Enter fullscreen mode Exit fullscreen mode
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");
});
Enter fullscreen mode Exit fullscreen mode

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");
});
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Disadvantages of Using This Strategy

  1. Security Risks with LocalStorage: Storing sensitive information, such as JWT tokens, in LocalStorage is not recommended. This is because LocalStorage 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.
  2. 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.

Top comments (0)