DevToolBoxGRATIS
Blog

Autentikasi JWT: Panduan Implementasi Lengkap

16 menit bacaoleh DevToolBox

TL;DR

JWT (JSON Web Token) is a compact, self-contained token format for transmitting authenticated claims between parties. A JWT has three base64url-encoded parts: header.payload.signature. Use HS256 for single-service auth and RS256 for multi-service architectures. Always set exp, verify the algorithm explicitly, and never store JWTs in localStorage. Use short-lived access tokens (15 min) combined with refresh tokens for the best balance of security and UX. Decode and inspect your tokens instantly with our online JWT decoder.

JWT Structure — header.payload.signature

A JSON Web Token (JWT) is defined by RFC 7519 as a compact URL-safe means of representing claims to be transferred between two parties. Every JWT has exactly three parts, separated by dots (.), each individually base64url-encoded:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiJ1c2VyXzEyMyIsImlzcyI6ImFwaS5leGFtcGxlLmNvbSIsImV4cCI6MTcwOTQ3MzYwMCwiaWF0IjoxNzA5NDY2NDAwLCJqdGkiOiJ1dWlkLWFiYy0xMjMifQ
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

# Part 1 — Header (decoded):
{
  "alg": "HS256",   // signing algorithm
  "typ": "JWT"      // token type
}

# Part 2 — Payload (decoded):
{
  "sub": "user_123",                  // subject (user ID)
  "iss": "api.example.com",           // issuer
  "exp": 1709473600,                  // expiry (Unix timestamp)
  "iat": 1709466400,                  // issued at
  "jti": "uuid-abc-123",              // JWT ID (for revocation)
  "role": "admin",                    // custom claim
  "email": "alice@example.com"        // custom claim
}

# Part 3 — Signature:
HMACSHA256(
  base64url(header) + "." + base64url(payload),
  secret
)

The header specifies the algorithm (alg) and token type (typ). The payload contains claims — the standard registered ones are:

ClaimFull NamePurposeRequired?
issIssuerWho issued the token (your domain or service name)Recommended
subSubjectWho the token is about (user ID)Recommended
expExpiration TimeToken is invalid after this Unix timestampCritical
iatIssued AtWhen the token was createdRecommended
jtiJWT IDUnique identifier for revocation/replay preventionOptional but useful

Important: JWT payloads are base64url-encoded, not encrypted. Anyone who has the token can read the payload — do not store sensitive data like passwords, credit card numbers, or secrets in JWT claims. The signature only proves the token was not tampered with; it does not hide the contents.

Creating JWTs in Node.js with jsonwebtoken

The jsonwebtoken package is the standard Node.js library for creating and verifying JWTs.

npm install jsonwebtoken
npm install --save-dev @types/jsonwebtoken
import jwt from 'jsonwebtoken';

const SECRET = process.env.JWT_SECRET!;  // at least 32 chars in production

// ─── Sign (create) a JWT ─────────────────────────────────────────────────────
const token = jwt.sign(
  {
    sub: 'user_123',
    email: 'alice@example.com',
    role: 'admin',
  },
  SECRET,
  {
    expiresIn: '15m',       // 15 minutes for access tokens
    algorithm: 'HS256',
    issuer: 'api.example.com',
    audience: 'app.example.com',
    jwtid: crypto.randomUUID(),  // unique jti for revocation
  }
);

// ─── Verify a JWT ────────────────────────────────────────────────────────────
try {
  const decoded = jwt.verify(token, SECRET, {
    algorithms: ['HS256'],         // ALWAYS specify — prevents alg:none attack
    issuer: 'api.example.com',
    audience: 'app.example.com',
  }) as jwt.JwtPayload;

  console.log(decoded.sub);      // 'user_123'
  console.log(decoded.role);     // 'admin'
  console.log(decoded.exp);      // expiry timestamp
} catch (err) {
  if (err instanceof jwt.TokenExpiredError) {
    console.error('Token expired at', err.expiredAt);
  } else if (err instanceof jwt.JsonWebTokenError) {
    console.error('Invalid token:', err.message);
  } else if (err instanceof jwt.NotBeforeError) {
    console.error('Token not yet active');
  }
}

