BelajarKoding Logobelajarkoding

Platform belajar web development Indonesia. Artikel, cheat sheets, roadmap, dan code challenges untuk developer Indonesia.

Navigasi

  • Artikel
  • Cheat Sheets
  • Roadmap
  • Challenges
  • Pricing
  • Search

Produk Lain

  • JagoHermes
  • KelasClaude
  • KilatKoding
  • BelajarVibeCoding
  • JualanKoding

Support

  • Privacy Policy
  • Terms of Service
  • Email

© 2026 BelajarKoding. All rights reserved.

Galih PratamaBagian dari ekosistem Galih Pratama
belajarkoding LogobyGalih Pratama
RoadmapArtikelCheat SheetsChallengesUpgrade
belajarkoding LogobyGalih Pratama
RoadmapArtikelCheat SheetsChallengesUpgrade
belajarkoding LogobyGalih Pratama
RoadmapArtikelCheat SheetsChallengesUpgrade

Daftar Isi

JWT BasicsJWT OptionsSession ManagementExpress SessionCookie SecurityCookie AttributesAccess Token vs Refresh TokenAuthentication MiddlewarePassword HashingCSRF ProtectionRate LimitingInput ValidationOAuth 2.0 / Social LoginToken Blacklist (JWT Revocation)Passwordless Authentication2FA (Two-Factor Authentication)Security HeadersClient-Side (Frontend)Best PracticesPattern UmumEnvironment VariablesTesting
AuthenticationSecurityBackendJWT

JWT & Session Management Cheat Sheet

Referensi cepat JWT authentication, session management, cookies security, dan best practices. Must-have buat backend developer.

JavaScript10 min read1.989 kata
Cheat sheet ini adalah konten premium. Login atau daftar untuk mengakses konten premium.

#JWT Basics

Dasar-dasar penggunaan JSON Web Token untuk authentication dan authorization.

javascript
const jwt = require('jsonwebtoken');
 
// Generate JWT
const token = jwt.sign(
 { userId: 123, username: 'john' }, // Payload
 process.env.JWT_SECRET,       // Secret
 { expiresIn: '1h' }         // Options
);
 
// Verify JWT
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
 if (err) {
  // Invalid or expired token
 }
 console.log(decoded); // { userId: 123, username: 'john', iat: ..., exp: ... }
});
 
// Decode without verification (for debugging)
const decoded = jwt.decode(token);

#JWT Options

Konfigurasi tambahan untuk JWT seperti expiration, audience, dan algoritma.

javascript
jwt.sign(payload, secret, {
 expiresIn: '1h',     // '1h', '7d', '30m', 60 (seconds)
 notBefore: '30s',     // Token not valid before
 audience: 'api.example.com',
 issuer: 'auth.example.com',
 subject: 'user@example.com',
 jwtid: 'unique-id',
 algorithm: 'HS256'    // HS256, RS256, ES256
});

#Session Management

Cara mengelola session di server untuk menyimpan state user.

#Express Session

Implementasi session menggunakan express-session middleware.

javascript
const session = require('express-session');
 
app.use(session({
 secret: process.env.SESSION_SECRET,
 resave: false,
 saveUninitialized: false,
 cookie: {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',
  sameSite: 'strict',
  maxAge: 24 * 60 * 60 * 1000 // 24 hours
 },
 store: new RedisStore({ client: redisClient }) // Use Redis in production
}));
 
// Access session
req.session.userId = user.id;
req.session.save(); // Explicit save (if needed)
req.session.destroy(); // Logout
req.session.regenerate(); // Regenerate session ID

#Cookie Security

Praktik keamanan penting untuk cookies yang menyimpan data sensitif.

javascript
// Set cookie
res.cookie('name', 'value', {
 httpOnly: true,   // JavaScript can't access (XSS protection)
 secure: true,    // HTTPS only
 sameSite: 'strict', // CSRF protection
 maxAge: 3600000,   // Expire time (ms)
 domain: '.example.com',
 path: '/',
 signed: true    // Cryptographically signed
});
 
// Get cookie
req.cookies.name    // Unsigned
req.signedCookies.name // Signed
 
// Clear cookie
res.clearCookie('name', { path: '/' });

#Cookie Attributes

Atribut-atribut penting pada cookie untuk keamanan dan kontrol.

javascript
// httpOnly - CRITICAL for auth cookies
httpOnly: true     // JavaScript ga bisa akses
 
// secure - HTTPS only
secure: true      // Only send over HTTPS
secure: process.env.NODE_ENV === 'production'
 
// sameSite - CSRF protection
sameSite: 'strict'   // Never sent cross-site
sameSite: 'lax'    // Sent on top-level navigation (default)
sameSite: 'none'    // Always sent (requires secure: true)
 
