DevToolBoxฟรี
บล็อก

คู่มือ JWT Token: JSON Web Token ทำงานอย่างไร, การยืนยันตัวตนและความปลอดภัย

10 นาทีในการอ่านโดย DevToolBox

What Is a JWT Token?

A JSON Web Token (JWT) is a compact, URL-safe token format used to securely transmit information between parties as a JSON object. JWTs are widely used for authentication, authorization, and information exchange in modern web applications, APIs, and microservices architectures.

JWTs are defined in RFC 7519 and have become the de facto standard for stateless authentication. Unlike traditional session-based authentication where the server stores session data, JWTs are self-contained tokens that carry all the necessary information within themselves. This makes them ideal for distributed systems, single-page applications (SPAs), and mobile apps.

Decode and inspect JWT tokens instantly with our free online JWT Decoder.

JWT Token Structure: Header, Payload, Signature

Every JWT consists of three parts separated by dots (.): the header, the payload, and the signature. Each part is Base64URL-encoded.

JWT Structure:

  xxxxx.yyyyy.zzzzz
    |      |      |
  Header Payload Signature

Example JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Decoded:
  Header:    { "alg": "HS256", "typ": "JWT" }
  Payload:   { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
  Signature: HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

Header

The header typically contains two fields: the token type (typ), which is always JWT, and the signing algorithm (alg) being used, such as HMAC SHA256 (HS256) or RSA (RS256).

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload (Claims)

The payload contains the claims -- statements about the user and additional metadata. There are three types of claims:

Registered claims are predefined by the JWT specification. They include iss (issuer), sub (subject), aud (audience), exp (expiration time), nbf (not before), iat (issued at), and jti (JWT ID).

Public claims are defined by users of JWTs and should be registered in the IANA JSON Web Token Registry to avoid collisions.

Private claims are custom claims created to share information between parties that agree on using them.

{
  "sub": "user-12345",
  "name": "Alice Johnson",
  "email": "alice@example.com",
  "role": "admin",
  "iat": 1708588800,
  "exp": 1708675200,
  "iss": "https://auth.example.com",
  "aud": "https://api.example.com"
}

Signature

The signature is created by taking the encoded header, the encoded payload, a secret key, and the algorithm specified in the header. This ensures the token has not been tampered with.

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  your-256-bit-secret
)

How JWT Authentication Works

JWT-based authentication follows a straightforward flow. The client authenticates with the server, receives a JWT, and includes it in subsequent requests.

JWT Authentication Flow:

1. Client sends credentials (username + password)
   POST /api/auth/login
   { "email": "alice@example.com", "password": "..." }

2. Server validates credentials, creates JWT
   JWT = sign({ sub: "user-123", role: "admin" }, SECRET, { expiresIn: "1h" })

3. Server returns JWT to client
   { "token": "eyJhbGciOiJIUzI1NiIs..." }

4. Client stores JWT (localStorage, cookie, or memory)

5. Client includes JWT in every request
   GET /api/protected-resource
   Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

6. Server verifies JWT signature and checks expiration
   - Valid? -> Process request
   - Invalid/Expired? -> 401 Unauthorized

Creating and Verifying JWTs in Node.js

The jsonwebtoken library is the most widely used package for creating and verifying JWTs in Node.js applications. Here is a complete example:

// Install: npm install jsonwebtoken
const jwt = require('jsonwebtoken');

// Your secret key (in production, use environment variables)
const SECRET_KEY = process.env.JWT_SECRET || 'your-super-secret-key-min-256-bits';

// ========== Creating a JWT ==========

function generateToken(user) {
  const payload = {
    sub: user.id,
    email: user.email,
    role: user.role,
  };

  const options = {
    expiresIn: '1h',         // Token expires in 1 hour
    issuer: 'myapp.com',     // Who issued the token
    audience: 'myapp-api',   // Who the token is intended for
  };

  return jwt.sign(payload, SECRET_KEY, options);
}

// Usage
const token = generateToken({
  id: 'user-123',
  email: 'alice@example.com',
  role: 'admin'
});
console.log(token);
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTEy...

// ========== Verifying a JWT ==========

function verifyToken(token) {
  try {
    const decoded = jwt.verify(token, SECRET_KEY, {
      issuer: 'myapp.com',
      audience: 'myapp-api',
    });
    return { valid: true, payload: decoded };
  } catch (error) {
    return { valid: false, error: error.message };
  }
}

// Usage
const result = verifyToken(token);
if (result.valid) {
  console.log('User ID:', result.payload.sub);
  console.log('Role:', result.payload.role);
} else {
  console.log('Invalid token:', result.error);
}