// ─── Decode without verifying (for debugging only) ───────────────────────────
const payload = jwt.decode(token);
console.log(payload);   // DO NOT use this for auth — signature is not verified

The expiresIn option accepts human-readable strings ('15m', '7d', '1h') or numbers in seconds. The algorithms array in verify() is critical — without it, some older library versions accept the alg:none attack.

JWT with Express Middleware

A well-structured Express JWT middleware extracts the Bearer token, verifies it, attaches the decoded payload to the request, and handles errors consistently.

// middleware/auth.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

export interface AuthRequest extends Request {
  user?: jwt.JwtPayload;
}

export function authenticate(req: AuthRequest, res: Response, next: NextFunction) {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing or malformed Authorization header' });
  }

  const token = authHeader.slice(7);  // Remove "Bearer " prefix

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET!, {
      algorithms: ['HS256'],
      issuer: 'api.example.com',
    }) as jwt.JwtPayload;

    req.user = decoded;
    next();
  } catch (err) {
    if (err instanceof jwt.TokenExpiredError) {
      return res.status(401).json({ error: 'Token expired', code: 'TOKEN_EXPIRED' });
    }
    return res.status(401).json({ error: 'Invalid token', code: 'INVALID_TOKEN' });
  }
}

// Optional: role-based authorization middleware
export function authorize(...roles: string[]) {
  return (req: AuthRequest, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Not authenticated' });
    }
    if (roles.length && !roles.includes(req.user.role as string)) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    next();
  };
}

// Apply middleware to protected routes
router.get('/profile', authenticate, (req: AuthRequest, res) => {
  res.json({ user: req.user });
});

router.delete('/users/:id', authenticate, authorize('admin'), (req, res) => {
  res.json({ deleted: req.params.id });
});

Refresh Token Endpoints in Express

// POST /auth/login — issue both tokens
app.post('/auth/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await validateCredentials(email, password);
  if (!user) return res.status(401).json({ error: 'Invalid credentials' });

  const accessToken = jwt.sign(
    { sub: user.id, email: user.email, role: user.role },
    process.env.JWT_SECRET!,
    { expiresIn: '15m', algorithm: 'HS256' }
  );

  const refreshToken = jwt.sign(
    { sub: user.id, type: 'refresh' },
    process.env.REFRESH_SECRET!,
    { expiresIn: '7d', algorithm: 'HS256' }
  );

  // Store refresh token in HttpOnly cookie
  res.cookie('refreshToken', refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 7 * 24 * 60 * 60 * 1000,
  });

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

// POST /auth/refresh — issue new access token from refresh token
app.post('/auth/refresh', async (req, res) => {
  const refreshToken = req.cookies.refreshToken;
  if (!refreshToken) return res.status(401).json({ error: 'No refresh token' });

  try {
    const decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET!, {
      algorithms: ['HS256'],
    }) as jwt.JwtPayload;

    const newAccessToken = jwt.sign(
      { sub: decoded.sub },
      process.env.JWT_SECRET!,
      { expiresIn: '15m', algorithm: 'HS256' }
    );

    res.json({ accessToken: newAccessToken });
  } catch {
    res.clearCookie('refreshToken');
    res.status(401).json({ error: 'Invalid or expired refresh token' });
  }
});

Next.js App Router JWT Authentication

Next.js 13+ App Router provides multiple integration points for JWT: server actions, route handlers, and middleware. The recommended approach stores JWTs in HttpOnly cookies and validates them in middleware.ts.

middleware.ts — Protecting Routes at the Edge

// middleware.ts (project root)
import { NextRequest, NextResponse } from 'next/server';
import { jwtVerify } from 'jose';    // 'jose' is edge-compatible; jsonwebtoken is not

const SECRET = new TextEncoder().encode(process.env.JWT_SECRET!);