// maxAge vs expires
maxAge: 3600000    // Relative (ms from now)
expires: new Date('2025-12-31') // Absolute date
 
// domain & path
domain: '.example.com' // Available to subdomains
path: '/api'     // Only for /api/* routes
 
// Cookie prefixes
__Secure-name     // Must have secure: true
__Host-name      // Must have secure: true, no domain, path: '/'

#Access Token vs Refresh Token

Perbedaan antara access token (pendek) dan refresh token (panjang) untuk keamanan yang lebih baik.

javascript
// Generate both tokens
function generateTokens(user) {
 const accessToken = jwt.sign(
  { userId: user.id, username: user.username },
  process.env.ACCESS_TOKEN_SECRET,
  { expiresIn: '15m' } // Short-lived
 );
 
 const refreshToken = jwt.sign(
  { userId: user.id },
  process.env.REFRESH_TOKEN_SECRET,
  { expiresIn: '7d' }  // Long-lived
 );
 
 return { accessToken, refreshToken };
}
 
// Login
app.post('/login', async (req, res) => {
 const user = await authenticateUser(req.body);
 const { accessToken, refreshToken } = generateTokens(user);
 
 // Store refresh token in database
 await db.saveRefreshToken(user.id, refreshToken);
 
 // Send refresh token as httpOnly cookie
 res.cookie('refreshToken', refreshToken, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
  maxAge: 7 * 24 * 60 * 60 * 1000
 });
 
 res.json({ accessToken });
});
 
// Refresh endpoint
app.post('/refresh', async (req, res) => {
 const refreshToken = req.cookies.refreshToken;
 
 const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
 const isValid = await db.validateRefreshToken(decoded.userId, refreshToken);
 
 if (!isValid) {
  return res.status(403).json({ error: 'Invalid refresh token' });
 }
 
 const accessToken = jwt.sign(
  { userId: decoded.userId },
  process.env.ACCESS_TOKEN_SECRET,
  { expiresIn: '15m' }
 );
 
 res.json({ accessToken });
});
 
// Logout
app.post('/logout', async (req, res) => {
 const refreshToken = req.cookies.refreshToken;
 await db.deleteRefreshToken(refreshToken);
 res.clearCookie('refreshToken');
 res.json({ message: 'Logged out' });
});

#Authentication Middleware

Middleware untuk memverifikasi authentication sebelum mengakses route yang dilindungi.

javascript
// JWT middleware
function authenticateToken(req, res, next) {
 const authHeader = req.headers['authorization'];
 const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
 
 if (!token) {
  return res.status(401).json({ error: 'No token provided' });
 }
 
 jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
  if (err) {
   return res.status(403).json({ error: 'Invalid token' });
  }
  req.user = user;
  next();
 });
}
 
// Session middleware
function requireAuth(req, res, next) {
 if (!req.session.userId) {
  return res.status(401).json({ error: 'Not authenticated' });
 }
 next();
}
 
// Usage
app.get('/profile', authenticateToken, (req, res) => {
 res.json({ user: req.user });
});

#Password Hashing

Cara meng-hash password dengan aman menggunakan bcrypt atau argon2.

javascript
const bcrypt = require('bcrypt');
 
// Hash password
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(password, saltRounds);
 
// Verify password
const isValid = await bcrypt.compare(password, hashedPassword);
 
// Argon2 (recommended, more secure)
const argon2 = require('argon2');
 
const hash = await argon2.hash(password);
const isValid = await argon2.verify(hash, password);

#CSRF Protection

Proteksi terhadap Cross-Site Request Forgery attacks.

javascript
const csrf = require('csurf');
 
// Setup CSRF
const csrfProtection = csrf({ cookie: true });
 
app.use(csrfProtection);
 
// Get CSRF token
app.get('/form', (req, res) => {
 res.json({ csrfToken: req.csrfToken() });
});
 
// Validate CSRF (automatic)
app.post('/submit', (req, res) => {
 // CSRF middleware validates automatically
 res.json({ message: 'Success' });
});
 
// Client-side (send in header or body)
fetch('/submit', {
 method: 'POST',
 headers: {
  'CSRF-Token': csrfToken,
  'Content-Type': 'application/json'
 },
 body: JSON.stringify(data)
});

#Rate Limiting

Membatasi jumlah request per pengguna untuk mencegah abuse dan serangan.

javascript
const rateLimit = require('express-rate-limit');
 
// General limiter
const limiter = rateLimit({
 windowMs: 15 * 60 * 1000, // 15 minutes
 max: 100, // Max requests per windowMs
 message: 'Too many requests, please try again later',
 standardHeaders: true,
 legacyHeaders: false,
});
 
// Login limiter (stricter)
const loginLimiter = rateLimit({
 windowMs: 15 * 60 * 1000,
 max: 5,
 skipSuccessfulRequests: true // Only count failed attempts
});
 