// ========== Express.js Middleware ==========

function authMiddleware(req, res, next) {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'No token provided' });
  }

  const token = authHeader.split(' ')[1];
  const result = verifyToken(token);

  if (!result.valid) {
    return res.status(401).json({ error: 'Invalid or expired token' });
  }

  req.user = result.payload;
  next();
}

// Protected route
app.get('/api/profile', authMiddleware, (req, res) => {
  res.json({ userId: req.user.sub, role: req.user.role });
});

Creating and Verifying JWTs in Python

Python developers can use the PyJWT library for JWT operations. Here is a complete example with Flask integration:

# Install: pip install PyJWT
import jwt
import datetime

SECRET_KEY = "your-super-secret-key-min-256-bits"

# ========== Creating a JWT ==========

def generate_token(user_id, email, role):
    payload = {
        "sub": user_id,
        "email": email,
        "role": role,
        "iat": datetime.datetime.utcnow(),
        "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1),
        "iss": "myapp.com",
    }
    return jwt.encode(payload, SECRET_KEY, algorithm="HS256")

token = generate_token("user-123", "alice@example.com", "admin")
print(token)

# ========== Verifying a JWT ==========

def verify_token(token):
    try:
        payload = jwt.decode(
            token,
            SECRET_KEY,
            algorithms=["HS256"],
            issuer="myapp.com",
        )
        return {"valid": True, "payload": payload}
    except jwt.ExpiredSignatureError:
        return {"valid": False, "error": "Token expired"}
    except jwt.InvalidTokenError as e:
        return {"valid": False, "error": str(e)}

result = verify_token(token)
print(result)

# ========== Flask Decorator ==========

from flask import Flask, request, jsonify
from functools import wraps

app = Flask(__name__)

def require_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth_header = request.headers.get("Authorization", "")
        if not auth_header.startswith("Bearer "):
            return jsonify({"error": "No token provided"}), 401

        token = auth_header.split(" ")[1]
        result = verify_token(token)

        if not result["valid"]:
            return jsonify({"error": result["error"]}), 401

        request.user = result["payload"]
        return f(*args, **kwargs)
    return decorated

@app.route("/api/profile")
@require_auth
def profile():
    return jsonify({"userId": request.user["sub"], "role": request.user["role"]})

JWT Signing Algorithms: HS256 vs RS256 vs ES256

Choosing the right signing algorithm is critical for your JWT security. Here is a comparison of the most commonly used algorithms:

JWT Signing Algorithm Comparison:

Algorithm  Type         Key             Best For
---------  ----------   ------------    ----------------------
HS256      Symmetric    Shared secret   Single-server apps
HS384      Symmetric    Shared secret   Higher security needs
HS512      Symmetric    Shared secret   Maximum HMAC security
RS256      Asymmetric   RSA key pair    Microservices, public APIs
RS384      Asymmetric   RSA key pair    Higher security needs
RS512      Asymmetric   RSA key pair    Maximum RSA security
ES256      Asymmetric   ECDSA P-256     Mobile, performance-sensitive
ES384      Asymmetric   ECDSA P-384     Higher security needs
ES512      Asymmetric   ECDSA P-521     Maximum ECDSA security
EdDSA      Asymmetric   Ed25519         Modern, fastest asymmetric

Symmetric (HS*):
  - Same key for signing and verification
  - Fast, simple
  - Secret must be shared between all services

Asymmetric (RS*, ES*, EdDSA):
  - Private key signs, public key verifies
  - No need to share private key
  - Ideal for distributed systems and third-party verification

RS256 Example (Asymmetric)

const jwt = require('jsonwebtoken');
const fs = require('fs');

// Generate keys: openssl genrsa -out private.pem 2048
//                openssl rsa -in private.pem -pubout -out public.pem

const privateKey = fs.readFileSync('private.pem');
const publicKey = fs.readFileSync('public.pem');

// Sign with private key (auth server)
const token = jwt.sign(
  { sub: 'user-123', role: 'admin' },
  privateKey,
  { algorithm: 'RS256', expiresIn: '1h' }
);

// Verify with public key (any service)
const decoded = jwt.verify(token, publicKey, {
  algorithms: ['RS256']
});
console.log(decoded.sub); // "user-123"

JWT vs Sessions: When to Use Each

Both JWTs and server-side sessions are valid approaches to authentication, but they have different trade-offs:

JWT vs Session Comparison:

Feature              JWT (Stateless)            Session (Stateful)
-----------          ----------------           -------------------
Storage              Client-side                Server-side (Redis/DB)
Scalability          Excellent (no server state) Requires shared session store
Revocation           Difficult (use deny-list)  Easy (delete session)
Size                 Larger (payload in token)  Small (just session ID)
Cross-domain         Easy (Authorization header) Requires CORS cookie config
Mobile-friendly      Yes                        Cookie management needed
Offline support      Yes (token is self-contained) No (needs server check)
Server memory        None                       Proportional to active users

When to use JWT:
  - Microservices architecture
  - Mobile/SPA applications
  - Cross-domain authentication (SSO)
  - Stateless API authentication
  - Short-lived access tokens

When to use Sessions:
  - Traditional server-rendered apps
  - Need immediate token revocation
  - Small number of users / single server
  - Sensitive applications (banking)

Refresh Token Pattern

In production applications, JWTs should have short expiration times (15 minutes to 1 hour). To avoid forcing users to log in repeatedly, implement the refresh token pattern.

// ========== Refresh Token Implementation ==========

const jwt = require('jsonwebtoken');
const crypto = require('crypto');

const ACCESS_SECRET = process.env.ACCESS_TOKEN_SECRET;
const REFRESH_SECRET = process.env.REFRESH_TOKEN_SECRET;

// In-memory store (use Redis/DB in production)
const refreshTokens = new Map();

// Generate token pair on login
function login(user) {
  // Short-lived access token (15 min)
  const accessToken = jwt.sign(
    { sub: user.id, role: user.role },
    ACCESS_SECRET,
    { expiresIn: '15m' }
  );

  // Long-lived refresh token (7 days)
  const refreshToken = jwt.sign(
    { sub: user.id, jti: crypto.randomUUID() },
    REFRESH_SECRET,
    { expiresIn: '7d' }
  );

  // Store refresh token hash for revocation
  refreshTokens.set(user.id, refreshToken);

  return { accessToken, refreshToken };
}

// Refresh endpoint
app.post('/api/auth/refresh', (req, res) => {
  const { refreshToken } = req.body;

  try {
    const decoded = jwt.verify(refreshToken, REFRESH_SECRET);

    // Check if refresh token is still valid (not revoked)
    const stored = refreshTokens.get(decoded.sub);
    if (stored !== refreshToken) {
      return res.status(401).json({ error: 'Token revoked' });
    }

    // Issue new access token
    const newAccessToken = jwt.sign(
      { sub: decoded.sub, role: 'admin' },
      ACCESS_SECRET,
      { expiresIn: '15m' }
    );

    res.json({ accessToken: newAccessToken });
  } catch (err) {
    res.status(401).json({ error: 'Invalid refresh token' });
  }
});

// Logout - revoke refresh token
app.post('/api/auth/logout', authMiddleware, (req, res) => {
  refreshTokens.delete(req.user.sub);
  res.json({ message: 'Logged out' });
});

Common JWT Security Mistakes (and How to Avoid Them)

JWTs are powerful but can introduce security vulnerabilities if implemented incorrectly. Here are the most common mistakes:

1. Storing Sensitive Data in the Payload

The JWT payload is Base64URL-encoded, not encrypted. Anyone can decode it. Never include passwords, credit card numbers, or other sensitive data in JWT claims.

// BAD - sensitive data in payload
const badToken = jwt.sign({
  sub: 'user-123',
  password: 'mypassword',    // NEVER do this
  ssn: '123-45-6789',        // NEVER do this
  creditCard: '4111...',     // NEVER do this
}, SECRET);

// GOOD - minimal claims
const goodToken = jwt.sign({
  sub: 'user-123',
  role: 'admin',
  permissions: ['read', 'write'],
}, SECRET, { expiresIn: '15m' });

2. Using the "none" Algorithm

The JWT specification includes an alg: "none" option that creates unsigned tokens. Always validate the algorithm on the server and reject unsigned tokens.

// VULNERABLE - accepts any algorithm
const decoded = jwt.verify(token, SECRET);

// SECURE - explicitly specify allowed algorithms
const decoded = jwt.verify(token, SECRET, {
  algorithms: ['HS256']   // Only accept HS256
});

3. Weak Secret Keys

For HMAC algorithms, the secret key must be at least 256 bits (32 bytes) long and cryptographically random. Short or predictable secrets can be brute-forced.

# Generate a secure secret key
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"

# Or use openssl
openssl rand -hex 64

4. Not Validating Expiration

Always set and check the exp claim. Without expiration, a stolen token can be used indefinitely.

5. Storing JWTs in localStorage

LocalStorage is vulnerable to XSS (Cross-Site Scripting) attacks. For web applications, store JWTs in HttpOnly, Secure, SameSite cookies instead.

// VULNERABLE - localStorage (accessible via JavaScript / XSS)
localStorage.setItem('token', jwt);