export async function middleware(request: NextRequest) {
  const token = request.cookies.get('accessToken')?.value;

  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  try {
    await jwtVerify(token, SECRET, {
      algorithms: ['HS256'],
      issuer: 'api.example.com',
    });
    return NextResponse.next();
  } catch {
    const response = NextResponse.redirect(new URL('/login', request.url));
    response.cookies.delete('accessToken');
    return response;
  }
}

export const config = {
  matcher: ['/dashboard/:path*', '/profile/:path*', '/admin/:path*'],
};

Server Action Login with Cookies

// app/actions/auth.ts
'use server';

import { cookies } from 'next/headers';
import { SignJWT, jwtVerify } from 'jose';
import { redirect } from 'next/navigation';

const SECRET = new TextEncoder().encode(process.env.JWT_SECRET!);

export async function loginAction(formData: FormData) {
  const email = formData.get('email') as string;
  const password = formData.get('password') as string;

  const user = await validateUser(email, password);
  if (!user) throw new Error('Invalid credentials');

  const token = await new SignJWT({ sub: user.id, email: user.email, role: user.role })
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setIssuer('api.example.com')
    .setExpirationTime('15m')
    .sign(SECRET);

  cookies().set('accessToken', token, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict',
    maxAge: 15 * 60,
    path: '/',
  });

  redirect('/dashboard');
}

export async function getCurrentUser() {
  const token = cookies().get('accessToken')?.value;
  if (!token) return null;

  try {
    const { payload } = await jwtVerify(token, SECRET, { algorithms: ['HS256'] });
    return payload;
  } catch {
    return null;
  }
}

Python PyJWT and FastAPI JWT Integration

pip install PyJWT cryptography
import jwt
from datetime import datetime, timedelta, timezone

SECRET_KEY = "your-super-secret-key-at-least-32-chars"
ALGORITHM = "HS256"

def create_access_token(user_id: str, role: str) -> str:
    payload = {
        "sub": user_id,
        "role": role,
        "iat": datetime.now(timezone.utc),
        "exp": datetime.now(timezone.utc) + timedelta(minutes=15),
        "iss": "api.example.com",
    }
    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

def verify_token(token: str) -> dict:
    try:
        return jwt.decode(
            token,
            SECRET_KEY,
            algorithms=[ALGORITHM],      # always specify algorithms
            options={"require": ["exp", "sub", "iss"]},
            issuer="api.example.com",
        )
    except jwt.ExpiredSignatureError:
        raise ValueError("Token has expired")
    except jwt.InvalidTokenError as e:
        raise ValueError(f"Invalid token: {e}")

FastAPI Dependency Injection for JWT Auth

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt

app = FastAPI()
security = HTTPBearer()

def get_current_user(
    credentials: HTTPAuthorizationCredentials = Depends(security),
) -> dict:
    token = credentials.credentials
    try:
        return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM], issuer="api.example.com")
    except jwt.ExpiredSignatureError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Token expired",
            headers={"WWW-Authenticate": "Bearer"},
        )
    except jwt.InvalidTokenError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token",
            headers={"WWW-Authenticate": "Bearer"},
        )

@app.get("/profile")
def get_profile(current_user: dict = Depends(get_current_user)):
    return {"user_id": current_user["sub"], "role": current_user["role"]}

def require_admin(current_user: dict = Depends(get_current_user)) -> dict:
    if current_user.get("role") != "admin":
        raise HTTPException(status_code=403, detail="Admin access required")
    return current_user

@app.delete("/users/{user_id}")
def delete_user(user_id: str, _: dict = Depends(require_admin)):
    return {"deleted": user_id}

JWT Refresh Token Pattern — Access + Refresh Token Architecture

Short-lived access tokens combined with long-lived refresh tokens provide the best balance of security and user experience.

Token TypeLifetimeStoragePurpose
Access Token15 minutesMemory / HttpOnly cookieSent with every API request
Refresh Token7 daysHttpOnly Secure cookie onlyObtains new access tokens
// Redis-backed refresh token rotation
import { createClient } from 'redis';
import jwt from 'jsonwebtoken';

