Gejala Auth Storm pada Backend
Insiden bermula ketika access token kedaluwarsa secara serentak pada ribuan perangkat aktif. Mobile app React Native memuat halaman beranda yang menembak 5 hingga 8 endpoint API paralel (profil, notifikasi, saldo, banner, dan transaksi). Setiap request mengembalikan HTTP 401 Unauthorized dalam jendela waktu milidetik yang sama.
Dampak langsung pada infrastruktur backend meliputi:
- CPU Spike pada Auth Microservice: Utilisasi CPU melonjak hingga 100% akibat tingginya beban parsing JWT, verifikasi signature kriptografis, dan hashing.
- Connection Pool Exhaustion: Lonjakan tajam koneksi ke Redis session store dan database relasional. Connection pool terlampaui sehingga request non-auth ikut mengalami starvation dan timeout.
- Banjir HTTP 429 (Too Many Requests): Rate limiter auth gateway memblokir traffic legitimate, memicu status 429 massal dan kegagalan login bagi pengguna aktif.
Analisis Akar Masalah: Interceptor Tanpa Mutex
Penyebab utama berada pada HTTP interceptor di sisi React Native. Saat 6 request paralel menerima HTTP 401, response interceptor mengeksekusi fungsi refresh token secara independen sebanyak 6 kali dengan payload refresh token yang sama.
Jika backend menerapkan Refresh Token Rotation ketat tanpa toleransi race condition, request refresh pertama berhasil dan langsung menginvaliasi token lama. Lima request refresh lainnya yang tiba beberapa milidetik kemudian ditolak backend karena dianggap sebagai percobaan pencurian token (token reuse attempt). Akibatnya, sesi pengguna diputus paksa (logout mendadak) atau client terjebak dalam infinite retry loop.
Solusi Client: Singleton Promise dan Request Queue
Klien membutuhkan mekanisme locking agar proses refresh hanya dieksekusi satu kali untuk beberapa panggilan 401 paralel. Request lain harus ditahan di antrean (queue) hingga token baru tersedia.
import axios from 'axios';
const apiClient = axios.create({
baseURL: 'https://api.domain.com/v1',
});
let isRefreshing = false;
let failedQueue = [];
const processQueue = (error, token = null) => {
failedQueue.forEach((prom) => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
failedQueue = [];
};
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject });
})
.then((token) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
return apiClient(originalRequest);
})
.catch((err) => Promise.reject(err));
}
originalRequest._retry = true;
isRefreshing = true;
try {
const refreshToken = await getSecureStorageItem('refreshToken');
const response = await axios.post('https://api.domain.com/v1/auth/refresh', {
refreshToken,
});
const { accessToken, refreshToken: newRefreshToken } = response.data;
await setSecureStorageItem('accessToken', accessToken);
await setSecureStorageItem('refreshToken', newRefreshToken);
apiClient.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
processQueue(null, accessToken);
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
return apiClient(originalRequest);
} catch (refreshError) {
processQueue(refreshError, null);
await clearAuthSession();
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
}
}
return Promise.reject(error);
}
);
Solusi Backend: Token Rotation Grace Period
Perbaikan client tidak sepenuhnya menyelesaikan masalah di jaringan mobile dengan latensi tinggi (packet reordering atau koneksi fluktuatif). Backend harus mendukung Grace Period pada deteksi token reuse.
Ketika refresh token diputar (rotated), simpan token lama di Redis selama 15-30 detik dengan pointer ke pasangan token yang baru. Jika token lama dikirim ulang dalam rentang grace period, backend mengembalikan token aktif yang sama alih-alih menghapus sesi pengguna.
// Contoh implementasi verifikasi refresh token di Node.js/Redis
async function handleTokenRefresh(providedToken) {
const tokenKey = `refresh_token:${providedToken}`;
const tokenData = await redis.get(tokenKey);
if (!tokenData) {
throw new Error('INVALID_TOKEN');
}
const session = JSON.parse(tokenData);
// Status: Token sudah diputar, cek batas grace period
if (session.status === 'ROTATED') {
const now = Date.now();
const rotatedAt = session.rotatedAt;
const GRACE_PERIOD_MS = 15000; // 15 detik
if (now - rotatedAt <= GRACE_PERIOD_MS) {
// Kembalikan token aktif yang sudah dihasilkan sebelumnya
return {
accessToken: session.replacedByAccessToken,
refreshToken: session.replacedByRefreshToken,
};
}
// Reuse terdeteksi di luar grace period: potensi serangan!
await revokeAllFamilyTokens(session.familyId);
throw new Error('TOKEN_REUSE_DETECTED');
}
// Token valid dan belum diputar: generate token baru
const newAccessToken = generateAccessToken(session.userId);
const newRefreshToken = generateSecureId();
// Update token lama sebagai ROTATED dengan TTL grace period
const updatedOldToken = {
...session,
status: 'ROTATED',
rotatedAt: Date.now(),
replacedByAccessToken: newAccessToken,
replacedByRefreshToken: newRefreshToken,
};
// Simpan record rotasi selama 30 detik sebelum dihapus permanen
await redis.set(tokenKey, JSON.stringify(updatedOldToken), 'EX', 30);
// Simpan record token baru
const newTokenData = {
userId: session.userId,
familyId: session.familyId,
status: 'ACTIVE',
};
await redis.set(`refresh_token:${newRefreshToken}`, JSON.stringify(newTokenData), 'EX', 604800); // 7 hari
return { accessToken: newAccessToken, refreshToken: newRefreshToken };
}
Verifikasi Pasca-Patch
Evaluasi metrik observabilitas setelah patch client dan backend diterapkan:
- Volume POST /auth/refresh: Berkurang hingga 78% di microservice telemetry.
- Error Rate 429 & 401: Tingkat error 401 dan 429 kembali stabil di bawah 0.05% dari total traffic.
- Kesehatan Session Store: Beban koneksi pool Redis turun ke batas normal tanpa memory spikes.
Kombinasi queueing interceptor di React Native dan mekanisme grace period di backend mencegah auth storm tanpa menurunkan postur keamanan autentikasi berbasis rotasi token.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!