I'd love for someone to review my code. It works fairly well, but I'm still getting the hang of PHP and want to make sure I've got an iron-clad system that isn't hilariously vulnerable. I'm also open to better ways to handle registering/logging in and out. I know the general answer is to not do this yourself, but I'm interested in learning to do this for myself rather than utilizing a framework. I definitely feel like I'm repeating code in several places, and would love to make this more concise.
index.php
<?php
error_reporting(E_ALL);
ini_set("display_errors", 1);
session_start();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Project Neue World</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<?php
if (isset($_SESSION["message"])) {
echo "<p>" . $_SESSION["message"] . "</p>";
unset($_SESSION["message"]);
}
if (isset($_SESSION["loggedin"]) && $_SESSION["loggedin"]) {
echo "<p>Welcome back! Click <a href=\"profile.php\">here</a> to view your profile, " . $_SESSION["username"] . ".</p>";
} else {
echo "<p>Welcome! Please <a href=\"register.php\">register</a> an account, or <a href=\"login.php\">login</a></p>";
}
?>
<script src="main.js"></script>
</body>
</html>
register.php
<?php
error_reporting(E_ALL);
ini_set("display_errors", 1);
session_start();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Project Neue World - Register</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<?php
if (isset($_SESSION["loggedin"]) && $_SESSION["loggedin"]) {
$_SESSION["message"] = "You're already logged into an account. Please <a href=\"?logout\">logout</a> to register a new account.";
header("Location: profile.php");
die();
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$errors = [];
$username = "";
$password = "";
$email = "";
if (isset($_POST["register"])) {
if (isset($_POST["username"])) {
$username = htmlspecialchars(trim($_POST["username"]));
if (empty($username)) {
$errors["username"] = "Please enter a username.";
} elseif (strlen($username) > 32) {
$errors["username"] = "Your username must be more than one character and less than 32 characters.";
}
}
if (isset($_POST["password"])) {
$password = trim($_POST["password"]);
if (empty($password)) {
$errors["password"] = "Please enter a password.";
} elseif (strlen($password) < 8) {
$errors["password"] = "Your password must be longer than eight characters.";
}
}
if (isset($_POST["email"])) {
$email = htmlspecialchars(trim($_POST["email"]));
if (empty($email)) {
$errors["email"] = "Please enter an e-mail address.";
} elseif (strlen($email) > 64) {
$errors["email"] = "Please enter an e-mail address shorter than 64 characters.";
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors["email"] = "Please enter a valid e-mail address.";
}
}
try {
$db = new PDO("sqlite:dev.sqlite3", null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$stmt = $db->prepare("SELECT username FROM users WHERE username = :username");
$stmt->execute([":username" => $username]);
$username_exists = $stmt->fetchColumn();
if ($username_exists) {
$errors["username"] = "Username already exists. Please enter another username.";
}
$stmt = $db->prepare("SELECT email FROM users WHERE email = :email");
$stmt->execute([":email" => $email]);
$email_exists = $stmt->fetchColumn();
if ($email_exists) {
$errors["email"] = "This e-mail address has already been used. Please enter another e-mail address.";
}
if (!empty($errors)) {
$_SESSION["errors"] = $errors;
$_SESSION["temporary_username"] = htmlspecialchars($_POST["username"]);
$_SESSION["temporary_email"] = htmlspecialchars($_POST["email"]);
header("Location: register.php");
die();
}
$stmt = $db->prepare("INSERT INTO users (username, password, email) VALUES (:username, :password, :email)");
$password_hash = password_hash($password, PASSWORD_DEFAULT);
$stmt->execute([
":username" => $username,
":password" => $password_hash,
":email" => $email
]);
$_SESSION["loggedin"] = true;
$_SESSION["username"] = $username;
$_SESSION["message"] = "Registration complete!";
header("Location: profile.php");
die();
}
catch (PDOException $e) {
echo "<p>" . $e->getMessage() . "</p>";
}
}
}
if (isset($_SESSION["errors"])) {
foreach ($_SESSION["errors"] as $error) {
echo "<p>" . $error . "</p>";
}
unset($_SESSION["errors"]);
}
?>
<form action="register.php" method="post">
<div><label for="username">Username</label></div>
<div><input type="text" name="username" id="username" value="<?php echo isset($_SESSION["temporary_username"]) ? $_SESSION["temporary_username"] : "" ?>" required></div>
<div><label for="password">Password</label></div>
<div><input type="password" name="password" id="password" required></div>
<div><label for="email">E-mail</label></div>
<div><input type="email" name="email" id="email" value="<?php echo isset($_SESSION["temporary_email"]) ? $_SESSION["temporary_email"] : "" ?>" required></div>
<div><input type="submit" name="register" value="Register Account"></div>
</form>
<?php
unset($_SESSION["temporary_username"]);
unset($_SESSION["temporary_email"]);
?>
<script src="main.js"></script>
</body>
</html>
login.php
<?php
error_reporting(E_ALL);
ini_set("display_errors", 1);
session_start();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Project Neue World - Login</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<?php
if (isset($_SESSION["message"])) {
echo "<p>" . $_SESSION["message"] . "</p>";
unset($_SESSION["message"]);
}
if (isset($_SESSION["loggedin"]) && $_SESSION["loggedin"]) {
$_SESSION["message"] = "You're already logged in!";
header("Location: profile.php");
die();
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$errors = [];
$username = "";
$password = "";
if (isset($_POST["login"])) {
if (isset($_POST["username"])) {
$username = $_POST["username"];
if (empty($username)) {
$errors["username"] = "Please enter a username.";
}
}
if (isset($_POST["password"])) {
$password = $_POST["password"];
if (empty($password)) {
$errors["password"] = "Please enter a password.";
}
}
try {
$db = new PDO("sqlite:dev.sqlite3", null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$stmt = $db->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute([":username" => $username]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result["username"] !== $username) {
$errors["login"] = "Incorrect login credentials.";
}
if (!password_verify($password, $result["password"])) {
$errors["login"] = "Incorrect login credentials.";
}
if (!empty($errors)) {
if (!empty($errors)) {
$_SESSION["errors"] = $errors;
$_SESSION["temporary_username"] = htmlspecialchars($_POST["username"]);
header("Location: login.php");
die();
}
}
$_SESSION["loggedin"] = true;
$_SESSION["username"] = $username;
$_SESSION["message"] = "Successfully logged in!";
header("Location: profile.php");
die();
}
catch (PDOException $e) {
echo "<p>" . $e->getMessage() . "</p>";
}
}
}
if (isset($_SESSION["errors"])) {
foreach ($_SESSION["errors"] as $error) {
echo "<p>" . $error . "</p>";
}
unset($_SESSION["errors"]);
}
?>
<form action="login.php" method="post" name="login-form">
<div><label for="username">Username</label></div>
<div><input type="text" name="username" id="username" value="<?php echo isset($_SESSION["temporary_username"]) ? $_SESSION["temporary_username"] : "" ?>" required></div>
<div><label for="password">Password</label></div>
<div><input type="password" name="password" id="password" required></div>
<div><input type="submit" name="login" value="Login"></div>
</form>
<?php
unset($_SESSION["temporary_username"]);
?>
<script src="main.js"></script>
</body>
</html>
profile.php
<?php
error_reporting(E_ALL);
ini_set("display_errors", 1);
session_start();
if ($_SERVER["REQUEST_METHOD"] === "GET") {
if (isset($_GET["logout"])) {
session_unset();
session_destroy();
header("Location: index.php");
die();
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Project Neue World - Profile</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<?php
if (isset($_SESSION["message"])) {
echo "<p>" . $_SESSION["message"] . "</p>";
unset($_SESSION["message"]);
}
if (!isset($_SESSION["loggedin"]) || !$_SESSION["loggedin"]) {
$_SESSION["message"] = "Please <a href=\"register.php\">register</a> an account to view your profile.";
header("Location: index.php");
die();
}
?>
<p>Welcome to your profile, <?php echo $_SESSION["username"]; ?>.</p>
<?php
try {
$db = new PDO("sqlite:dev.sqlite3", null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$stmt = $db->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute([":username" => $_SESSION["username"]]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
echo "<p>User ID: " . $result["id"] . "</p>";
echo "<p>E-mail Address: " . $result["email"] . "</p>";
}
catch (PDOException $e) {
echo "<p>" . $e->getMessage() . "</p>";
}
?>
<p><a href="?logout">Logout</a>.</p>
<script src="main.js"></script>
</body>
</html>
error_reporting(E_ALL); ini_set("display_errors", 1);
in production. Any errors or warnings can give away information to a hacker. I would create a seperate 'start.php' file to include in each php file containing this, that way you only have to change it in one location. \$\endgroup\$$_SESSION["username"]
against the database each time. \$\endgroup\$trim
the password? What if the user wishes to use a space? \$\endgroup\$