Skip to content

Orbit2x/jwt-decoder-security-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 

Repository files navigation

JWT Decoder & Security Testing Guide

JWT Decoder JWT Cracker MIT License

Need to decode JWT tokens? Try the free JWT Decoder at Orbit2x - inspect headers, payloads, and verify signatures instantly without installing libraries.

What is JWT (JSON Web Token)?

JWT is a compact, URL-safe token format used for authentication and information exchange. JWTs are commonly used in:

  • API Authentication - Bearer tokens for REST APIs
  • Single Sign-On (SSO) - Auth0, Okta, AWS Cognito
  • OAuth 2.0 - Access tokens and refresh tokens
  • Session Management - Stateless session storage
  • Microservices - Service-to-service authentication

JWT Structure

A JWT consists of three Base64URL-encoded parts separated by dots (.):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
│                                      │                                                                  │
│         HEADER                       │                    PAYLOAD                                       │         SIGNATURE

Decoded:

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

// PAYLOAD
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

// SIGNATURE
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

Quick Decode - Online Tool

No installation required! Use the free online decoder:

👉 JWT Decoder at Orbit2x

Features:

  • ✅ Decode header and payload instantly
  • ✅ Inspect all claims (sub, iss, exp, iat, nbf)
  • ✅ Verify signatures (HS256, HS384, HS512, RS256)
  • ✅ Check expiration status
  • ✅ View token metadata
  • ✅ 100% client-side (no server upload)
  • ✅ Copy decoded JSON
  • ✅ Free, no signup

Decoding JWT in Code

JavaScript/Node.js

// Decode without verification (read-only)
function decodeJWT(token) {
  const parts = token.split('.');
  if (parts.length !== 3) {
    throw new Error('Invalid JWT format');
  }

  const header = JSON.parse(atob(parts[0]));
  const payload = JSON.parse(atob(parts[1]));

  return { header, payload };
}

// Usage
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
const decoded = decodeJWT(token);

console.log('Header:', decoded.header);
console.log('Payload:', decoded.payload);
console.log('Expires:', new Date(decoded.payload.exp * 1000));

With Verification (using jsonwebtoken library):

npm install jsonwebtoken
const jwt = require('jsonwebtoken');

// Decode and verify HMAC signature
try {
  const decoded = jwt.verify(token, 'your-256-bit-secret');
  console.log('✅ Valid token:', decoded);
} catch (err) {
  console.error('❌ Invalid token:', err.message);
}

// Decode without verification (unsafe for production)
const decoded = jwt.decode(token, { complete: true });
console.log('Header:', decoded.header);
console.log('Payload:', decoded.payload);

// Verify RSA signature (public key)
const publicKey = fs.readFileSync('public-key.pem');
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });

Python

pip install pyjwt
import jwt
import json
from base64 import urlsafe_b64decode

# Decode without verification
def decode_jwt(token):
    # Split token
    parts = token.split('.')
    if len(parts) != 3:
        raise ValueError('Invalid JWT format')

    # Add padding if needed
    header = parts[0] + '=' * (4 - len(parts[0]) % 4)
    payload = parts[1] + '=' * (4 - len(parts[1]) % 4)

    # Decode
    header_decoded = json.loads(urlsafe_b64decode(header))
    payload_decoded = json.loads(urlsafe_b64decode(payload))

    return header_decoded, payload_decoded

# With verification (PyJWT)
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

try:
    # Verify HMAC signature
    decoded = jwt.decode(token, 'your-256-bit-secret', algorithms=['HS256'])
    print('✅ Valid token:', decoded)
except jwt.ExpiredSignatureError:
    print('❌ Token expired')
except jwt.InvalidTokenError:
    print('❌ Invalid token')

# Decode without verification (read claims only)
decoded = jwt.decode(token, options={"verify_signature": False})
print('Payload:', decoded)

# Get header
header = jwt.get_unverified_header(token)
print('Algorithm:', header['alg'])