const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();

async function issueTokens(userId: string) {
  const accessToken = jwt.sign({ sub: userId }, process.env.JWT_SECRET!, {
    expiresIn: '15m', algorithm: 'HS256',
  });

  const refreshTokenId = crypto.randomUUID();
  const refreshToken = jwt.sign(
    { sub: userId, jti: refreshTokenId, type: 'refresh' },
    process.env.REFRESH_SECRET!,
    { expiresIn: '7d', algorithm: 'HS256' }
  );

  await redis.setEx(`refresh:${userId}:${refreshTokenId}`, 7 * 24 * 60 * 60, 'valid');
  return { accessToken, refreshToken };
}

async function rotateRefreshToken(refreshToken: string) {
  const decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET!, {
    algorithms: ['HS256'],
  }) as jwt.JwtPayload;

  const key = `refresh:${decoded.sub}:${decoded.jti}`;
  const exists = await redis.get(key);

  if (!exists) {
    // Reuse detected — possible theft; invalidate all sessions
    await redis.del(await redis.keys(`refresh:${decoded.sub}:*`));
    throw new Error('Refresh token reuse detected');
  }

  await redis.del(key);
  return issueTokens(decoded.sub as string);
}

RS256 Asymmetric JWT — Private Key Signing, Public Key Verification

RS256 uses an RSA key pair: the private key signs tokens (kept secret on the auth server only), and the public key verifies them (can be distributed freely to all services).

# Generate RSA key pair (4096-bit recommended)
openssl genrsa -out private.pem 4096
openssl rsa -in private.pem -pubout -out public.pem
import fs from 'fs';
import jwt from 'jsonwebtoken';

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

// Auth server: sign with private key
const token = jwt.sign(
  { sub: 'user_123', role: 'editor' },
  privateKey,
  {
    algorithm: 'RS256',
    expiresIn: '15m',
    keyid: 'key-2024-01',
    issuer: 'auth.example.com',
  }
);

// Any service: verify with public key
const decoded = jwt.verify(token, publicKey, {
  algorithms: ['RS256'],    // NEVER allow HS256 here — algorithm confusion attack!
  issuer: 'auth.example.com',
});

// JWKS endpoint — serve public keys for auto-discovery
import { createPublicKey } from 'crypto';
import { exportJWK } from 'jose';

app.get('/.well-known/jwks.json', async (req, res) => {
  const publicKeyObj = createPublicKey(publicKey);
  const jwk = await exportJWK(publicKeyObj);
  res.json({ keys: [{ ...jwk, kid: 'key-2024-01', use: 'sig', alg: 'RS256' }] });
});

// Consuming service: auto-fetch key from JWKS
import jwksClient from 'jwks-rsa';

const client = jwksClient({
  jwksUri: 'https://auth.example.com/.well-known/jwks.json',
  cache: true,
  cacheMaxAge: 600000,
});

function getPublicKey(header: jwt.JwtHeader, callback: jwt.SigningKeyCallback) {
  client.getSigningKey(header.kid, (err, key) => {
    callback(err, key?.getPublicKey());
  });
}

jwt.verify(token, getPublicKey, { algorithms: ['RS256'] }, (err, decoded) => {
  if (err) console.error('Verification failed:', err.message);
  else console.log('Decoded:', decoded);
});

Common JWT Vulnerabilities and How to Prevent Them

1. Algorithm Confusion / alg:none Attack

// VULNERABLE: not specifying algorithms allows alg:none and RS256->HS256 confusion
jwt.verify(token, secret);

// RS256->HS256 confusion: attacker changes header alg to HS256,
// signs with the PUBLIC key (which is known). If the verifier uses
// the public key as an HS256 secret, verification passes!

// PREVENTION: always pass the algorithms array
jwt.verify(token, publicKey, { algorithms: ['RS256'] });   // RS256 only
jwt.verify(token, secret, { algorithms: ['HS256'] });       // HS256 only