// SECURE - HttpOnly cookie (not accessible via JavaScript)
res.cookie('accessToken', jwt, {
  httpOnly: true,     // Cannot be read by JavaScript
  secure: true,       // Only sent over HTTPS
  sameSite: 'strict', // Prevents CSRF
  maxAge: 15 * 60 * 1000, // 15 minutes
  path: '/',
});

Decoding JWT Tokens Without a Library

Since JWTs are just Base64URL-encoded JSON, you can decode the header and payload without any library. This is useful for debugging but does not verify the signature.

// Decode JWT in the browser (no library needed)
function decodeJwt(token) {
  const parts = token.split('.');
  if (parts.length !== 3) throw new Error('Invalid JWT format');

  const decodeBase64Url = (str) => {
    // Add padding
    str = str.replace(/-/g, '+').replace(/_/g, '/');
    while (str.length % 4) str += '=';
    return JSON.parse(atob(str));
  };

  return {
    header: decodeBase64Url(parts[0]),
    payload: decodeBase64Url(parts[1]),
    signature: parts[2],
  };
}

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.' +
  'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.' +
  'SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';

const decoded = decodeJwt(token);
console.log(decoded.header);  // { alg: "HS256", typ: "JWT" }
console.log(decoded.payload); // { sub: "1234567890", name: "John Doe", iat: 1516239022 }

JWT Best Practices Checklist

Follow these best practices for secure JWT implementation in production:

JWT Security Checklist:

  [x] Use strong, cryptographically random secret keys (256+ bits)
  [x] Set short expiration times (15 min for access tokens)
  [x] Implement refresh token rotation
  [x] Explicitly specify allowed algorithms during verification
  [x] Never store sensitive data in the payload
  [x] Use HttpOnly cookies instead of localStorage for web apps
  [x] Validate all registered claims (exp, iss, aud, nbf)
  [x] Use asymmetric algorithms (RS256/ES256) for distributed systems
  [x] Implement token revocation via deny-lists for critical apps
  [x] Always use HTTPS to prevent token interception
  [x] Keep payload size small (avoid bloating tokens)
  [x] Rotate signing keys periodically
  [x] Log and monitor failed verification attempts

Frequently Asked Questions

What does JWT stand for?

JWT stands for JSON Web Token. It is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. The information can be verified and trusted because it is digitally signed.

Can JWT tokens be hacked?

JWT tokens cannot be tampered with if properly implemented because the signature prevents modification. However, JWTs can be stolen through XSS attacks, man-in-the-middle attacks (without HTTPS), or insecure storage. The payload can always be decoded (it is not encrypted), so never store sensitive information in it. Use short expiration times and secure storage to minimize risk.

Should I use JWT or session cookies?

Use JWTs for stateless APIs, microservices, SPAs, and mobile apps where scalability matters. Use session cookies for traditional server-rendered applications, when you need immediate token revocation, or for highly sensitive applications like banking where fine-grained control is critical.

How long should a JWT token last?

Access tokens should expire in 15 minutes to 1 hour. Refresh tokens can last 7 to 30 days. The exact duration depends on your security requirements. Shorter expiration times are more secure but require more frequent token refreshes.

What is the difference between JWT and OAuth?

JWT is a token format, while OAuth 2.0 is an authorization framework. OAuth defines the flow for obtaining tokens, and JWTs are often used as the token format within OAuth flows. OAuth issues access tokens (which can be JWTs) and refresh tokens through defined grant types like authorization code, client credentials, and PKCE.

Related Tools and Guides

𝕏 Twitterin LinkedIn
บทความนี้มีประโยชน์ไหม?

อัปเดตข่าวสาร

รับเคล็ดลับการพัฒนาและเครื่องมือใหม่ทุกสัปดาห์

ไม่มีสแปม ยกเลิกได้ตลอดเวลา

ลองเครื่องมือที่เกี่ยวข้อง

JWTJWT DecoderJWTJWT Decoder Online🔐Base64 Encoder Decoder

บทความที่เกี่ยวข้อง

JWT ทำงานอย่างไร: คู่มือ JSON Web Tokens ฉบับสมบูรณ์

เรียนรู้วิธีการทำงานของ JWT authentication กับ header, payload และ signature

ถอดรหัส JWT โดยไม่ใช้ Library: JavaScript, Python, Bash One-Liner

ถอดรหัส JWT token โดยไม่ต้องติดตั้ง library

API Authentication: OAuth 2.0 vs JWT vs API Key

เปรียบเทียบวิธีการยืนยันตัวตน API: OAuth 2.0, JWT Bearer token และ API key