Go

go get github.com/golang-jwt/jwt/v5
package main

import (
    "encoding/base64"
    "encoding/json"
    "fmt"
    "log"
    "strings"

    "github.com/golang-jwt/jwt/v5"
)

// Decode without verification
func decodeJWT(tokenString string) (map[string]interface{}, map[string]interface{}, error) {
    parts := strings.Split(tokenString, ".")
    if len(parts) != 3 {
        return nil, nil, fmt.Errorf("invalid JWT format")
    }

    // Decode header
    headerBytes, _ := base64.RawURLEncoding.DecodeString(parts[0])
    var header map[string]interface{}
    json.Unmarshal(headerBytes, &header)

    // Decode payload
    payloadBytes, _ := base64.RawURLEncoding.DecodeString(parts[1])
    var payload map[string]interface{}
    json.Unmarshal(payloadBytes, &payload)

    return header, payload, nil
}

// With verification
func verifyJWT(tokenString string, secret []byte) (*jwt.Token, error) {
    token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        // Validate algorithm
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
        }
        return secret, nil
    })

    if err != nil {
        return nil, err
    }

    if !token.Valid {
        return nil, fmt.Errorf("invalid token")
    }

    return token, nil
}

func main() {
    tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

    // Decode without verification
    header, payload, _ := decodeJWT(tokenString)
    fmt.Println("Header:", header)
    fmt.Println("Payload:", payload)

    // Verify signature
    token, err := verifyJWT(tokenString, []byte("your-256-bit-secret"))
    if err != nil {
        log.Fatal("Invalid token:", err)
    }

    // Extract claims
    if claims, ok := token.Claims.(jwt.MapClaims); ok {
        fmt.Println("User ID:", claims["sub"])
        fmt.Println("Name:", claims["name"])
        fmt.Println("Issued at:", claims["iat"])
    }
}

PHP

<?php
// Decode without verification
function decodeJWT($token) {
    $parts = explode('.', $token);
    if (count($parts) !== 3) {
        throw new Exception('Invalid JWT format');
    }

    $header = json_decode(base64_decode(strtr($parts[0], '-_', '+/')), true);
    $payload = json_decode(base64_decode(strtr($parts[1], '-_', '+/')), true);

    return ['header' => $header, 'payload' => $payload];
}

// Usage
$token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
$decoded = decodeJWT($token);

echo "Header: " . json_encode($decoded['header']) . "\n";
echo "Payload: " . json_encode($decoded['payload']) . "\n";

// With verification (using firebase/php-jwt)
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$secret = 'your-256-bit-secret';

try {
    $decoded = JWT::decode($token, new Key($secret, 'HS256'));
    echo "✅ Valid token\n";
    print_r($decoded);
} catch (Exception $e) {
    echo "❌ Invalid token: " . $e->getMessage() . "\n";
}
?>

Common JWT Claims

Registered Claims (RFC 7519)

Claim Name Type Description
iss Issuer String Token issuer (e.g., "auth0.com")
sub Subject String User ID or subject identifier
aud Audience String/Array Intended recipient(s)
exp Expiration Time Number Unix timestamp when token expires
nbf Not Before Number Unix timestamp when token becomes valid
iat Issued At Number Unix timestamp when token was created
jti JWT ID String Unique identifier for token

Custom Claims (Application-Specific)

{
  "sub": "user123",
  "name": "John Doe",
  "email": "john@example.com",
  "roles": ["admin", "editor"],
  "permissions": ["read", "write", "delete"],
  "org_id": "company-456",
  "premium": true,
  "iat": 1516239022,
  "exp": 1516242622
}

Try decoding your tokens: JWT Decoder


JWT Security Best Practices

