Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
17 Aug 2025 · 5 min read ·Article 48 / 125
Go

48 Storing and Validating JWT Tokens

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

48 Storing and Validating JWT Tokens

JSON Web Token (JWT) has become the de facto standard for modern authentication, especially for web and mobile applications. Using JWT allows us to build a stateless authentication system, so the server no longer needs to store user sessions in the backend. Even so, there are still many fundamental questions among engineers, particularly about where to store the JWT token and how to validate it securely. This article will thoroughly cover those two key aspects: storing and validating JWTs, complete with code examples, a simple architectural simulation, and several best practices worth following.


What Is a JWT?

Put simply, a JWT is a specially formatted string made up of three main parts:

  1. Header
  2. Payload
  3. Signature

A JWT is sent to the client after the user successfully logs in, and is then sent back to the server on every request that requires authorization. JWTs are not only efficient but also portable—they can be carried anywhere, anytime.

Example JWT token:

text
1eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsIm5hbWUiOiJKb2huIERvZSJ9.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

Part 1: Storing the JWT Token on the Client

Storing a JWT token on the client side can be a double-edged sword. Choosing the wrong storage location can open up security holes such as XSS and CSRF. There are several main options:

MethodProsCons
Local StorageEasy to implement, broadly accessibleVulnerable to XSS
Session StorageLike localStorage, but session-basedVulnerable to XSS, lost when the tab is closed
Cookie (HTTPOnly)Not accessible from JS, safe from XSSVulnerable to CSRF without SameSite

Table 1: Comparison of JWT token storage locations

Simulating JWT Storage in Local Storage

javascript
 1// After a successful login
 2localStorage.setItem('access_token', jwtToken);
 3
 4// Retrieving the token on subsequent requests
 5const token = localStorage.getItem('access_token');
 6fetch('/api/resource', {
 7  headers: {
 8    'Authorization': `Bearer ${token}`
 9  }
10});

However, localStorage is highly vulnerable to XSS attacks. If an attacker manages to inject malicious scripts into the application, the token can be stolen with ease.

The best approach for higher security needs is to store the JWT in a cookie marked with the HttpOnly and SameSite flags.

javascript
1// The server sends a set-cookie header on login
2Set-Cookie: token=eyJhbGc...; HttpOnly; Secure; SameSite=Strict

This way, the token cannot be accessed from JavaScript, so an XSS attack cannot retrieve it.

Pro Tips:

  • Use SameSite=Strict if your application does not need to share cookies across domains.
  • Add Secure so the cookie is only sent over HTTPS.

Part 2: Checking/Validating the JWT Token on the Server

Once the JWT token is stored on the client, the next step is to validate every incoming request. Typically, this process is handled by middleware in the backend. Let’s go through it on two stacks: Node.js (Express) and Go (Golang).

2.1 Checking a JWT Token in Node.js/Express

To verify a JWT in Express, we’ll use the jsonwebtoken library.

Express Middleware: authenticateJWT.js

javascript
 1const jwt = require('jsonwebtoken');
 2
 3function authenticateJWT(req, res, next) {
 4  const authHeader = req.headers.authorization;
 5
 6  if (authHeader) {
 7    // Format: "Bearer <token>"
 8    const token = authHeader.split(' ')[1];
 9
10    jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
11      if (err) {
12        return res.sendStatus(403); // Forbidden
13      }
14      req.user = user;
15      next();
16    });
17  } else {
18    res.sendStatus(401); // Unauthorized
19  }
20}
21
22module.exports = authenticateJWT;

Using the Middleware

javascript
1const express = require('express');
2const authenticateJWT = require('./authenticateJWT');
3
4const app = express();
5
6app.get('/api/resource', authenticateJWT, (req, res) => {
7  res.json({ message: "Hello, " + req.user.name });
8});

2.2 Verifying a JWT in Golang

In Golang, we typically use the github.com/golang-jwt/jwt/v4 package.

go
 1import (
 2    "github.com/golang-jwt/jwt/v4"
 3    "net/http"
 4    "strings"
 5)
 6
 7var secretKey = []byte("your-secret-key")
 8
 9func authMiddleware(next http.Handler) http.Handler {
10    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
11        authHeader := r.Header.Get("Authorization")
12        if authHeader == "" {
13            http.Error(w, "missing auth header", http.StatusUnauthorized)
14            return
15        }
16
17        tokenString := strings.TrimPrefix(authHeader, "Bearer ")
18        token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
19            return secretKey, nil
20        })
21
22        if err != nil || !token.Valid {
23            http.Error(w, "invalid token", http.StatusForbidden)
24            return
25        }
26        next.ServeHTTP(w, r)
27    })
28}

Flow Diagram: The JWT Validation Process

Let’s visually illustrate the flow of storing and checking a JWT token.

MERMAID
sequenceDiagram
    participant Client
    participant Server

    Client->>Server: Login (username, password)
    Server-->>Client: JWT Token (response body / Set-Cookie)
    Note over Client: Simpan token (localStorage/cookie)

    Client->>Server: Request API (Authorization: Bearer)
    Server->>Server: Verifikasi JWT (cek signature & expired)
    alt Valid
      Server-->>Client: Data/Respon Success
    else Tidak Valid
      Server-->>Client: 401/403 Error
    end

Case Study: Client Local Storage vs. Cookies

ScenariolocalStorageCookies (HTTPOnly)
Can be stolen via XSSYesNo
Can be used for CSRFNo (generally)Yes (without SameSite=Strict)
Accessible from JavaScriptYesNo
Safari/Android implementationConsistentSometimes problematic with CORS and domain/subdomain

Best Practices

  1. Use an HTTPOnly cookie if your application places a high priority on security.
  2. Add a separate refresh token mechanism, rather than baking it into the main JWT.
  3. Keep the JWT library on the server up to date.
  4. Always validate the exp (expiration) field in the JWT payload.
  5. Do not store sensitive data in the payload.
  6. Implement revocation by keeping a blacklist on the server (optional, if you need session revocation).

Conclusion

Storing and validating JWT tokens is not merely a technical implementation, but also an architectural decision that greatly affects your application’s security.
Before choosing a storage method, understand the trade-off between convenience (developer experience) and safety (security risk). For small-to-medium scale applications, localStorage is often the practical choice, but for financial applications or sensitive data, a cookie with HttpOnly is an absolute must.

Always reassess your application’s security landscape periodically, and don’t hesitate to adopt the latest best practices in the world of JWT authentication. Happy coding!

Related Articles

💬 Comments