🔐 Modul 3: Sistem Login & Session

Membuat login yang aman dengan password_hash dan session PHP

1. Alur Login yang Benar

Sebelum coding, pahami dulu alur login yang akan kita buat:

👤
1. User isi form
username & password
🔍
2. Cari di DB
SELECT WHERE username
🔑
3. Verifikasi hash
password_verify()
💾
4. Simpan session
$_SESSION['user_id']
🏠
5. Redirect
ke dashboard
💡 Tentang password_hash:
Password TIDAK pernah disimpan dalam bentuk asli di database. Kita menyimpan hash-nya menggunakan password_hash(). Untuk verifikasi, kita pakai password_verify() yang membandingkan password asli dengan hash.
password_hash('admin123', PASSWORD_BCRYPT)
// → '$2y$10$abc123...' (hash, beda setiap kali!)

password_verify('admin123', '$2y$10$abc123...')
// → true ✅

password_verify('salah', '$2y$10$abc123...')
// → false ❌
2. Halaman Login: auth/login.php
📄 auth/login.php HTML + PHP
<?php
// ============================================
// FILE: auth/login.php
// Halaman login sistem POS
// ============================================

session_start(); // Wajib di awal sebelum pakai $_SESSION

// Jika sudah login, langsung ke dashboard
if (isset($_SESSION['user_id'])) {
    header('Location: ../dashboard.php');
    exit();
}

// Inisialisasi variabel
$error = '';
$username_input = ''; // Untuk mengisi ulang form jika error