2. Weak Secret Brute Force

# HS256 tokens can be brute-forced offline — attacker needs only the token
hashcat -a 0 -m 16500 token.txt wordlist.txt

# Bad: short or dictionary-based secrets
JWT_SECRET=secret          # cracked in seconds
JWT_SECRET=myapp2024       # cracked in minutes

# Good: cryptographically random 256-bit secret
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# → e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

3. Missing Expiry and Sensitive Data in Payload

// VULNERABLE: no expiry — stolen tokens are valid forever
const token = jwt.sign({ sub: userId }, secret);

// VULNERABLE: sensitive data in payload (base64url is NOT encryption!)
const token = jwt.sign({ password: 'abc123', creditCard: '4111...' }, secret);

// CORRECT: set expiry, store only minimal non-sensitive claims
const token = jwt.sign(
  { sub: user.id, role: user.role, scope: ['read', 'write'] },
  secret,
  { expiresIn: '15m' }
);

// For truly sensitive payloads: use JWE (JSON Web Encryption)
// JWE encrypts the payload with AES-256-GCM in addition to signing

4. Token Fixation and Missing Audience Validation

// VULNERABLE: token issued for one service accepted by another
// (token fixation — a token for service A works on service B)

// PREVENTION: always set and validate the audience claim
const token = jwt.sign({ sub: userId }, secret, {
  audience: 'api.service-a.com',   // set intended audience
  issuer: 'auth.example.com',
});

jwt.verify(token, secret, {
  algorithms: ['HS256'],
  audience: 'api.service-a.com',   // reject tokens for other services
  issuer: 'auth.example.com',
});

JWT vs Session-Based Authentication — When to Use Each

AspectJWT (Stateless)Sessions (Stateful)
Server storageNone — token is self-containedRequires DB/Redis per session
Horizontal scalingEasy — any server can verifyRequires sticky sessions or shared store
Instant logout/revocationHard — requires blocklist in RedisEasy — delete session record
MicroservicesExcellent — no shared state neededDifficult — requires shared session store
Token/cookie sizeLarge (hundreds of bytes per request)Small (session ID only)
CSRF riskLow (Authorization header bypasses CSRF)Higher — needs explicit CSRF tokens

Use JWT when: building microservices, mobile APIs, or multi-service architectures where multiple independent services need to verify tokens without a shared database. JWT is also the natural choice for OAuth2 and OpenID Connect.

Use sessions when: building a traditional server-rendered web app where instant logout is critical, or when you need to store large amounts of user state server-side. Sessions are simpler to reason about for monolithic applications.

OAuth2 and OpenID Connect — id_token vs access_token, PKCE, and Providers

OAuth2 is an authorization framework; OpenID Connect (OIDC) adds an identity layer on top. Both use JWTs extensively. Understanding the difference between id_token and access_token is critical for correct implementation.

TokenPurposeAudienceContains
id_tokenAuthentication (who the user is)Your application onlyUser identity claims (email, name, sub)
access_tokenAuthorization (what can be accessed)Resource server (API)Scopes, permissions

Authorization Code + PKCE Flow (SPAs and Mobile)

// Step 1: Generate PKCE code verifier and challenge
const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto
  .createHash('sha256')
  .update(codeVerifier)
  .digest('base64url');

// Step 2: Redirect to authorization URL
const authUrl = new URL('https://auth.example.com/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', CLIENT_ID);
authUrl.searchParams.set('redirect_uri', 'https://app.example.com/callback');
authUrl.searchParams.set('scope', 'openid email profile');
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('state', crypto.randomBytes(16).toString('hex'));

// Step 3: Exchange authorization code for tokens
const tokenResponse = await fetch('https://auth.example.com/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: authorizationCode,
    redirect_uri: 'https://app.example.com/callback',
    client_id: CLIENT_ID,
    code_verifier: codeVerifier,
  }),
});

