Practical Guide to Login Security
Building a login feature is easy, but making it secure requires extra attention. This article summarizes the security layers (Defense in Depth) from Backend, Frontend, to Infrastructure (OS & Web Server).
1What should the Backend do?
Backend is the data manager (Data Layer). If our backend is easily breached, no matter how advanced other systems are, they will be useless. Here is the mandatory checklist for backend security:
1. Never Store Plain Passwords
Must change the password into an unreadable format (hashing). Use slow algorithms like Bcrypt or Argon2 so hackers have a hard time cracking it even if the database leaks.
2. Limit Login Attempts
Prevent Brute Force attacks by applying Rate Limiting. If failed 5 consecutive times, lockout the IP access for 5-15 minutes.
3. Neutral Error Messages
If the user enters the wrong password or the email is not registered, give a general message like "Invalid Email or Password". Don't be specific like "Email not found" to prevent hackers from guessing the active user list.
4. Short Token Lifespan
If using JWT, set a fast expiration time (e.g., 15 minutes). To keep the user logged in without disrupting UX, use a Refresh Token system in the background.
2Preventing Cross-User Data Access (IDOR)
Ever heard of a case where User A is logged in, but they can view or modify User B's profile just by changing the ID in the URL (e.g., /api/users/1 changed to /api/users/2)? This is called IDOR (Insecure Direct Object Reference). Successful login (Authentication) means nothing if we don't check access permission (Authorization).
Always Check Data Ownership
Every time there is a request from Frontend to fetch, edit, or delete data, the backend must check whether the requested data belongs to the currently logged-in user. Never trust the ID parameters sent by Frontend 100%.
Use UUID / ULID
Instead of using sequential numeric IDs like 1, 2, 3 (which are very easy to guess), use UUID (e.g., a1b2c3d4-e5f6...). Although UUID is not the only way to secure data, it will make it very difficult for hackers trying to guess other users' data.
3Implementation Across Frameworks
What does the code look like to implement the security measures above (Hashing, Rate Limiting, & IDOR Prevention)? Check out the comparison:
Laravel has an incredibly mature ecosystem. We use RateLimiter for login, and use Policies to elegantly prevent IDOR.
// 1. Login & Rate Limiting (AuthController.php)
public function login(Request $request) {
$ip = $request->ip();
if (RateLimiter::tooManyAttempts($ip, 5)) {
return response()->json(['message' => 'Tunggu beberapa menit'], 429);
}
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
RateLimiter::hit($ip);
return response()->json(['message' => 'Kredensial salah'], 401);
}
RateLimiter::clear($ip);
// Kembalikan HttpOnly Cookie...
}
// 2. Mencegah IDOR menggunakan "Policy" (ProfilePolicy.php)
public function update(User $user, Profile $profile) {
// Return true jika user ini adalah pemilik dari profil tersebut
return $user->id === $profile->user_id;
}
// ProfileController.php
public function updateProfile(Request $request, Profile $profile) {
// Jika policy gagal (false), otomatis return 403 Forbidden!
$this->authorize('update', $profile);
// Jika lolos, aman untuk dilanjutkan update...
}4The Crucial Role of Frontend
The client/browser side is not just UI. This is where attacks against users often occur (like XSS & CSRF).
Store Tokens in HttpOnly Cookie, Not localStorage
Storing JWT in localStorage (or sessionStorage) makes the token highly vulnerable to theft if your web is hit by XSS (Cross-Site Scripting) attacks. The solution: Backend drops the token into an HttpOnly Cookie. This type of cookie CANNOT be read by JavaScript (including hacker scripts).
How does Frontend send this Cookie back to Backend?
The browser will send it automatically! You just need to configure the HTTP Client (e.g., Axios) on the Frontend like this:
// Konfigurasi Axios di Vue/React
import axios from 'axios';
const api = axios.create({
baseURL: 'https://api.domain-anda.com',
// SANGAT PENTING: Izinkan pengiriman kredensial (Cookie) lintas origin
withCredentials: true
});
// Tidak perlu lagi setup header "Authorization: Bearer [token]" secara manual!Must Use HTTPS (SSL/TLS)
Ensure all communication between frontend and backend is sent via HTTPS. If using standard HTTP, *passwords* and *cookies* will be sent in plaintext, making it very easy to be *sniffed* or intercepted over public Wi-Fi networks (Man-in-the-Middle Attack).
5Infrastructure Layer (OS & Web Server)
The application code (Frontend & Backend) is secure. But is the Physical/Cloud Server secure? This is the bottommost layer supporting everything.
Web Server (Nginx)
Apply Rate Limiting at the Nginx level (very efficient before requests hit Node/PHP apps) and hide your Nginx version.
# /etc/nginx/nginx.conf
http {
# 1. Sembunyikan versi Nginx di header
server_tokens off;
# 2. Setup Rate Limiting Zone (Maksimal 5 request / menit)
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;
server {
listen 443 ssl;
# 3. Paksa browser memakai HTTPS (HSTS)
add_header Strict-Transport-Security "max-age=31536000" always;
location /api/login {
# 4. Aktifkan rate limiter di endpoint login
limit_req zone=login_limit burst=5 nodelay;
proxy_pass http://backend_app;
}
}
}Sistem Operasi (Linux) / OS
Close all ports except HTTP/HTTPS. Secure SSH access by enforcing SSH Keys and disabling Root user login.
# 1. Konfigurasi Firewall UFW (Ubuntu/Debian)
$ sudo ufw default deny incoming
$ sudo ufw allow 80/tcp
$ sudo ufw allow 443/tcp
$ sudo ufw allow 22/tcp # (Port SSH)
$ sudo ufw enable
# 2. Matikan Login Root (Edit /etc/ssh/sshd_config)
PermitRootLogin no
PasswordAuthentication no # Wajib pakai SSH Key
# 3. Restart Service SSH
$ sudo systemctl restart ssh✓Login Security Checklist
- Passwords hashed using slow algorithms (Bcrypt/Argon2)
- Login Rate Limiting mechanism is applied in Backend / Web Server
- Login error messages are generic ("Invalid Email or Password")
- All data manipulation endpoints must check access permission (preventing IDOR)
- JWT / Session tokens are stored in HttpOnly Cookies
- The application is only accessible via HTTPS (SSL/TLS activated)
- OS server access (SSH) disallows root & password logins (requires SSH Key)
The Bottom Line (Defense in Depth)
Building a secure system means applying the concept of Defense in Depth. There is no single magic bullet-proof vest. We need padlocks on Infrastructure, steel doors in the Backend, and smart CCTVs in the Frontend. If one layer falls, the others will still protect your data.