// ============================================
// PROSES LOGIN (saat form di-submit)
// ============================================
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    
    // Ambil & bersihkan input
    $username = trim($_POST['username'] ?? '');
    $password = $_POST['password'] ?? '';
    $username_input = htmlspecialchars($username);
    
    // Validasi: pastikan tidak kosong
    if (empty($username) || empty($password)) {
        $error = 'Username dan password harus diisi!';
    } else {
        // Koneksi database
        require_once '../config/database.php';
        
        // Cari user berdasarkan username
        // Gunakan Prepared Statement untuk keamanan!
        $stmt = $pdo->prepare(
            "SELECT * FROM users WHERE username = ? LIMIT 1"
        );
        $stmt->execute([$username]);
        $user = $stmt->fetch();
        
        // Verifikasi: user ada DAN password cocok
        if ($user && password_verify($password, $user['password'])) {
            
            // Login berhasil! Simpan data ke session
            $_SESSION['user_id']   = $user['id'];
            $_SESSION['user_nama'] = $user['nama'];
            $_SESSION['user_role'] = $user['role'];
            
            // Regenerate session ID untuk keamanan
            // (mencegah Session Fixation Attack)
            session_regenerate_id(true);
            
            // Redirect ke dashboard
            header('Location: ../dashboard.php');
            exit();
            
        } else {
            // Login gagal
            $error = 'Username atau password salah!';
            // JANGAN beritahu mana yang salah (username atau password)
            // karena itu petunjuk bagi hacker!
        }
    }
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login — Sistem POS</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <style>
        body {
            background: linear-gradient(135deg, #1e3a8a 0%, #7c3aed 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
        }
        .login-card {
            border: none;
            border-radius: 20px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
        }
        .login-logo {
            width: 80px; height: 80px;
            background: linear-gradient(135deg, #2563eb, #7c3aed);
            border-radius: 20px;
            display: flex; align-items: center; justify-content: center;
            font-size: 2rem; margin: 0 auto 16px;
        }
        .form-control:focus {
            border-color: #2563eb;
            box-shadow: 0 0 0 0.25rem rgba(37, 99, 235, 0.25);
        }
        .btn-login {
            background: linear-gradient(135deg, #2563eb, #7c3aed);
            border: none; border-radius: 10px;
            padding: 12px; font-weight: 600; font-size: 1rem;
            transition: opacity 0.2s;
        }
        .btn-login:hover { opacity: 0.9; }
        .input-group-text { border-right: none; }
        .form-control { border-left: none; }
    </style>
</head>
<body>
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-5 col-lg-4">
            <div class="card login-card p-4">
                
                <!-- Logo & Judul -->
                <div class="text-center mb-4">
                    <div class="login-logo">🛒</div>
                    <h4 class="fw-bold">Sistem POS</h4>
                    <p class="text-muted small">Silakan login untuk melanjutkan</p>
                </div>
                
                <!-- Tampilkan error jika ada -->
                <?php if ($error): ?>
                    <div class="alert alert-danger py-2 small">
                        <i class="fas fa-exclamation-circle me-1"></i>
                        <?= htmlspecialchars($error) ?>
                    </div>
                <?php endif; ?>
                
                <!-- Form Login -->
                <form method="POST" action="">
                    
                    <!-- Username -->
                    <div class="mb-3">
                        <label class="form-label fw-semibold">Username</label>
                        <div class="input-group">
                            <span class="input-group-text">
                                <i class="fas fa-user text-muted"></i>
                            </span>
                            <input 
                                type="text" 
                                name="username" 
                                class="form-control" 
                                placeholder="Masukkan username"
                                value="<?= $username_input ?>"
                                required autofocus
                            >
                        </div>
                    </div>
                    
                    <!-- Password -->
                    <div class="mb-4">
                        <label class="form-label fw-semibold">Password</label>
                        <div class="input-group">
                            <span class="input-group-text">
                                <i class="fas fa-lock text-muted"></i>
                            </span>
                            <input 
                                type="password" 
                                name="password" 
                                id="passwordInput"
                                class="form-control" 
                                placeholder="Masukkan password"
                                required
                            >
                            <button 
                                type="button" 
                                class="btn btn-outline-secondary"
                                onclick="togglePassword()"
                                title="Lihat password"
                            >
                                <i class="fas fa-eye" id="eyeIcon"></i>
                            </button>
                        </div>
                    </div>
                    
                    <!-- Tombol Login -->
                    <button type="submit" class="btn btn-primary btn-login w-100 text-white">
                        <i class="fas fa-sign-in-alt me-2"></i>Login
                    </button>
                    
                </form>
                
                <!-- Info Demo -->
                <div class="text-center mt-3 p-2 bg-light rounded">
                    <small class="text-muted">
                        Demo: username <code>admin</code> / password <code>password</code>
                    </small>
                </div>
                
            </div>
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
function togglePassword() {
    const input = document.getElementById('passwordInput');
    const icon = document.getElementById('eyeIcon');
    if (input.type === 'password') {
        input.type = 'text';
        icon.classList.replace('fa-eye', 'fa-eye-slash');
    } else {
        input.type = 'password';
        icon.classList.replace('fa-eye-slash', 'fa-eye');
    }
}
</script>
</body>
</html>
4. File Logout: auth/logout.php
📄 auth/logout.php
<?php
// ============================================
// FILE: auth/logout.php
// Proses logout: hapus semua session
// ============================================

session_start();

// 1. Hapus semua data session
$_SESSION = [];

// 2. Hapus cookie session
if (ini_get("session.use_cookies")) {
    $params = session_get_cookie_params();
    setcookie(
        session_name(), '', time() - 42000,
        $params["path"], $params["domain"],
        $params["secure"], $params["httponly"]
    );
}

// 3. Hancurkan session
session_destroy();

// 4. Redirect ke halaman login
header('Location: login.php?logout=1');
exit();
?>
5. File Header & Navbar: includes/header.php
📄 includes/header.php
<?php
// ============================================
// FILE: includes/header.php
// Header yang diinclude di setiap halaman
// ============================================

// Pastikan session sudah dimulai
if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

// Cek apakah user sudah login
// Jika belum, redirect ke login
if (!isset($_SESSION['user_id'])) {
    // Simpan URL yang mau diakses (untuk redirect setelah login)
    $_SESSION['redirect_after_login'] = $_SERVER['REQUEST_URI'];
    header('Location: /pos_sederhana/auth/login.php');
    exit();
}

// Ambil data user dari session
$currentUser = [
    'id'   => $_SESSION['user_id'],
    'nama' => $_SESSION['user_nama'],
    'role' => $_SESSION['user_role'],
];

// Tentukan halaman aktif untuk navbar
$currentPage = basename($_SERVER['PHP_SELF'], '.php');
$currentDir  = basename(dirname($_SERVER['PHP_SELF']));
?>
<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?= $pageTitle ?? 'Sistem POS' ?></title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <style>
        body { background-color: #f1f5f9; }
        .navbar { background: linear-gradient(135deg, #1e3a8a, #2563eb) !important; }
        .navbar-brand { font-weight: 800; font-size: 1.3rem; }
        .nav-link { color: rgba(255,255,255,0.85) !important; border-radius: 8px; }
        .nav-link:hover, .nav-link.active { 
            color: white !important; 
            background: rgba(255,255,255,0.15) !important; 
        }
        .sidebar-wrapper { min-height: calc(100vh - 70px); }
        .user-badge { background: rgba(255,255,255,0.15); border-radius: 20px; padding: 4px 12px; }
    </style>
</head>
<body>

<!-- NAVBAR -->
<nav class="navbar navbar-expand-lg navbar-dark">
    <div class="container-fluid px-4">
        
        <!-- Brand -->
        <a class="navbar-brand" href="/pos_sederhana/dashboard.php">
            🛒 <span>Sistem POS</span>
        </a>
        
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav">
            <span class="navbar-toggler-icon"></span>
        </button>
        
        <div class="collapse navbar-collapse" id="mainNav">
            
            <!-- Menu kiri -->
            <ul class="navbar-nav me-auto gap-1">
                <li class="nav-item">
                    <a class="nav-link <?= ($currentPage==='dashboard') ? 'active' : '' ?>" 
                       href="/pos_sederhana/dashboard.php">
                        <i class="fas fa-tachometer-alt me-1"></i>Dashboard
                    </a>
                </li>
                <li class="nav-item">
                    <a class="nav-link <?= ($currentDir==='produk') ? 'active' : '' ?>" 
                       href="/pos_sederhana/produk/index.php">
                        <i class="fas fa-box me-1"></i>Produk
                    </a>
                </li>
                <li class="nav-item">
                    <a class="nav-link <?= ($currentDir==='transaksi') ? 'active' : '' ?>" 
                       href="/pos_sederhana/transaksi/index.php">
                        <i class="fas fa-shopping-cart me-1"></i>Transaksi
                    </a>
                </li>
                <li class="nav-item">
                    <a class="nav-link <?= ($currentDir==='laporan') ? 'active' : '' ?>" 
                       href="/pos_sederhana/laporan/index.php">
                        <i class="fas fa-chart-bar me-1"></i>Laporan
                    </a>
                </li>
            </ul>
            
            <!-- User info kanan -->
            <ul class="navbar-nav">
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle d-flex align-items-center gap-2" 
                       href="#" data-bs-toggle="dropdown">
                        <div class="user-badge">
                            <i class="fas fa-user-circle me-1"></i>
                            <?= htmlspecialchars($currentUser['nama']) ?>
                            <span class="badge bg-warning text-dark ms-1 small">
                                <?= ucfirst($currentUser['role']) ?>
                            </span>
                        </div>
                    </a>
                    <ul class="dropdown-menu dropdown-menu-end">
                        <li><h6 class="dropdown-header">
                            Halo, <?= htmlspecialchars($currentUser['nama']) ?>!
                        </h6></li>
                        <li><hr class="dropdown-divider"></li>
                        <li>
                            <a class="dropdown-item text-danger" href="/pos_sederhana/auth/logout.php">
                                <i class="fas fa-sign-out-alt me-2"></i>Logout
                            </a>
                        </li>
                    </ul>
                </li>
            </ul>
            
        </div>
    </div>
</nav>

<!-- Konten halaman mulai di sini -->
<div class="container-fluid py-4 px-4">
6. Halaman Dashboard: dashboard.php
📄 dashboard.php
<?php
$pageTitle = 'Dashboard — Sistem POS';
require_once 'config/database.php';
require_once 'includes/header.php'; // Otomatis cek login
?>

<div class="row g-4 mb-4">
    <div class="col-12">
        <h4 class="fw-bold">
            👋 Selamat Datang, <?= htmlspecialchars($_SESSION['user_nama']) ?>!
        </h4>
        <p class="text-muted"><?= date('l, d F Y') ?></p>
    </div>

    <?php
    // Statistik: hitung data dari database
    $totalProduk    = $pdo->query("SELECT COUNT(*) FROM produk")->fetchColumn();
    $totalHariIni   = $pdo->query("SELECT COUNT(*) FROM transaksi WHERE DATE(tanggal) = CURDATE()")->fetchColumn();
    $omzetHariIni   = $pdo->query("SELECT COALESCE(SUM(total), 0) FROM transaksi WHERE DATE(tanggal) = CURDATE()")->fetchColumn();
    $stokMenipis    = $pdo->query("SELECT COUNT(*) FROM produk WHERE stok < 10")->fetchColumn();
    ?>

    <!-- Kartu Statistik -->
    <div class="col-md-3">
        <div class="card border-0 shadow-sm h-100">
            <div class="card-body">
                <div class="d-flex justify-content-between align-items-center">
                    <div>
                        <div class="text-muted small">Total Produk</div>
                        <div class="h3 fw-bold mt-1"><?= $totalProduk ?></div>
                    </div>
                    <div class="p-3 rounded-3 bg-primary bg-opacity-10">
                        <i class="fas fa-box fa-2x text-primary"></i>
                    </div>
                </div>
                <a href="produk/index.php" class="btn btn-sm btn-outline-primary mt-2 w-100">Lihat Semua</a>
            </div>
        </div>
    </div>

    <div class="col-md-3">
        <div class="card border-0 shadow-sm h-100">
            <div class="card-body">
                <div class="d-flex justify-content-between align-items-center">
                    <div>
                        <div class="text-muted small">Transaksi Hari Ini</div>
                        <div class="h3 fw-bold mt-1"><?= $totalHariIni ?></div>
                    </div>
                    <div class="p-3 rounded-3 bg-success bg-opacity-10">
                        <i class="fas fa-shopping-cart fa-2x text-success"></i>
                    </div>
                </div>
                <a href="transaksi/index.php" class="btn btn-sm btn-outline-success mt-2 w-100">Buat Transaksi</a>
            </div>
        </div>
    </div>

    <div class="col-md-3">
        <div class="card border-0 shadow-sm h-100">
            <div class="card-body">
                <div class="d-flex justify-content-between align-items-center">
                    <div>
                        <div class="text-muted small">Omzet Hari Ini</div>
                        <div class="h4 fw-bold mt-1">
                            Rp <?= number_format($omzetHariIni, 0, ',', '.') ?>
                        </div>
                    </div>
                    <div class="p-3 rounded-3 bg-warning bg-opacity-10">
                        <i class="fas fa-money-bill fa-2x text-warning"></i>
                    </div>
                </div>
                <a href="laporan/index.php" class="btn btn-sm btn-outline-warning mt-2 w-100">Lihat Laporan</a>
            </div>
        </div>
    </div>

    <div class="col-md-3">
        <div class="card border-0 shadow-sm h-100">
            <div class="card-body">
                <div class="d-flex justify-content-between align-items-center">
                    <div>
                        <div class="text-muted small">Stok Menipis (<10)</div>
                        <div class="h3 fw-bold mt-1 text-danger"><?= $stokMenipis ?></div>
                    </div>
                    <div class="p-3 rounded-3 bg-danger bg-opacity-10">
                        <i class="fas fa-exclamation-triangle fa-2x text-danger"></i>
                    </div>
                </div>
                <a href="produk/index.php?filter=stok_minim" class="btn btn-sm btn-outline-danger mt-2 w-100">Cek Stok</a>
            </div>
        </div>
    </div>
</div>

<?php require_once 'includes/footer.php'; ?>
7. Prinsip Keamanan Login
🛡️ Hal-hal yang sudah kita terapkan:
  • password_hash() — Password tidak pernah disimpan plaintext
  • Prepared Statements — Mencegah SQL Injection
  • session_regenerate_id() — Mencegah Session Fixation Attack
  • htmlspecialchars() — Mencegah XSS (Cross-Site Scripting)
  • Pesan error tidak spesifik — Tidak memberitahu hacker apakah username atau password yang salah
  • Cek login di setiap halaman — Melalui header.php