app.use('/api/', limiter);
app.post('/login', loginLimiter, loginHandler);
 
// Redis store (for distributed systems)
const RedisStore = require('rate-limit-redis');
 
const limiter = rateLimit({
 store: new RedisStore({
  client: redisClient
 }),
 max: 100
});

#Input Validation

javascript
const { body, validationResult } = require('express-validator');
 
app.post('/register',
 // Validation rules
 body('email').isEmail().normalizeEmail(),
 body('password').isLength({ min: 8 })
  .matches(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/)
  .withMessage('Must contain uppercase, lowercase, and number'),
 body('username').isAlphanumeric().isLength({ min: 3, max: 20 }),
 
 // Handler
 async (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
   return res.status(400).json({ errors: errors.array() });
  }
 
  // Process registration
 }
);

#OAuth 2.0 / Social Login

javascript
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
 
// Configure strategy
passport.use(new GoogleStrategy({
  clientID: process.env.GOOGLE_CLIENT_ID,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
  callbackURL: '/auth/google/callback'
 },
 async (accessToken, refreshToken, profile, done) => {
  const user = await db.findOrCreateUser(profile);
  done(null, user);
 }
));
 
// Routes
app.get('/auth/google',
 passport.authenticate('google', { scope: ['profile', 'email'] })
);
 
app.get('/auth/google/callback',
 passport.authenticate('google', { failureRedirect: '/login' }),
 (req, res) => {
  res.redirect('/dashboard');
 }
);

#Token Blacklist (JWT Revocation)

javascript
const redis = require('redis');
const client = redis.createClient();
 
// Blacklist token on logout
app.post('/logout', async (req, res) => {
 const token = req.headers.authorization?.split(' ')[1];
 const decoded = jwt.decode(token);
 
 // Store in Redis with expiration
 const expiresIn = decoded.exp - Math.floor(Date.now() / 1000);
 await client.setex(`blacklist:${token}`, expiresIn, 'true');
 
 res.json({ message: 'Logged out' });
});
 
// Check blacklist in middleware
async function authenticateToken(req, res, next) {
 const token = req.headers.authorization?.split(' ')[1];
 
 // Check blacklist
 const isBlacklisted = await client.get(`blacklist:${token}`);
 if (isBlacklisted) {
  return res.status(401).json({ error: 'Token revoked' });
 }
 
 // Verify token
 jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
  if (err) return res.status(403).json({ error: 'Invalid token' });
  req.user = user;
  next();
 });
}

#Passwordless Authentication

javascript
const crypto = require('crypto');
 
// Send magic link
app.post('/login/email', async (req, res) => {
 const { email } = req.body;
 
 const token = crypto.randomBytes(32).toString('hex');
 const expires = Date.now() + 15 * 60 * 1000; // 15 minutes
 
 await db.saveLoginToken(email, token, expires);
 
 const magicLink = `https://app.com/verify?token=${token}`;
 await sendEmail(email, magicLink);
 
 res.json({ message: 'Check your email' });
});
 
// Verify token
app.get('/verify', async (req, res) => {
 const { token } = req.query;
 
 const record = await db.findLoginToken(token);
 if (!record || record.expires < Date.now()) {
  return res.status(401).json({ error: 'Invalid or expired token' });
 }
 
 // Create session
 req.session.userId = record.userId;
 await db.deleteLoginToken(token);
 
 res.redirect('/dashboard');
});

#2FA (Two-Factor Authentication)

javascript
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');
 
// Generate secret
app.post('/2fa/setup', async (req, res) => {
 const secret = speakeasy.generateSecret({
  name: `MyApp (${req.user.email})`
 });
 
 // Save secret to database
 await db.save2FASecret(req.user.id, secret.base32);
 
 // Generate QR code
 const qrCodeUrl = await QRCode.toDataURL(secret.otpauth_url);
 
 res.json({ secret: secret.base32, qrCode: qrCodeUrl });
});
 
// Verify 2FA code
app.post('/2fa/verify', async (req, res) => {
 const { token } = req.body;
 const user = await db.findUser(req.user.id);
 
 const verified = speakeasy.totp.verify({
  secret: user.twoFactorSecret,
  encoding: 'base32',
  token: token,
  window: 2 // Allow 2 time steps (±60 seconds)
 });
 
 if (!verified) {
  return res.status(401).json({ error: 'Invalid code' });
 }
 
 // Enable 2FA
 await db.enable2FA(req.user.id);
 res.json({ message: '2FA enabled' });
});

#Security Headers

javascript
const helmet = require('helmet');
 
// Use helmet (sets multiple security headers)
app.use(helmet());
 