const { access_token, id_token, refresh_token } = await tokenResponse.json();

// Step 4: Verify id_token via JWKS
import { jwtVerify, createRemoteJWKSet } from 'jose';

const JWKS = createRemoteJWKSet(
  new URL('https://auth.example.com/.well-known/jwks.json')
);

const { payload } = await jwtVerify(id_token, JWKS, {
  issuer: 'https://auth.example.com',
  audience: CLIENT_ID,
});
console.log(payload.sub, payload.email);

Auth0, Keycloak, and Supabase Integrations

// ─── Auth0 with Next.js ───────────────────────────────────────────────────────
npm install @auth0/nextjs-auth0

// app/api/auth/[auth0]/route.ts
import { handleAuth } from '@auth0/nextjs-auth0';
export const GET = handleAuth();

// Protected server component
import { getSession } from '@auth0/nextjs-auth0';
const session = await getSession();
// session.accessToken is the OAuth2 access_token (for calling APIs)
// session.user comes from the id_token claims

// ─── Supabase Auth (Next.js App Router) ──────────────────────────────────────
npm install @supabase/ssr @supabase/supabase-js

import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';

const supabase = createServerClient(URL, ANON_KEY, {
  cookies: { getAll: () => cookies().getAll() }
});

const { data: { user } } = await supabase.auth.getUser();
// Supabase auto-handles JWT issuance, verification, and refresh

// ─── Keycloak JWT verification ────────────────────────────────────────────────
import { createRemoteJWKSet, jwtVerify } from 'jose';

const JWKS = createRemoteJWKSet(
  new URL('https://keycloak.example.com/realms/myrealm/protocol/openid-connect/certs')
);

const { payload } = await jwtVerify(token, JWKS, {
  issuer: 'https://keycloak.example.com/realms/myrealm',
  audience: 'my-client-id',
});

Decode and inspect your JWTs instantly — paste any token into our online JWT decoder to view the header, payload, claims, expiry, and algorithm without any external tools.

Key Takeaways

  • JWT = header.payload.signature: base64url-encoded, not encrypted — never put sensitive data in claims.
  • Always set exp: tokens without expiry are valid forever — a critical security risk.
  • Always specify algorithms: pass { algorithms: ['HS256'] } to jwt.verify() to prevent alg:none and algorithm confusion attacks.
  • Use RS256 for microservices: distribute the public key freely; keep the private key only on the auth server. Expose keys via a JWKS endpoint.
  • Short-lived access tokens + refresh tokens: 15-minute access tokens with 7-day rotating refresh tokens stored in HttpOnly cookies.
  • Never store JWTs in localStorage: XSS attacks can steal them. Use HttpOnly cookies or in-memory storage.
  • Redis for revocation: track refresh token JTIs in Redis with TTL for instant logout and reuse detection.
  • Use PKCE for SPAs: the authorization code + PKCE flow is the secure standard for browser-based OAuth2 clients.
  • id_token vs access_token: id_token authenticates the user to your app; access_token authorizes calls to APIs.
  • Managed providers (Auth0, Supabase, Keycloak) handle key rotation, JWKS, and token refresh automatically — prefer them for production.
𝕏 Twitterin LinkedIn
Apakah ini membantu?

Tetap Update

Dapatkan tips dev mingguan dan tool baru.

Tanpa spam. Berhenti kapan saja.

Coba Alat Terkait

JWTJWT Decoder🔒Bcrypt Hash GeneratorB64Base64 Encoder/Decoder#Hash Generator

Artikel Terkait

Cara Kerja JWT: Panduan Lengkap JSON Web Tokens

Pelajari cara kerja autentikasi JWT dengan header, payload, dan signature.

Autentikasi API: OAuth 2.0 vs JWT vs API Key

Bandingkan metode autentikasi API: OAuth 2.0, token JWT Bearer, dan kunci API.

bcrypt vs Argon2 vs scrypt: Password Hashing 2026

Bandingkan bcrypt, Argon2id, dan scrypt.