✅ DO

  1. Use Strong Secrets

    // ❌ Weak secret
    const secret = "password123";
    
    // ✅ Strong secret (256+ bits)
    const secret = crypto.randomBytes(32).toString('hex');
    // "a4f8e9c2b7d6f1a3e8c7b2d9f4a1e6c3b8d2f9a4e7c1b6d3f8a2e9c4b7d1f6a3"
  2. Set Expiration Times

    // ✅ Access token: 15-60 minutes
    jwt.sign(payload, secret, { expiresIn: '15m' });
    
    // ✅ Refresh token: 7-30 days
    jwt.sign(payload, secret, { expiresIn: '7d' });
  3. Use HTTPS Only

    // ✅ Send JWT over HTTPS
    res.cookie('token', jwt, {
      httpOnly: true,
      secure: true,  // HTTPS only
      sameSite: 'strict'
    });
  4. Verify Algorithm

    // ✅ Whitelist allowed algorithms
    jwt.verify(token, secret, { algorithms: ['HS256'] });
  5. Check Expiration

    // ✅ Validate exp claim
    const decoded = jwt.verify(token, secret);
    if (decoded.exp < Date.now() / 1000) {
      throw new Error('Token expired');
    }

❌ DON'T

  1. Don't use none algorithm

    // ❌ DANGEROUS - No signature verification!
    { "alg": "none", "typ": "JWT" }
  2. Don't store sensitive data

    // ❌ JWT payload is NOT encrypted (only Base64 encoded)
    {
      "password": "secret123",  // NEVER do this
      "credit_card": "1234-5678-9012-3456"  // NEVER do this
    }
  3. Don't accept all algorithms

    // ❌ Vulnerable to algorithm confusion attacks
    jwt.verify(token, secret);  // Missing algorithm check
    
    // ✅ Specify allowed algorithms
    jwt.verify(token, secret, { algorithms: ['HS256'] });
  4. Don't skip signature verification

    // ❌ Unsafe - anyone can modify payload
    const decoded = jwt.decode(token);
    
    // ✅ Always verify signature
    const decoded = jwt.verify(token, secret);
  5. Don't use weak secrets

    // ❌ Weak secrets can be cracked
    const secret = "secret";  // 6 characters = crackable in seconds
    
    // ✅ Use 256+ bit secrets
    const secret = crypto.randomBytes(32).toString('hex');  // 64 hex chars = 256 bits

JWT Security Testing

Test JWT Security Online

👉 JWT Signature Cracker - Test your JWT secrets for weakness (educational/authorized testing only)

Features:

  • ✅ Dictionary attack with 10,000+ common passwords
  • ✅ Custom wordlist upload
  • ✅ HS256/HS384/HS512 support
  • ✅ Performance metrics
  • ✅ Security recommendations

WARNING: Only test JWTs you own or have written permission to test.

Manual Security Check

# Check algorithm (look for "none" or weak algorithms)
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...." | cut -d'.' -f1 | base64 -d

# Check expiration
echo "eyJhbGci..." | cut -d'.' -f2 | base64 -d | jq '.exp'
date -d @1516242622  # Convert Unix timestamp

# Verify signature manually (HS256)
echo -n "header.payload" | openssl dgst -sha256 -hmac "your-secret" -binary | base64

Common Vulnerabilities

Vulnerability Description Impact Fix
Algorithm Confusion Accept none algorithm No signature = attacker can forge tokens Whitelist HS256 only
Weak Secret Use short/common secrets Brute force attack Use 256+ bit random secrets
No Expiration Missing exp claim Tokens valid forever Set exp claim (15-60 min)
Public Key Substitution RS256 → HS256 switching Attacker uses public key as secret Validate algorithm
JTI Reuse Reuse jti claim Token replay attacks Use unique jti per token

Test your tokens: JWT Cracker (educational purposes)


JWT vs Other Authentication Methods

Method Storage Stateless Scalability Security Use Case
JWT Client ✅ Yes Excellent Good* APIs, SPAs, Mobile
Session Cookies Server ❌ No Limited Excellent Traditional web apps
OAuth 2.0 Server ❌ No Good Excellent Third-party auth
API Keys Client ✅ Yes Excellent Basic Service-to-service
SAML Server ❌ No Limited Excellent Enterprise SSO

*Good if implemented correctly with strong secrets and HTTPS


Complete JWT Workflow Example

1. User Login (Generate JWT)

// Login endpoint
app.post('/login', async (req, res) => {
  const { email, password } = req.body;

  // Verify credentials
  const user = await User.findOne({ email });
  if (!user || !await bcrypt.compare(password, user.password)) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  // Generate access token (15 min)
  const accessToken = jwt.sign(
    {
      sub: user.id,
      email: user.email,
      roles: user.roles
    },
    process.env.JWT_SECRET,
    { expiresIn: '15m' }
  );

  // Generate refresh token (7 days)
  const refreshToken = jwt.sign(
    { sub: user.id },
    process.env.JWT_REFRESH_SECRET,
    { expiresIn: '7d' }
  );

  // Store refresh token in database
  await RefreshToken.create({
    userId: user.id,
    token: refreshToken,
    expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
  });

  res.json({ accessToken, refreshToken });
});

2. Protected Route (Verify JWT)

// Middleware to verify JWT
const authenticateJWT = (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.substring(7);  // Remove "Bearer "

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;  // Attach user to request
    next();
  } catch (err) {
    if (err.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'Token expired' });
    }
    return res.status(403).json({ error: 'Invalid token' });
  }
};

// Protected route
app.get('/profile', authenticateJWT, async (req, res) => {
  const user = await User.findById(req.user.sub);
  res.json({ user });
});

3. Refresh Token

app.post('/refresh', async (req, res) => {
  const { refreshToken } = req.body;

  if (!refreshToken) {
    return res.status(401).json({ error: 'No refresh token' });
  }

  try {
    // Verify refresh token
    const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);

    // Check if token exists in database
    const storedToken = await RefreshToken.findOne({
      userId: decoded.sub,
      token: refreshToken
    });

    if (!storedToken) {
      return res.status(403).json({ error: 'Invalid refresh token' });
    }

    // Generate new access token
    const user = await User.findById(decoded.sub);
    const accessToken = jwt.sign(
      {
        sub: user.id,
        email: user.email,
        roles: user.roles
      },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }
    );

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

4. Logout (Revoke Token)

app.post('/logout', authenticateJWT, async (req, res) => {
  const { refreshToken } = req.body;

  // Delete refresh token from database
  await RefreshToken.deleteOne({
    userId: req.user.sub,
    token: refreshToken
  });

  res.json({ message: 'Logged out successfully' });
});

Tools & Resources

Online Tools

Libraries

References


FAQ

Q: Is JWT secure?

A: JWT is secure IF implemented correctly:

  • ✅ Use strong secrets (256+ bits)
  • ✅ Verify signatures
  • ✅ Set expiration times
  • ✅ Use HTTPS only
  • ❌ Don't store secrets in JWT payload

Q: Can JWT be encrypted?

A: Yes, use JWE (JSON Web Encryption) for encrypted tokens. Standard JWT is only signed, not encrypted.

Q: Should I store JWT in localStorage or cookies?

A:

  • Cookies (HttpOnly, Secure, SameSite) - More secure against XSS
  • localStorage - Easier to use but vulnerable to XSS
  • Recommendation: Use HttpOnly cookies for web apps

Q: How do I revoke a JWT?

A: JWTs can't be revoked (stateless). Solutions:

  1. Use short expiration times (15 min)
  2. Store token IDs in database (blacklist)
  3. Use refresh tokens for long-lived sessions

Q: What's the difference between HS256 and RS256?

A:

  • HS256 (HMAC): Symmetric secret key, faster, good for single-server
  • RS256 (RSA): Asymmetric keypair, slower, good for distributed systems

Related Tools


Made with ❤️ by Orbit2x - Free Developer & Security Tools

Decode tokens now: JWT DecoderTest Security

About

JWT Decoder & Security Testing Guide

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published