// Or manually
app.use((req, res, next) => {
 // Prevent clickjacking
 res.setHeader('X-Frame-Options', 'DENY');
 
 // Prevent MIME sniffing
 res.setHeader('X-Content-Type-Options', 'nosniff');
 
 // XSS protection
 res.setHeader('X-XSS-Protection', '1; mode=block');
 
 // HTTPS only
 res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
 
 // Content Security Policy
 res.setHeader('Content-Security-Policy', "default-src 'self'");
 
 next();
});

#Client-Side (Frontend)

javascript
// Store access token in memory (React)
const [accessToken, setAccessToken] = useState(null);
 
// Login
async function login(credentials) {
 const response = await fetch('/api/login', {
  method: 'POST',
  credentials: 'include', // Send cookies
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(credentials)
 });
 
 const { accessToken } = await response.json();
 setAccessToken(accessToken); // Store in memory
}
 
// Use token in requests
async function fetchProfile() {
 const response = await fetch('/api/profile', {
  headers: {
   'Authorization': `Bearer ${accessToken}`
  }
 });
 return response.json();
}
 
// Refresh token automatically
async function refreshAccessToken() {
 const response = await fetch('/api/refresh', {
  method: 'POST',
  credentials: 'include' // Send refresh token cookie
 });
 
 const { accessToken } = await response.json();
 setAccessToken(accessToken);
 return accessToken;
}
 
// Axios interceptor (auto refresh)
axios.interceptors.response.use(
 (response) => response,
 async (error) => {
  if (error.response?.status === 401) {
   const newToken = await refreshAccessToken();
   error.config.headers.Authorization = `Bearer ${newToken}`;
   return axios.request(error.config);
  }
  return Promise.reject(error);
 }
);

#Best Practices

javascript
// DO
- Use HTTPS in production
- httpOnly cookies for refresh tokens
- Short-lived access tokens (5-15 min)
- Long-lived refresh tokens (7-30 days)
- Strong, random secrets (crypto.randomBytes(64))
- Hash passwords with bcrypt/argon2
- Validate all inputs
- Rate limit auth endpoints
- Use CSRF protection
- Implement 2FA for sensitive operations
 
// DON'T
- Store JWT in localStorage (XSS vulnerable)
- Put sensitive data in JWT payload
- Use weak secrets (e.g., "secret")
- Skip token expiration
- Trust client-side data
- Use GET for logout
- Store passwords in plain text
- Expose detailed error messages
- Allow unlimited login attempts
- Use cookies without httpOnly/secure/sameSite

#Pattern Umum

javascript
// Login flow
POST /login
→ Verify credentials
→ Generate access + refresh tokens
→ Store refresh token in DB
→ Send refresh token as httpOnly cookie
→ Return access token in response body
 
// Protected request
GET /api/resource
Headers: Authorization: Bearer <access-token>
→ Verify access token
→ If valid, process request
→ If expired, return 401
 
// Refresh flow
POST /refresh
Cookies: refreshToken=<refresh-token>
→ Verify refresh token
→ Check DB for validity
→ Generate new access token
→ Return new access token
 
// Logout flow
POST /logout
→ Delete refresh token from DB
→ Clear refresh token cookie
→ (Optional) Blacklist access token

#Environment Variables

bash
# .env file
JWT_SECRET=your-256-bit-secret-here
REFRESH_TOKEN_SECRET=different-256-bit-secret
SESSION_SECRET=session-secret-256-bit
 
ACCESS_TOKEN_EXPIRES=15m
REFRESH_TOKEN_EXPIRES=7d
 
BCRYPT_ROUNDS=10
 
NODE_ENV=production

#Testing

javascript
// Jest example
describe('Authentication', () => {
 let token;
 
 test('should register new user', async () => {
  const res = await request(app)
   .post('/register')
   .send({ email: 'test@test.com', password: 'Password123!' });
 
  expect(res.status).toBe(201);
 });
 
 test('should login', async () => {
  const res = await request(app)
   .post('/login')
   .send({ email: 'test@test.com', password: 'Password123!' });
 
  expect(res.status).toBe(200);
  expect(res.body).toHaveProperty('accessToken');
  token = res.body.accessToken;
 });
 
 test('should access protected route with token', async () => {
  const res = await request(app)
   .get('/api/profile')
   .set('Authorization', `Bearer ${token}`);
 
  expect(res.status).toBe(200);
 });
 
 test('should reject without token', async () => {
  const res = await request(app).get('/api/profile');
  expect(res.status).toBe(401);
 });
 
 test('should logout', async () => {
  const res = await request(app)
   .post('/logout')
   .set('Authorization', `Bearer ${token}`);
 
  expect(res.status).toBe(200);
 });
});

Baca Cheat Sheet Lengkap

Login untuk mengakses konten premium ini.

LoginDaftar Gratis
Share: