Chapterبەش 10 · Lectureوانە 10.3
A Login System with Sessionsسیستەمی چوونەژوورەوە بە سێشن
Build the course's SessionLab step by step — the users table, a connection file, registration, login, a protected page and logout — the safe way: password_hash(), prepared statements, session_regenerate_id(), exit after every redirect and a complete logout. Also: why the lab's cookie version can be forged, and how to add admin and user roles.
SessionLabـی کۆرسەکە هەنگاو بە هەنگاو دروست بکە — بەکارهێنەران، پەیوەندی، ، ، لاپەڕەیەکی پارێزراو و — بە ڕێگای سەلامەت: password_hash()، ، session_regenerate_id()، exit دوای هەموو و چوونەدەرەوەیەکی تەواو. هەروەها: بۆچی وەشانی تاقیگەکە دەتوانرێت ساختە بکرێت و چۆن ڕۆڵی بەڕێوەبەر و زیاد بکەیت.
What you will learnچی فێر دەبیت
- Build registration, login, protected pages and logout with sessions, following the steps of Lab 9.
- Store passwords with
password_hash()and check them withpassword_verify(). - Use prepared statements, and explain how SQL injection breaks a login.
- Protect pages with a session check followed by
exit, and log users out completely. - Explain why a cookie that holds the user id is not a login, and use cookies only for harmless conveniences.
- Add roles and tell authentication from authorization.
- بە و بە پێی هەنگاوەکانی تاقیگەی ٩، ، ، لاپەڕەی پارێزراو و دروست بکەیت.
- بە
password_hash()خەزن بکەیت و بەpassword_verify()بیانپشکنیت. - بەکاربهێنیت و ڕوونی بکەیتەوە چۆن تێک دەدات.
- لاپەڕەکان بە پشکنینی و دواتر
exitبپارێزیت و بەکارهێنەران بە تەواوی بخەیتە دەرەوە. - ڕوونی بکەیتەوە بۆچی کە ناسێنەری تێدایە نییە و تەنها بۆ ئاسانکاریی بێزیان بەکاربهێنیت.
- ڕۆڵ زیاد بکەیت و لە جیا بکەیتەوە.
What we are going to buildچی دروست دەکەین
Lab 9 of the course is called “Session and Cookie”. In it you use a session (or a cookie) to remember users after they log in to their account. In general, sessions and cookies are used to keep track of users' activity on a website or web application.
تاقیگەی ٩ی کۆرسەکە ناوی «Session and Cookie»ـە. تێیدا (یان ) بەکار دەهێنیت بۆ ئەوەی بەکارهێنەران دوای چوونەژوورەوەیان بۆ لە بیر بگریت. بە گشتی، و بۆ شوێنکەوتنی چالاکیی بەکارهێنەران لە یان بەکار دێن.
We build the in-class project of this lab, SessionLab, step by step: a users table, a connection file, a registration page, a login page, a protected page and a logout page. The course's code works on XAMPP, but it has serious security holes. In this lecture every step is kept, every hole is shown, and every file is rebuilt the safe way.
پرۆژەی ناو پۆلی ئەم تاقیگەیە، SessionLab، هەنگاو بە هەنگاو دروست دەکەین: users، پەیوەندی، لاپەڕەی ، لاپەڕەی ، لاپەڕەیەکی پارێزراو و لاپەڕەی . کۆدەکەی کۆرسەکە لەسەر XAMPP کار دەکات، بەڵام کونی ئاسایشیی گەورەی تێدایە. لەم وانەیەدا هەموو هەنگاوێک دەپارێزرێت، هەموو کونێک پیشان دەدرێت و هەموو بە شێوەی سەلامەت دووبارە دروست دەکرێتەوە.
Checking who a user is, usually with a username and password. If the details are correct, the user is logged in. It answers 'Who are you?'; answers 'What may you do?'.
پشکنینی ئەوەی بەکارهێنەرێک کێیە، زۆرجار بە ناوی بەکارهێنەر و تێپەڕەوشە. ئەگەر زانیارییەکان دروست بن، بەکارهێنەرەکە دەچێتە ژوورەوە. وەڵامی «تۆ کێیت؟» دەداتەوە؛ وەڵامی «بۆت هەیە چی بکەیت؟» دەداتەوە.
A login system does two jobs. answers “Who are you?”: the user proves it with an email and a password. answers “What may you do?”: for example, only logged-in users may see index.php, and only an admin may manage users.
سیستەمی دوو کار دەکات. وەڵامی «تۆ کێیت؟» دەداتەوە: بە و دەیسەلمێنێت. وەڵامی «بۆت هەیە چی بکەیت؟» دەداتەوە: بۆ نموونە، تەنها بەکارهێنەرانی چووەژوورەوە بۆیان هەیە index.php ببینن و تەنها بەڕێوەبەر بۆی هەیە بەکارهێنەران بەڕێوە ببات.
At the university gate the guard checks your student card once. Then he gives you a coloured wristband for the day. Inside, every building only looks at the wristband; nobody asks for your card again. When you leave, you give the wristband back. In our system, checking the card is login.php, the wristband is the session, every building is a protected page that checks $_SESSION, and giving it back is logout.php.
لە دەروازەی زانکۆ پاسەوانەکە یەک جار کارتی خوێندکارییەکەت دەپشکنێت. پاشان بازنەیەکی ڕەنگاوڕەنگی دەستت بۆ ئەو ڕۆژە دەداتێ. لە ژوورەوە، هەموو باڵەخانەیەک تەنها سەیری بازنەکە دەکات؛ کەس دیسان داوای کارتەکەت ناکاتەوە. کاتێک دەڕۆیت، بازنەکە دەگەڕێنیتەوە. لە سیستەمەکەی ئێمەدا، پشکنینی کارتەکە login.phpـە، بازنەکە ، هەر باڵەخانەیەک لاپەڕەیەکی پارێزراوە کە $_SESSION دەپشکنێت و گەڕاندنەوەکەی logout.phpـە.
Where the analogy stops workingسنووری ئەم نموونەیە
A wristband can be copied by someone who looks closely. A session ID is long and random, but it can still be stolen, so we keep it in an HttpOnly cookie and give the user a new one at login.
بازنەی دەست دەتوانرێت لەلایەن کەسێکەوە کۆپی بکرێت کە بە وردی سەیری بکات. درێژ و هەڕەمەکییە، بەڵام هێشتا دەتوانرێت بدزرێت، بۆیە لە HttpOnlyدا ڕایدەگرین و لە کاتی ناسێنەرێکی نوێ دەدەین بە .
What we are going to buildچی دروست دەکەین
The login flow at a glanceڕەوتی چوونەژوورەوە بە یەک نیگا
The lab plan in seven stepsپلانی تاقیگەکە بە حەوت هەنگاو
The lab lists the steps of the project. The first steps were already done in earlier labs (database and CRUD). Here they are, cleaned up, with the section of this lecture where each one is done:
تاقیگەکە هەنگاوەکانی پرۆژەکە ڕیز دەکات. یەکەم هەنگاوەکان پێشتر لە تاقیگەکانی پێشوودا کراون ( و CRUD). ئەمە هەنگاوەکانن، پاککراونەتەوە، لەگەڵ ئەو بەشەی ئەم وانەیە کە هەر هەنگاوێکی تێدا دەکرێت:
- Create the databaseبنکەدراوە دروست بکە
Create a MySQL database named
session(Database and table).MySQL بە ناوی
sessionدروست بکە (بنکەدراوە و خشتە). - Create the tableخشتە دروست بکە
Create the table
userswith the fieldsid,username,emailandpassword(Database and table).usersبەid،username،emailوpasswordدروست بکە (بنکەدراوە و خشتە). - Connection fileفایلی پەیوەندی
In your NetBeans project, create
connection.php(Step 1).لە پرۆژەی NetBeansـەکەتدا
connection.phpدروست بکە (هەنگاوی ١). - Registration pageلاپەڕەی خۆتۆمارکردن
Create
register.php, where users create an account to use later. It needs an HTML form asking for username, email address, password and password confirmation (Step 2).register.phpدروست بکە، کە بەکارهێنەران تێیدا هەژمار دروست دەکەن بۆ ئەوەی دواتر بەکاری بهێنن. پێویستی بە HTML هەیە کە ناوی ، ، و دووپاتکردنەوەی بپرسێت (هەنگاوی ٢). - Login pageلاپەڕەی چوونەژوورەوە
Create
login.phpwith a form asking for email and password, and the PHP code for logging in, using a SELECT query (Step 3).login.phpدروست بکە بە کە و بپرسێت و کۆدی PHP بۆ ، بە بەکارهێنانی SELECT (هەنگاوی ٣). - Create the sessionسێشنەکە دروست بکە
In
login.php, when the user is found, create the session for the user who logged in successfully (Step 4).لە
login.phpدا، کاتێک دۆزرایەوە، بۆ ئەو بەکارهێنەرە دروست بکە کە بە سەرکەوتوویی چووەتە ژوورەوە (هەنگاوی ٤). - Check and end the sessionپشکنین و کۆتاییهێنان بە سێشن
Check the session on every other page (Step 5), and when users log out, delete the session (and any cookie) you created (Final step).
لە هەموو لاپەڕەکانی تردا بپشکنە (هەنگاوی ٥) و کاتێک بەکارهێنەران دەچنە دەرەوە، ئەو (و هەر ) بسڕەوە کە دروستت کردووە (هەنگاوی کۆتایی).
The lab plan in seven stepsپلانی تاقیگەکە بە حەوت هەنگاو
The lab PDF and the in-class SessionLabPDFـی تاقیگەکە و SessionLabـی ناو پۆل
There are two versions of this project in the course: the code printed in the Lab 9 PDF, and the SessionLab folder written in class. They differ in many places. This lecture shows both and builds a third, corrected version:
دوو وەشانی ئەم پرۆژەیە لە کۆرسەکەدا هەن: ئەو کۆدەی لە PDFـی تاقیگەی ٩دا چاپ کراوە و SessionLab کە لە ناو پۆلدا نووسراوە. لە زۆر شوێندا جیاوازن. ئەم وانەیە هەردووکیان پیشان دەدات و وەشانێکی سێیەمی ڕاستکراو دروست دەکات:
| Pointخاڵ | Lab 9 PDFPDFـی تاقیگەی ٩ | SessionLab (in class)SessionLab (ناو پۆل) | CêIT versionوەشانی CêIT |
|---|---|---|---|
| PHP processingکارکردنی PHP | Inside the HTML, after the form (headers too late)لە ناو HTMLدا، دوای (سەردێڕەکان زۆر درەنگن) | At the top, before the HTMLلە سەرەوە، پێش HTML | At the top, before the HTMLلە سەرەوە، پێش HTML |
| Quotes in code لە کۆددا | Some curly quotes ‘ ’ “ ” (fatal errors)هەندێک خوار ‘ ’ “ ” () | Straight quotes ڕاست | Straight quotes ڕاست |
| Password storageخەزنکردنی | sha1() | Plain textدەقی سادە | password_hash() |
| SQL queries SQL | Input pasted into the query ڕاستەوخۆ خراوەتە ناو | Input pasted into the query ڕاستەوخۆ خراوەتە ناو | Prepared statements |
| Validation | Empty-field checks, confirm passwordپشکنینی بەتاڵ، دووپاتکردنەوەی | Noneهیچ | Empty fields, email format, length, confirm بەتاڵ، شێوەی ، درێژی، دووپاتکردنەوە |
| Error function هەڵە | mysql_error() (removed in PHP 7)mysql_error() (لە PHP 7دا لابراوە) | mysqli_error($connect) | Short messages, no database detailsپەیامی کورت، بێ وردەکاریی |
| Field namesناوی | uname, email, pass1, pass2, submituname، email، pass1، pass2، submit | usernameInput, emailInput, passwordInput, confirmBtn, loginBtnusernameInput، emailInput، passwordInput، confirmBtn، loginBtn | SessionLab names, plus confirmInputناوەکانی SessionLab، لەگەڵ confirmInput |
| Connectionپەیوەندی | Password 'root' (MAMP), mysqli_select_db() 'root' (MAMP)، mysqli_select_db() | Password '' (XAMPP), database as 4th argument '' (XAMPP)، وەک چوارەم | XAMPP values, exceptions handledنرخەکانی XAMPP، مامەڵەیان لەگەڵ دەکرێت |
| Finding the userدۆزینەوەی | mysqli_num_rows() == 1mysqli_num_rows() == 1 | while with mysqli_fetch_assoc()while لەگەڵ mysqli_fetch_assoc() | mysqli_num_rows() === 1, then password_verify()mysqli_num_rows() === 1، پاشان password_verify() |
| Cookie versionوەشانی | Three extra code stepsسێ هەنگاوی کۆدی زیادە | One commented-out setcookie() lineیەک دێڕی setcookie() کە کراوە بە | Shown only to explain why it is unsafeتەنها بۆ ڕوونکردنەوەی ئەوەی بۆچی ناسەلامەتە پیشان دەدرێت |
Database and tableبنکەدراوە و خشتە
Steps 1 and 2: create a database named session and a table for the users. The lab gives only the field names, so here is a complete definition. You can run it in the SQL tab of phpMyAdmin (Creating a database in phpMyAdmin).
هەنگاوی ١ و ٢: بە ناوی session و بۆ بەکارهێنەران دروست بکە. تاقیگەکە تەنها ناوی دەدات، بۆیە ئەمە پێناسەیەکی تەواوە. دەتوانیت لە بەشی SQLـی phpMyAdminدا کاری پێبکەیت (دروستکردنی بنکەدراوە لە phpMyAdminدا).
CREATE DATABASE session;
USE session;
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL
);Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 5
AUTO_INCREMENT PRIMARY KEY: MySQL numbers the users 1, 2, 3 … by itself.AUTO_INCREMENT PRIMARY KEY: MySQL خۆی بەکارهێنەران ژمارە دەکات 1، 2، 3 …- Lineدێڕی 7
UNIQUE: two accounts cannot have the same email. The login finds users by email, so this matters.UNIQUE: دوو هەژمار ناتوانن هەمان هەبێت. بەکارهێنەران بە دەدۆزێتەوە، بۆیە ئەمە گرنگە.- Lineدێڕی 8
VARCHAR(255): room for apassword_hash()hash (60 characters today, longer with future algorithms). This column never holds the real password.VARCHAR(255): شوێن بۆ هاشێکیpassword_hash()(ئەمڕۆ ٦٠ پیت، لەگەڵ داهاتوو درێژتر). ئەم هەرگیز ڕاستەقینەی تێدا نییە.
Step 1: the connection fileهەنگاوی ١: فایلی پەیوەندی
Every page that talks to the database needs a connection. Instead of writing it again on every page, we put it in one file, connection.php, and include it (A real use: one database connection file). This is the lab PDF's version:
هەموو لاپەڕەیەک کە قسە لەگەڵ دەکات پێویستی بە هەیە. لە جیاتی ئەوەی لە هەموو لاپەڕەیەکدا دووبارە بنووسرێتەوە، دەیخەینە یەک فایلەوە، connection.php و بە require دەیهێنینە ناو لاپەڕەکانەوە (؛ بەکارهێنانێکی ڕاستەقینە: یەک فایلی پەیوەندیی بنکەدراوە). ئەمە وەشانەکەی PDFـی تاقیگەکەیە:
<?php
$connect= mysqli_connect('localhost', 'root','root' );
if(!$connect){
die("could not connect".mysqli_error($connect));
}
$db= mysqli_select_db($connect, 'session');
if(!$db){
die("could not select database".mysqli_error($connect));
}SessionLab, written in class, is shorter: it uses XAMPP's empty password and gives the database name as the fourth argument, so mysqli_select_db() is not needed:
SessionLab، کە لە ناو پۆلدا نووسراوە، کورتترە: بەتاڵەکەی XAMPP بەکار دەهێنێت و ناوی وەک چوارەم دەدات، بۆیە mysqli_select_db() پێویست نییە:
<?php
$connect = mysqli_connect('localhost', 'root', '', 'session');
if (!$connect) {
die("Cannot connect to the db: ".mysqli_error($connect));
}
?>Step 1: the connection fileهەنگاوی ١: فایلی پەیوەندی
The corrected connection.phpconnection.phpـی ڕاستکراو
Here is connection.php written for PHP 8. It uses the XAMPP values of SessionLab, catches the exception, and shows visitors only a short message:
ئەمە connection.phpـە کە بۆ PHP 8 نووسراوە. نرخەکانی XAMPPـی SessionLab بەکار دەهێنێت، دەگرێت و تەنها پەیامێکی کورت پیشانی سەردانکەران دەدات:
<?php
// connection.php: the database connection, included by the other pages.
// Since PHP 8.1, mysqli throws an exception when something fails. We say so clearly:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
// server, MySQL user, password, database name (XAMPP: user "root", empty password)
$connect = mysqli_connect('localhost', 'root', '', 'session');
mysqli_set_charset($connect, 'utf8mb4');
} catch (mysqli_sql_exception $e) {
// Do not show technical details to visitors
die('Cannot connect to the database. Is MySQL running?');
}Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 4
Errors become exceptions (this is already the default since PHP 8.1; the line makes it clear).
دەبنە (لە PHP 8.1ـەوە ئەمە بنەڕەتییە؛ دێڕەکە ڕوونی دەکاتەوە).
- Lineدێڕی 8
Server, MySQL user, password, database name: the XAMPP values.
، MySQL، ، ناوی : نرخەکانی XAMPP.
- Lineدێڕی 9
utf8mb4: Kurdish and every other script are stored correctly.utf8mb4: کوردی و هەموو ئەلفوبێیەکی تر بە دروستی خەزن دەکرێن.- Lineدێڕی 10-12
If MySQL is not running, the visitor sees one short sentence, not a fatal error with your file paths.
ئەگەر MySQL کار نەکات، سەردانکەر یەک ڕستەی کورت دەبینێت، نەک لەگەڵ فایلەکانت.
Step 2: the registration pageهەنگاوی ٢: لاپەڕەی خۆتۆمارکردن
register.php lets a new user create an account to use later. Its form asks for a username, an email address, a password and the password again. The lab PDF prints all of register.php like this (only the apostrophe in one comment has been straightened):
register.php ڕێگە بە نوێ دەدات هەژمارێک دروست بکات بۆ ئەوەی دواتر بەکاری بهێنێت. ناوی ، ، و دووبارە دەپرسێت. PDFـی تاقیگەکە هەموو register.php بەم شێوەیە چاپ دەکات (تەنها ئەپۆسترۆفی ناو یەک ڕاست کراوەتەوە):
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<form align="center" method="post" action="<?php echo
htmlspecialchars($_SERVER["PHP_SELF"]) ?>">
Username: <input type="text" name="uname" size="30"><br><br> Email:
<input type="text" name="email" size="30"><br><br> Password: <input
type="password" name="pass1" size="30"><br><br> Username: <input
type="password" name="pass2" size="30"><br><br>
<input type="submit" value="Create Account" name="submit"><br>
<?php
if(isset($_POST["submit"])){
if(empty($_POST["uname"])){
echo "Username is required";
}elseif(empty($_POST["email"])) {
echo"Please enter your email address";
}elseif(empty($_POST["pass1"])) {
echo"Please enter your password";
}elseif(empty($_POST["pass2"])) {
echo"Please confirm your password";
}elseif($_POST["pass1"]!=$_POST["pass2"]) {
echo" passwords didnot match, please try again";
}else{
//getting values from form inputs
$name=$_POST["uname"];
$email=$_POST["email"];
$password=$_POST["pass1"];
$password= sha1($password);
require './connection.php';
$query="INSERT INTO Users (name, email, password) VALUES ('$name', '$email',
'$password')";
$result= mysqli_query ($connect, $query);
//check the query if it's true
if($result){
echo "Account created succeffuly, you can login now";
}else{
echo"error occured, please try again".mysql_error();
}
//close the connecton
mysqli_close($connect);
}
}
?>
</form>Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 7-8
The form posts to itself:
htmlspecialchars($_SERVER["PHP_SELF"])is the XSS-safe way to do this.بۆ خۆی دەنێرێت:
htmlspecialchars($_SERVER["PHP_SELF"])ڕێگای سەلامەت لە XSSـە بۆ ئەم کارە.- Lineدێڕی 16-25
The checks, in order: username, email, password, confirmation, and do both passwords match?
پشکنینەکان، بە ڕیزبەندی: ناوی ، ، ، دووپاتکردنەوە و ئایا هەردوو وەک یەکن؟
Builds a signup page step by step: checking the form on the server, password_hash(), connecting to MySQL, and saving the user with a prepared statement.
هەنگاو بە هەنگاو دروست دەکات: بۆ ، password_hash()، پەیوەندیکردن بە و هەڵگرتنی بە .
Watch 4:04–18:55: processing the signup form up to the duplicate-email check.
لە 4:04 تا 18:55 سەیر بکە: کارکردن لەسەر ، تا پشکنینی ئەوەی ئایا پێشتر بەکارهاتووە.
Step 2: the registration pageهەنگاوی ٢: لاپەڕەی خۆتۆمارکردن
SessionLab's register.phpregister.phpـی SessionLab
SessionLab's register.php moves the PHP to the top of the file, which is good, but it drops all the checks:
register.phpـی SessionLab کۆدی PHP دەباتە سەرەوەی ، کە باشە، بەڵام هەموو پشکنینەکان لادەبات:
<?php
include('connection.php');
if (isset($_POST['confirmBtn']))
{
$username = $_POST['usernameInput'];
$email = $_POST['emailInput'];
$address = $_POST['addressInput'];
$password = $_POST['passwordInput'];
$query = mysqli_query($connect, "insert into Users (username, email, password) values ('$username', '$email', '$password')");
if ($query)
{
echo "Account created successfully!";
} else {
echo "An error has been occured!".mysqli_error($connect);
}
}
?>
<html>
<body>
<form method="POST">
Username: <input type="text" name="usernameInput"><br>
Email Address: <input type="text" name="emailInput"><br>
Password: <input type="text" name="passwordInput"><br>
<input type="submit" name="confirmBtn" value="Create Account">
</form>
</body>
</html>Warning: Undefined array key "addressInput" in /www/register.php on line 8 Account created successfully! [the form again]
HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<br />
<b>Warning</b>: Undefined array key "addressInput" in <b>/www/register.php</b> on line <b>8</b><br />
Account created successfully!
<html>
<body>
<form method="POST">
…Step 2: the registration pageهەنگاوی ٢: لاپەڕەی خۆتۆمارکردن
The corrected register.phpregister.phpـی ڕاستکراو
The corrected page keeps the lab's order of checks and its messages, uses SessionLab's field names, and adds what is missing: an email check, a minimum length, hashing, a prepared statement, sticky values and a message for a duplicate email.
لاپەڕە ڕاستکراوەکە ڕیزبەندیی پشکنینەکان و پەیامەکانی تاقیگەکە دەهێڵێتەوە، ناوی خانەکانی SessionLab بەکار دەهێنێت و ئەوەی کەمە زیادی دەکات: پشکنینی ، کەمترین درێژی، هاشکردن، ، لکاو و پەیامێک بۆ دووبارە.
<?php
require 'connection.php';
$message = '';
$username = '';
$email = '';
if (isset($_POST['confirmBtn'])) {
// Read the form values (?? '' avoids warnings if a field is missing)
$username = trim($_POST['usernameInput'] ?? '');
$email = trim($_POST['emailInput'] ?? '');
$password = $_POST['passwordInput'] ?? '';
$confirm = $_POST['confirmInput'] ?? '';
// Check the inputs in order, like the lab does
if ($username === '') {
$message = 'Username is required.';
} elseif ($email === '') {
$message = 'Please enter your email address.';
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$message = 'Please enter a valid email address.';
} elseif ($password === '') {
$message = 'Please enter your password.';
} elseif (strlen($password) < 8) {
$message = 'The password needs at least 8 characters.';
} elseif ($confirm === '') {
$message = 'Please confirm your password.';
} elseif ($password !== $confirm) {
$message = 'The passwords did not match. Please try again.';
} else {
// Never store the password itself: store a slow, salted hash
$hash = password_hash($password, PASSWORD_DEFAULT);
// Prepared statement: the ? marks are filled in safely (no SQL injection)
$stmt = mysqli_prepare($connect, 'INSERT INTO users (username, email, password) VALUES (?, ?, ?)');
mysqli_stmt_bind_param($stmt, 'sss', $username, $email, $hash);
try {
mysqli_stmt_execute($stmt);
$message = 'Account created successfully. You can log in now.';
$username = '';
$email = '';
} catch (mysqli_sql_exception $e) {
// Error 1062 = duplicate entry: the email column is UNIQUE
if ($e->getCode() === 1062) {
$message = 'An account with this email already exists.';
} else {
$message = 'An error occurred. Please try again.';
}
}
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Create an account</title>
</head>
<body>
<h1>Create an account</h1>
<p><?php echo htmlspecialchars($message); ?></p>
<form method="post">
Username: <input type="text" name="usernameInput" value="<?php echo htmlspecialchars($username); ?>"><br>
Email Address: <input type="email" name="emailInput" value="<?php echo htmlspecialchars($email); ?>"><br>
Password: <input type="password" name="passwordInput"><br>
Confirm Password: <input type="password" name="confirmInput"><br>
<input type="submit" name="confirmBtn" value="Create Account">
</form>
<p>Already have an account? <a href="login.php">Log in</a></p>
</body>
</html>Create an account The passwords did not match. Please try again. Username: [Aram] Email Address: [aram@example.com] Password: [ ] Confirm Password: [ ] [Create Account] Already have an account? Log in
HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Create an account</title>
</head>
<body>
<h1>Create an account</h1>
<p>The passwords did not match. Please try again.</p>
<form method="post">
Username: <input type="text" name="usernameInput" value="Aram"><br>
Email Address: <input type="email" name="emailInput" value="aram@example.com"><br>
Password: <input type="password" name="passwordInput"><br>
Confirm Password: <input type="password" name="confirmInput"><br>
<input type="submit" name="confirmBtn" value="Create Account">
</form>
<p>Already have an account? <a href="login.php">Log in</a></p>
</body>
</html>Tested with PHP 8.5: the same file was run through a small test layer that answers the mysqli calls from an SQLite database (no MySQL server was available).
بە PHP 8.5 تاقی کراوەتەوە: هەمان لە ڕێگەی چینێکی تاقیکردنەوەی بچووکەوە کارپێکرا کە وەڵامی بانگکردنەکانی mysqli لە SQLiteـەوە دەداتەوە ( MySQL بەردەست نەبوو).
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
require: without the connection the page cannot work, so stop if the file is missing.require: بەبێ پەیوەندییەکە لاپەڕەکە ناتوانێت کار بکات، بۆیە ئەگەر نەبوو بوەستە.- Lineدێڕی 10-13
trim()removes spaces around the text;?? ''avoids warnings. Passwords are not trimmed: a space may be part of a password.trim()بۆشاییەکانی دەوری دەقەکە لادەبات؛?? ''ڕێگە لە دەگرێت. trim ناکرێن: لەوانەیە بەشێک بێت لە .- Lineدێڕی 20
filter_var(…, FILTER_VALIDATE_EMAIL)checks the shape of an email address.filter_var(…, FILTER_VALIDATE_EMAIL)شێوەی دەپشکنێت.- Lineدێڕی 32
A slow, salted hash such as
$2y$12$…. The real password is never stored.هاشێکی خاو و خوێدار وەک
$2y$12$…. ڕاستەقینە هەرگیز خەزن ناکرێت.- Lineدێڕی 35-36
The SQL has
?placeholders;bind_paramfills them with three strings ('sss'). The data can never change the SQL.SQLـەکە
?ـی تێدایە؛bind_paramبە سێ ('sss') پڕیان دەکاتەوە. هەرگیز ناتوانێت SQLـەکە بگۆڕێت.- Lineدێڕی 42-45
Error 1062 is MySQL's “duplicate entry”: the email is already registered.
هەڵەی 1062 «duplicate entry»ـی MySQLـە: پێشتر تۆمار کراوە.
- Lineدێڕی 63-64
Sticky values, escaped with
htmlspecialchars(). Password fields are never refilled.لکاو، پارێزراو بە
htmlspecialchars(). خانەکانی هەرگیز پڕ ناکرێنەوە.
| The visitor sends…سەردانکەر دەنێرێت… | The page answersلاپەڕەکە دەداتەوە |
|---|---|
| an empty usernameناوی بەتاڵ | Username is required. |
aram@example as the emailaram@example وەک | Please enter a valid email address. |
| two different passwordsدوو جیاواز | The passwords did not match. Please try again. |
| correct data دروست | Account created successfully. You can log in now. |
| the same email a second timeهەمان بۆ جاری دووەم | An account with this email already exists. |
Step 2: the registration pageهەنگاوی ٢: لاپەڕەی خۆتۆمارکردن
Why passwords are hashedبۆچی تێپەڕەوشەکان هاش دەکرێن
Storing a password as a slow, salted hash instead of the password itself. In PHP use password_hash() to save it and password_verify() to check it at login; never use md5() or sha1() for this.
خەزنکردنی تێپەڕەوشەیە وەک هاشێکی خاو کە نرخێکی هەڕەمەکی (salt)ی تێکەڵ کراوە، نەک خودی تێپەڕەوشەکە. لە PHPـدا password_hash() بۆ خەزنکردن و password_verify() بۆ پشکنین لە کاتی بەکار بهێنە؛ هەرگیز md5() یان sha1() بۆ ئەم کارە بەکار مەهێنە.
Why hash? If someone steals your users table, plain-text passwords (SessionLab) can be used at once, also on other websites where people reuse them. sha1() and md5() hashes (the lab PDF, a student's version) are cracked very fast, because these functions are made to be fast. password_hash() is made to be slow, and adds a random salt, so the same password gives a different hash each time. password_verify() knows how to check it:
بۆچی هاش؟ ئەگەر کەسێک usersـەکەت بدزێت، دەقی سادە (SessionLab) دەستبەجێ بەکار دەهێنرێن، لەسەر تریش کە خەڵک هەمان بەکار دەهێنن. هاشەکانی sha1() و md5() (PDFـی تاقیگەکە، وەشانی خوێندکارێک) زۆر خێرا دەشکێنرێن، چونکە ئەم بۆ خێرایی دروست کراون. password_hash() بۆ خاوی دروست کراوە و هەڕەمەکی زیاد دەکات، بۆیە هەمان هەر جارێک هاشێکی جیاواز دەدات. password_verify() دەزانێت چۆن بیپشکنێت:
<?php
$password = 'tea-and-bread-42';
$hash1 = password_hash($password, PASSWORD_DEFAULT);
$hash2 = password_hash($password, PASSWORD_DEFAULT);
echo $hash1 . "<br>";
echo $hash2 . "<br>"; // different: each hash has its own random salt
var_dump(password_verify('tea-and-bread-42', $hash1)); // the right password
var_dump(password_verify('Tea-and-bread-42', $hash1)); // one letter is different$2y$12$M7OMsg8rmPREetLqTC8kFOhqiefzjZIAie6UM0/j9danS999KWdjy $2y$12$UoPqmxtr4fFGM.VnOSx1D.I5Ujh1SxMEMupsTp.jOk8xEnCayJ48m bool(true) bool(false)
HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
$2y$12$M7OMsg8rmPREetLqTC8kFOhqiefzjZIAie6UM0/j9danS999KWdjy<br>$2y$12$UoPqmxtr4fFGM.VnOSx1D.I5Ujh1SxMEMupsTp.jOk8xEnCayJ48m<br>bool(true)
bool(false)$2y$ = bcrypt, 12 = the cost (how slow). Your hashes will be different every time.
$2y$ = bcrypt، 12 = تێچوو (چەند خاو). هاشەکانی تۆ هەر جارێک جیاواز دەبن.
Ranks ways to store passwords, from plain text and encryption to hashing, salting and slow hashes like bcrypt (the method password_hash() uses).
ڕێگاکانی هەڵگرتنی پلەبەندی دەکات: لە دەقی ئاسایی و ، تا ، زیادکردنی و هێواش وەک bcrypt کە password_hash() بەکاری دەهێنێت.
Watch 0:00–8:45: from plain text up to slow hashing (bcrypt).
لە 0:00 تا 8:45 سەیر بکە: لە دەقی ئاساییەوە تا هێواش وەک bcrypt.
Step 3: the login pageهەنگاوی ٣: لاپەڕەی چوونەژوورەوە
login.php lets users log in to their account. It has a form asking for email and password, and PHP code that runs a SELECT query. The lab explains the idea: after running the query, check whether the result has exactly one row. If it does, a matching user was found: fetch the user's information from the database, and then create a session (or a cookie) for that user.
login.php ڕێگە بە بەکارهێنەران دەدات بچنە ناو . هەیە کە و دەپرسێت و کۆدی PHP کە SELECT کارپێدەکات. تاقیگەکە بیرۆکەکە ڕوون دەکاتەوە: دوای کارپێکردنی ، بپشکنە ئایا ئەنجامەکە تەنها یەک ڕیزی هەیە. ئەگەر وا بوو، هاوتا دۆزرایەوە: زانیارییەکانی لە بهێنە و پاشان (یان ) بۆ ئەو بەکارهێنەرە دروست بکە.
$result= mysqli_query ($connect, $query);
$num_row=mysqli_num_rows($result);
//check the row number is equal to 1, so we founded matched user
if($num_row==1){
//fetch user information and put them in array
$row=mysqli_fetch_array($result, MYSQLI_ASSOC);
//HERE YOU CREATE SESSION OR COOKIE
}Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
mysqli_num_rows()counts the rows in the result of a SELECT.mysqli_num_rows()ڕیزەکانی ئەنجامی SELECTێک دەژمێرێت.- Lineدێڕی 4
Exactly one row = one matching user. (“founded” should be “found”.)
تەنها یەک ڕیز = یەک هاوتا. («founded» دەبێت «found» بێت.)
- Lineدێڕی 6
mysqli_fetch_array($result, MYSQLI_ASSOC)returns the row as an associative array, the same asmysqli_fetch_assoc($result).mysqli_fetch_array($result, MYSQLI_ASSOC)ڕیزەکە وەک دەگەڕێنێتەوە، هەمان شتیmysqli_fetch_assoc($result).
Step 3: the login pageهەنگاوی ٣: لاپەڕەی چوونەژوورەوە
Step 4: create the session in login.phpهەنگاوی ٤: سێشنەکە لە login.phpـدا دروست بکە
Inside that if, the lab creates the session. First call session_start(). Then create a session variable with a name and give it a value from the database. The syntax is $_SESSION['session name'] = 'session value';. The lab stores the user's id, and then sends the user to index.php. This is the code as printed in the lab:
لە ناو ئەو ifـەدا، تاقیگەکە دروست دەکات. سەرەتا session_start() بانگ بکە. پاشان بە ناوێک دروست بکە و لە پێ بدە. بریتییە لە $_SESSION['session name'] = 'session value';. تاقیگەکە ناسێنەری (id) هەڵدەگرێت و پاشان دەنێرێت بۆ index.php. ئەمە کۆدەکەیە وەک لە تاقیگەکەدا چاپ کراوە:
$result= mysqli_query ($connect, $query);
$num_row=mysqli_num_rows($result);
//check the the row number is equal to 1, so we found matched user
if($num_row==1){
//fetch user information and put them in array
$row=mysqli_fetch_array($result, MYSQLI_ASSOC);
//HERE YOU CREATE SESSION OR COOKIE
session_start();
$_SESSION[‘user_id’]=$row[‘id’];
header(‘Location: index.php’);
exit();
}Fatal error: Uncaught Error: Undefined constant "‘user_id’" … Fatal error: Uncaught Error: Undefined constant "index" …
In the corrected login.php below, these lines become:
لە login.phpـی ڕاستکراوی خوارەوەدا، ئەم دێڕانە دەبن بە:
if (password_verify($password, $row['password'])) {
// HERE WE CREATE THE SESSION
session_regenerate_id(true); // new session id after login
$_SESSION['user_id'] = $row['id'];
$_SESSION['username'] = $row['username'];
header('Location: index.php');
exit; // stop: nothing else may run
}Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 1
The password is checked in PHP against the stored hash.
لە PHPـدا بەرامبەر بە هاشە خەزنکراوەکە دەپشکنرێت.
- Lineدێڕی 3
A new session ID, so that an ID known before login is useless ().
نوێ، بۆ ئەوەی ناسێنەرێک کە پێش زانرابێت بێسوود بێت ().
- Lineدێڕی 4-5
Store the id (and the name, for greeting the user). The password is never stored in the session.
ناسێنەرەکە (و ناوەکە، بۆ بەخێرهاتنی ) هەڵبگرە. هەرگیز لە هەڵناگیرێت.
- Lineدێڕی 7
exitends the script at once (Why exit must follow header()).exitدەستبەجێ کۆتایی پێدەهێنێت (بۆچی دەبێت exit دوای header() بێت).
Step 3: the login pageهەنگاوی ٣: لاپەڕەی چوونەژوورەوە
The lab’s complete login.phplogin.phpـی تەواوی تاقیگەکە
The lab PDF prints the whole file like this (curly quotes as printed, so it cannot run as it is):
PDFـی تاقیگەکە هەموو بەم شێوەیە چاپ دەکات ( خوار وەک چاپکراوە، بۆیە وەک خۆی کار ناکات):
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<form align="center" method="post" action="<?php echo
htmlspecialchars($_SERVER["PHP_SELF"]) ?>">
Email: <input type="text" name="email" size="30"><br><br> Password:
<input type="password" name="pass1" size="30"><br><br>
<input type="submit" value="login" name="submit"><br>
<?php
if(isset($_POST["submit"])){
if(empty($_POST["email"])) {
echo"Please enter your email address";
}elseif(empty($_POST["pass1"])) {
echo"Please enter your password";
} else{
//getting values from form inputs
$email=$_POST["email"];
$password=$_POST["pass1"];
$password= sha1($password);
require './connection.php';
$query="SELECT * FROM Users WHERE email='$email' AND password='$password'";
$result= mysqli_query ($connect, $query);
$num_row=mysqli_num_rows($result);
//check the the row number is equal to 1, so we found matched user
if($num_row==1){
//fetch user information and put them in array
$row=mysqli_fetch_array($result, MYSQLI_ASSOC);
//HERE YOU CREATE SESSION OR COOKIE
session_start();
$_SESSION[‘user_id’] = $row[‘id’];
header(‘Location: index.php’);
exit();
}else{
echo"error occured, please try again".mysql_error();
}
//close the connecton
mysqli_close($connect);
}
}
?>
</form>Step 3: the login pageهەنگاوی ٣: لاپەڕەی چوونەژوورەوە
SessionLab’s login.phplogin.phpـی SessionLab
The in-class version puts the PHP at the top and uses straight quotes, so it runs:
وەشانی ناو پۆل PHP دەخاتە سەرەوە و ڕاست بەکار دەهێنێت، بۆیە کار دەکات:
<?php
include('connection.php');
if (isset($_POST['loginBtn']))
{
$email = $_POST['emailInput'];
$password = $_POST['passwordInput'];
$query = "SELECT * FROM Users WHERE email='$email' AND password='$password'";
$result = mysqli_query ($connect, $query);
while ($row = mysqli_fetch_assoc($result))
{
//HERE YOU CREATE SESSION OR COOKIE
session_start();
$_SESSION['user_id'] = $row['id'];
//setcookie('user_id', $row['id'], time() + 50, "/index.php");
header('Location:index.php');
exit();
}
}
?>
<html>
<body>
<form method="post">
Email: <input type="text" name="emailInput"><br>
Password: <input type="text" name="passwordInput"><br>
<input type="submit" name="loginBtn" value="Login">
</form>
</body>
</html>Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 9
The email and password are pasted into the SQL: SQL injection. The password is compared as plain text.
و ڕاستەوخۆ خراونەتە ناو SQL: . وەک دەقی سادە بەراورد دەکرێت.
- Lineدێڕی 12
A
whileloop used as “if there is a row”.if ($row = mysqli_fetch_assoc($result))says it more clearly.whileکە وەک «ئەگەر ڕیزێک هەبێت» بەکارهاتووە.if ($row = mysqli_fetch_assoc($result))ڕوونتر دەیڵێت.- Lineدێڕی 18
The cookie alternative, commented out (see below).
جێگرەوەی ، کراوە بە (خوارەوە ببینە).
| We sent…ناردمان… | What happened (PHP 8.5)چی ڕوویدا (PHP 8.5) |
|---|---|
| the right email with a wrong password ڕاست لەگەڵ هەڵە | The form appears again, with no message at all. دیسان دەردەکەوێت، بەبێ هیچ پەیامێک. |
the email x' OR '1'='1' -- and any password x' OR '1'='1' -- و هەر | 302 redirect to index.php: logged in as the first user, with no password.302 بۆ index.php: وەک یەکەم چووە ژوورەوە، بەبێ . |
Step 3: the login pageهەنگاوی ٣: لاپەڕەی چوونەژوورەوە
A student’s login.phplogin.phpـی خوێندکارێک
A student's practice project checks a username and an md5() password, and stores two session variables:
پرۆژەی ڕاهێنانی خوێندکارێک ناوی و md5() دەپشکنێت و دوو هەڵدەگرێت:
<!DOCTYPE html>
<html lang="en">
<body>
<?php
include("config.php");
if (isset($_POST['Login'])){
$username = $_POST['user'];
$password= md5($_POST['pass']);
$selectQuery = mysqli_query($connection,"select * from users where username ='$username' and password = '$password' ");
if (mysqli_num_rows($selectQuery)){
while ($row = mysqli_fetch_assoc($selectQuery)){
session_start();
$_SESSION['userId'] = $row['id'];
$_SESSION['userInput'] = $row['username'];
header('location: view.php');
exit();
}
}else{
echo "username or password failed.";
}
}
?>
<form method="post">
username : <input type="text" name="user"><br><br>
password : <input type="password" name="pass"><br><br>
<input type="submit" name="Login" value="Login">
</form>
</body>
</html>Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 1
HTML output starts on line 1, so
session_start()andheader()come too late (they work only with output buffering).HTML لە دێڕی 1ـەوە دەست پێدەکات، بۆیە
session_start()وheader()زۆر درەنگ دێن (تەنها بە کار دەکەن).- Lineدێڕی 11
md5()is as unsafe for passwords assha1().md5()بۆ هێندەیsha1()ناسەلامەتە.- Lineدێڕی 13
SQL injection again: try the username
admin' --.دیسان : ناوی
admin' --تاقی بکەرەوە.- Lineدێڕی 24-25
Good:
exit()right afterheader(). But the target isview.php, while other pages of this project useView.php. On Linux file names are case-sensitive, so one of them fails there.باشە:
exit()ڕاستەوخۆ دوایheader(). بەڵام ئامانجەکەview.phpـە، لە کاتێکدا لاپەڕەکانی تری ئەم پرۆژەیەView.phpبەکار دەهێنن. لەسەر Linux ناوی ، بۆیە یەکێکیان لەوێ شکست دەهێنێت.
Step 3: the login pageهەنگاوی ٣: لاپەڕەی چوونەژوورەوە
How SQL injection logs in without a passwordتێخزاندنی SQL چۆن بەبێ تێپەڕەوشە دەچێتە ژوورەوە
An attack in which someone types SQL code into a form or URL. If the script puts that input straight into its query, the attacker's code runs and can show or delete data. stop it.
هێرشێکە کە تێیدا هێرشبەر کۆدی SQL لە یان دەنووسێت. ئەگەر کۆدەکە ئەو نووسینە ڕاستەوخۆ لەگەڵ تێکەڵ بکات، کۆدی هێرشبەرەکە جێبەجێ دەبێت و دەتوانێت دراوەکان ئاشکرا بکات یان بیانسڕێتەوە. ڕێگەی لێ دەگرن.
All three login pages above build the SQL by pasting the user's text into it. Watch what happens when the “email” is x' OR '1'='1' -- . This demo uses a small SQLite table, but the SQL is the same in MySQL:
هەر سێ لاپەڕەی سەرەوە SQLـەکە بە دانانی دەقی لە ناویدا دروست دەکەن. سەیر بکە چی ڕوودەدات کاتێک «» x' OR '1'='1' -- ـە. ئەم پیشاندانە بچووکی SQLite بەکار دەهێنێت، بەڵام SQLـەکە لە MySQLـیشدا هەمانە:
<?php
// A small users table in memory (SQLite), only to show the attack
$db = new PDO('sqlite::memory:');
$db->exec("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT, email TEXT, password TEXT)");
$db->exec("INSERT INTO users (username, email, password) VALUES
('admin', 'admin@example.com', 'k8#Lq2!z'),
('Aram', 'aram@example.com', 'tea-and-bread-42')");
// What an attacker types into the Email box (the password does not matter)
$email = "x' OR '1'='1' -- ";
$password = "anything";
// UNSAFE: the input becomes part of the SQL text (like the lab's login.php)
$query = "SELECT * FROM users WHERE email='$email' AND password='$password'";
echo $query . "\n";
$rows = $db->query($query)->fetchAll(PDO::FETCH_ASSOC);
echo "Rows found: " . count($rows) . ", logged in as: " . $rows[0]['username'] . "\n";
// SAFE: a prepared statement keeps the input as data only
$stmt = $db->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
echo "Prepared statement, rows found: " . count($stmt->fetchAll()) . "\n";SELECT * FROM users WHERE email='x' OR '1'='1' -- ' AND password='anything' Rows found: 2, logged in as: admin Prepared statement, rows found: 0
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 14
The attacker's quote
'closes the email string;OR '1'='1'is always true;--turns the rest (the password test) into a comment.'ـی هێرشبەر دادەخات؛OR '1'='1'هەمیشە ڕاستە؛--باقی دێڕەکە (پشکنینی ) دەکات بە .- Lineدێڕی 20-21
With a prepared statement the whole text is just an email address, and no user has it.
بە هەموو دەقەکە تەنها ناونیشانێکی ئیمەیڵە و هیچ ئەوەی نییە.
The student's version falls the same way with the username admin' -- : the comment removes the password check, so the attacker becomes admin:
وەشانەکەی خوێندکار بە هەمان شێوە بە ناوی admin' -- دەڕووخێت: پشکنینی لادەبات، بۆیە هێرشبەر دەبێت بە admin:
<?php
$db = new PDO('sqlite::memory:');
$db->exec("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT, password TEXT)");
$db->exec("INSERT INTO users (username, password) VALUES ('admin', '" . md5('k8#Lq2!z') . "')");
$username = "admin' -- ";
$password = md5("I do not know it");
$query = "select * from users where username ='$username' and password = '$password' ";
echo $query . "\n";
$rows = $db->query($query)->fetchAll(PDO::FETCH_ASSOC);
echo "Rows found: " . count($rows) . ", logged in as: " . $rows[0]['username'] . "\n";select * from users where username ='admin' -- ' and password = 'acf7a170ff3c2f7c5e79a387ee759af3' Rows found: 1, logged in as: admin
The cure is always the same: never paste user input into SQL. Use a prepared statement, look the user up by email only, and check the password in PHP with password_verify(). More about this in Database security.
چارەسەرەکە هەمیشە هەمانە: هەرگیز ڕاستەوخۆ مەخەرە ناو SQL. بەکاربهێنە، تەنها بە بدۆزەوە و لە PHPـدا بە password_verify() بپشکنە. زیاتر لە ئاسایشی بنکەدراوە.
How SQL injection changes a PHP database query, why escaping is only a weak fix, and why prepared statements are the right one.
چۆن لە دەگۆڕێت، بۆچی escaping تەنها چارەسەرێکی لاوازە و بۆچی چارەسەری ڕاستن.
Watch 2:17–8:43: how injection works and how prepared statements stop it.
لە 2:17 تا 8:43 سەیر بکە: چۆن کار دەکات و چۆن ڕێگەی لێ دەگرن.
Step 3: the login pageهەنگاوی ٣: لاپەڕەی چوونەژوورەوە
The corrected login.phplogin.phpـی ڕاستکراو
Here is the whole corrected login.php. It keeps the lab's steps (form, SELECT, exactly one row, session, redirect) and fixes every problem shown above:
ئەمە هەموو login.phpـی ڕاستکراوە. هەنگاوەکانی تاقیگەکە دەهێڵێتەوە (، SELECT، تەنها یەک ڕیز، ، ) و هەموو ئەو کێشانەی سەرەوە چاک دەکات:
<?php
session_start(); // before any output
require 'connection.php';
// Already logged in? Go straight to the protected page.
if (isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$message = '';
if (isset($_POST['loginBtn'])) {
$email = trim($_POST['emailInput'] ?? '');
$password = $_POST['passwordInput'] ?? '';
if ($email === '') {
$message = 'Please enter your email address.';
} elseif ($password === '') {
$message = 'Please enter your password.';
} else {
// Find the user by email only; the password is checked in PHP below
$stmt = mysqli_prepare($connect, 'SELECT id, username, password FROM users WHERE email = ?');
mysqli_stmt_bind_param($stmt, 's', $email);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
// Exactly one row: a user with this email exists (email is UNIQUE)
if (mysqli_num_rows($result) === 1) {
$row = mysqli_fetch_array($result, MYSQLI_ASSOC); // the row as an associative array
if (password_verify($password, $row['password'])) {
// HERE WE CREATE THE SESSION
session_regenerate_id(true); // new session id after login
$_SESSION['user_id'] = $row['id'];
$_SESSION['username'] = $row['username'];
header('Location: index.php');
exit; // stop: nothing else may run
}
}
// One message for "unknown email" and "wrong password"
$message = 'Wrong email or password.';
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Log in</title>
</head>
<body>
<h1>Log in</h1>
<p><?php echo htmlspecialchars($message); ?></p>
<form method="post">
Email: <input type="email" name="emailInput"><br>
Password: <input type="password" name="passwordInput"><br>
<input type="submit" name="loginBtn" value="Login">
</form>
<p>No account yet? <a href="register.php">Create one</a></p>
</body>
</html>Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
session_start()on the first line, before any output.session_start()لە یەکەم دێڕدا، پێش هەر .- Lineدێڕی 6-9
Already logged in? Do not show the form again.
پێشتر چوویتەتە ژوورەوە؟ دیسان پیشان مەدە.
- Lineدێڕی 23-26
A prepared SELECT by email only.
mysqli_stmt_get_result()gives a normal result (it needs the mysqlnd driver, which XAMPP includes).SELECTێکی ئامادەکراو تەنها بە .
mysqli_stmt_get_result()ئەنجامێکی ئاسایی دەدات (پێویستی بە mysqlnd هەیە، کە XAMPP تێیدایە).- Lineدێڕی 29-30
The lab's own check: exactly one row, fetched as an associative array.
پشکنینەکەی خودی تاقیگەکە: تەنها یەک ڕیز، وەک هێنراوە.
- Lineدێڕی 32
password_verify()compares the typed password with the stored hash.password_verify()نووسراوەکە لەگەڵ هاشە خەزنکراوەکە بەراورد دەکات.- Lineدێڕی 34
New session ID after login.
نوێ دوای .
- Lineدێڕی 42
One message for both cases, so attackers cannot test which emails exist.
یەک پەیام بۆ هەردوو حاڵەتەکە، بۆ ئەوەی هێرشبەران نەتوانن تاقی بکەنەوە کام هەن.
- Lineدێڕی 56-57
type="password"hides what is typed.type="password"ئەوەی دەنووسرێت دەشارێتەوە.
| Request | Server response |
|---|---|
| POST: right email, wrong passwordPOST: ڕاست، هەڵە | 200 — the form again, with “Wrong email or password.”200 — دیسان، لەگەڵ «Wrong email or password.» |
POST: email x' OR '1'='1' -- POST: هێرش بە x' OR '1'='1' -- | 200 — “Wrong email or password.” (the attack fails)200 — «Wrong email or password.» (هێرشەکە شکست دەهێنێت) |
| POST: right email and passwordPOST: و ڕاست | 302, Location: index.php, and a new Set-Cookie: PHPSESSID=… (the regenerated ID)302، Location: index.php و Set-Cookie: PHPSESSID=…ی نوێ (ناسێنەرە نوێکراوەکە) |
| GET login.php while logged inGET login.php لە کاتی بوون لە ژوورەوە | 302, Location: index.php302، Location: index.php |
Step 5: protect every other pageهەنگاوی ٥: هەموو لاپەڕەکانی تر بپارێزە
After a user logs in, every other page must check whether the session exists. If it does, the user may see the page. If the user has not logged in, the page must send them to the login page first. For example, to protect index.php, call session_start() and then check the session. The lab prints this:
دوای ئەوەی دەچێتە ژوورەوە، هەموو لاپەڕەکانی تر دەبێت بپشکنن ئایا هەیە. ئەگەر هەبێت، بۆی هەیە لاپەڕەکە ببینێت. ئەگەر نەچووبێتە ژوورەوە، لاپەڕەکە دەبێت سەرەتا بینێرێت بۆ لاپەڕەی . بۆ نموونە، بۆ پاراستنی index.php، session_start() بانگ بکە و پاشان بپشکنە. تاقیگەکە ئەمە چاپ دەکات:
Session_start();
If(! isset($_SESSION[‘user_id’])){
Header(‘Location:login.php’);
}<? Php
Session_start();
If(! isset($_SESSION[‘user_id’])){
Header(‘Location:login.php’);
}
echo “<h1> Welcome to website</h1>”;
?>With short_open_tag = On: Parse error: syntax error, unexpected identifier "Session_start" in index.php on line 2 With short_open_tag = Off (the php.ini default): the whole file is sent to the browser as plain text, PHP code included.
SessionLab's index.php uses straight quotes and runs, but it also forgets exit:
index.phpـی SessionLab ڕاست بەکار دەهێنێت و کار دەکات، بەڵام ئەویش exitی لە بیر دەچێت:
<?php
session_start();
if(!isset($_SESSION['user_id'])){
header('Location: login.php');
}
echo "<h1>Welcome to website</h1>";
echo "<a href='logout.php'>Logout</a>";
?>HTTP/1.1 302 Found Set-Cookie: PHPSESSID=…; path=/ Location: login.php <h1>Welcome to website</h1><a href='logout.php'>Logout</a>
The browser follows the redirect, so a normal visitor does not see the page. But the “protected” HTML is in the response, and tools such as curl show it. The same happened after logout with the old cookie.
بەدوای دەڕوات، بۆیە سەردانکەرێکی ئاسایی لاپەڕەکە نابینێت. بەڵام HTMLـە «پارێزراوەکە» لە ناو و ئامرازی وەک curl پیشانی دەدەن. هەمان شت دوای بە کۆنەکە ڕوویدا.
Step 5: protect every other pageهەنگاوی ٥: هەموو لاپەڕەکانی تر بپارێزە
The corrected index.phpindex.phpـی ڕاستکراو
The corrected index.php checks the session, redirects and stops with exit, and escapes the name it prints:
index.phpـی ڕاستکراو دەپشکنێت، سەردانکەر ئاڕاستە دەکاتەوە و دەوەستێت بە exit و ئەو ناوەی چاپی دەکات دەیپارێزێت:
<?php
session_start();
// Not logged in? Send the visitor to the login page and STOP the script.
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Home</title>
</head>
<body>
<h1>Welcome to the website, <?php echo htmlspecialchars($_SESSION['username']); ?>!</h1>
<p><a href="logout.php">Logout</a></p>
</body>
</html>Welcome to the website, Aram! Logout
HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Home</title>
</head>
<body>
<h1>Welcome to the website, Aram!</h1>
<p><a href="logout.php">Logout</a></p>
</body>
</html>Not logged in: “302 Found, Location: login.php” with an empty body.
نەچووەتە ژوورەوە: «302 Found، Location: login.php» بە ناوەڕۆکێکی بەتاڵ.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
Open the session (before any output).
بکەرەوە (پێش هەر ).
- Lineدێڕی 5
No
user_idin the session: this visitor has not logged in.هیچ
user_idێک لە نییە: ئەم سەردانکەرە نەچووەتە ژوورەوە.- Lineدێڕی 7
Stop. Nothing below this line is sent.
بوەستە. هیچ شتێکی خوار ئەم دێڕە نانێردرێت.
- Lineدێڕی 17
Greet the user by name, escaped.
بە ناوی خۆیەوە بەخێرهاتنی بکە، بە پاراستنەوە.
Step 5: protect every other pageهەنگاوی ٥: هەموو لاپەڕەکانی تر بپارێزە
One check for all pages: auth.phpیەک پشکنین بۆ هەموو لاپەڕەکان: auth.php
A real site has many protected pages. Instead of copying the check into each one, put it in auth.php and require it on the first line of every protected page. If you ever improve the check, you change one file.
ڕاستەقینە زۆر لاپەڕەی پارێزراوی هەیە. لە جیاتی کۆپیکردنی پشکنینەکە بۆ هەریەکەیان، بیخە ناو auth.php و لە یەکەم دێڕی هەموو لاپەڕەیەکی پارێزراودا requireی بکە. ئەگەر ڕۆژێک پشکنینەکەت باشتر کرد، تەنها یەک دەگۆڕیت.
<?php
// auth.php: require this file at the very top of every protected page
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}<?php
require 'auth.php'; // only logged-in users get past this line
?>
<!DOCTYPE html>
<html>
<body>
<h1>Your profile</h1>
<p>You are logged in as <?php echo htmlspecialchars($_SESSION['username']); ?>.</p>
</body>
</html>Your profile You are logged in as Aram.
HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<!DOCTYPE html>
<html>
<body>
<h1>Your profile</h1>
<p>You are logged in as Aram.</p>
</body>
</html>Final step: the logout pageهەنگاوی کۆتایی: لاپەڕەی چوونەدەرەوە
In every web-based project, when users log out of their account, the application should delete the session. The lab's logout.php (the same in the PDF and in SessionLab) does this:
لە هەموو پرۆژەیەکی وێبیدا، کاتێک بەکارهێنەران لە دەچنە دەرەوە، دەبێت بسڕێتەوە. logout.phpـی تاقیگەکە (لە PDF و SessionLabـدا وەک یەکە) ئەمە دەکات:
<?php
session_start();
//clear session variable
unset($_SESSION["user_id"]);
//destroy session on the server
session_destroy();
//redirect user to login page
header("Location: login.php");HTTP/1.1 302 Found Location: login.php
No Set-Cookie header: the PHPSESSID cookie stays in the browser.
هیچ سەردێڕێکی Set-Cookie نییە: PHPSESSID لە دەمێنێتەوە.
A student's version clears all variables with session_unset() before destroying the session:
وەشانی خوێندکارێک پێش لەناوبردنی بە session_unset() هەموو بەتاڵ دەکات:
<?php
session_start();
session_unset();
session_destroy();
header('location: login.php');
?><?php
session_start(); // open the current session so that we can end it
$_SESSION = []; // 1. remove all session variables
// 2. delete the session cookie in the browser (same name, path and domain; time in the past)
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params['path'], $params['domain'], $params['secure'], $params['httponly']);
session_destroy(); // 3. delete the session data on the server
header('Location: login.php'); // 4. back to the login page
exit;HTTP/1.1 302 Found Set-Cookie: PHPSESSID=deleted; expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; path=/ Location: login.php
Opening index.php afterwards gives “302 Found, Location: login.php”.
کردنەوەی index.php دوای ئەوە «302 Found، Location: login.php» دەدات.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
We need the session open to end it.
دەبێت کراوە بێت بۆ ئەوەی کۆتایی پێ بهێنین.
- Lineدێڕی 4
1. Remove all variables.
١. هەموو لاببە.
- Lineدێڕی 7-9
2. Delete the session cookie with the same name, path and domain.
٢. بە هەمان ناو، و دۆمەین بسڕەوە.
- Lineدێڕی 11
3. Delete the data on the server.
٣. لەسەر بسڕەوە.
- Lineدێڕی 13-14
4. Back to the login page, and stop.
٤. گەڕانەوە بۆ لاپەڕەی و وەستان.
Try the whole systemهەموو سیستەمەکە تاقی بکەرەوە
- Start XAMPPXAMPP دەست پێبکە
Open the XAMPP Control Panel and start Apache and MySQL (XAMPP).
XAMPP بکەرەوە و Apache و MySQL دەست پێبکە (XAMPP).
- Create the databaseبنکەدراوە دروست بکە
In phpMyAdmin (
http://localhost/phpmyadmin), run the SQL from Database and table.لە phpMyAdminدا (
http://localhost/phpmyadmin)، SQLـەکەی بنکەدراوە و خشتە کارپێبکە. - Copy the filesفایلەکان کۆپی بکە
Put
connection.php,register.php,login.php,index.php,logout.php(andauth.php) into a folder such ashtdocs/session-lab/.connection.php،register.php،login.php،index.php،logout.php(وauth.php) بخە ناو فۆڵدەرێکی وەکhtdocs/session-lab/. - Registerخۆت تۆمار بکە
Open
http://localhost/session-lab/register.phpand create an account. Look at theuserstable in phpMyAdmin: the password column holds a hash that starts with$2y$.http://localhost/session-lab/register.phpبکەرەوە و هەژمارێک دروست بکە. لە phpMyAdminدا سەیریusersبکە: password هاشێکی تێدایە کە بە$2y$دەست پێدەکات. - Try the protectionپاراستنەکە تاقی بکەرەوە
Open
index.phpdirectly: you land onlogin.php. Log in with a wrong password, then with the right one.ڕاستەوخۆ
index.phpبکەرەوە: دەگەیتەlogin.php. بە هەڵە بچۆ ژوورەوە، پاشان بە ڕاستەکە. - Watch the cookieسەیری کووکییەکە بکە
In the developer tools (F12 → Application/Storage → Cookies), note the
PHPSESSIDvalue before and after login: it changes. After logout it is gone.لە ئامرازەکانی پەرەپێدەردا (F12 ← Application/Storage ← Cookies)،
PHPSESSIDپێش و دوای بنووسە: دەگۆڕێت. دوای نامێنێت.
We ran the corrected files through PHP 8.5 request by request. This is what a browser receives:
فایلە ڕاستکراوەکانمان بە بە PHP 8.5 کارپێکرد. ئەمە ئەوەیە کە وەریدەگرێت:
| #ژ. | Request | Statusدۆخ | Important headersسەردێڕە گرنگەکان | The page showsلاپەڕەکە پیشان دەدات |
|---|---|---|---|---|
| 1 | GET /index.php | 302 | Set-Cookie: PHPSESSID=1b1e…; Location: login.php | (nothing, redirect)(هیچ، ) |
| 2 | POST /register.php (passwords differ)POST /register.php ( جیاوازن) | 200 | — | The passwords did not match. Please try again. |
| 3 | POST /register.php (correct)POST /register.php (دروست) | 200 | — | Account created successfully. You can log in now. |
| 4 | POST /login.php (wrong password)POST /login.php ( هەڵە) | 200 | — | Wrong email or password. |
| 5 | POST /login.php (correct)POST /login.php (دروست) | 302 | Set-Cookie: PHPSESSID=5642… Location: index.php | (redirect; the session ID changed)(؛ گۆڕا) |
| 6 | GET /index.php | 200 | — | Welcome to the website, Aram! |
| 7 | GET /logout.php | 302 | Set-Cookie: PHPSESSID=deleted; Max-Age=0 Location: login.php | (redirect to the login page)( بۆ لاپەڕەی ) |
| 8 | GET /index.php | 302 | Set-Cookie: PHPSESSID=7b67…; Location: login.php | (redirect: logged out)(: چووەتە دەرەوە) |
The lab’s cookie version, and why it is unsafeوەشانی کووکیی تاقیگەکە و بۆچی ناسەلامەتە
At the end, the lab shows how to use a cookie instead of the session, in three steps. Learn the steps, because they practise setcookie(). Then learn why you must not log users in this way.
لە کۆتاییدا، تاقیگەکە بە سێ هەنگاو پیشان دەدات چۆن لە جیاتی بەکاربهێنیت. هەنگاوەکان فێر بە، چونکە ڕاهێنان لەسەر setcookie() دەکەن. پاشان فێر بە بۆچی نابێت بەکارهێنەران بەم شێوەیە بخەیتە ژوورەوە.
1. Create a cookie in login.php after a successful login
١. دوای چوونەژوورەوەی سەرکەوتوو لە login.phpـدا کووکییەک دروست بکە
// time()+3600 set cookie for one hour
setcookie ('user_id', $row ['id'], time()+3600, '/', '', 0,
0);Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
Name
user_id, value = the user's id, one hour (time()+3600), path/, domain''(this host), secure0, httponly0. ($row ['id']with a space is legal PHP.)ناو
user_id، نرخ = ناسێنەری ، یەک کاتژمێر (time()+3600)،/، دۆمەین''(هەمان )، secure0، httponly0. ($row ['id']بە بۆشاییەوە PHPـی یاساییە.)
2. Check the cookie at the top of every page
٢. لە سەرەوەی هەموو لاپەڕەیەکدا کووکییەکە بپشکنە
if (!isset($_COOKIE['user_id'])) {
//redirect user to login page
header(“Location: login.php”)
}Parse error: syntax error, unexpected token "}" in check.php on line 5
3. Delete the cookie in logout.php
٣. لە logout.phpـدا کووکییەکە بسڕەوە
The lab: when users log out, delete the cookie with the same function, the same cookie name, an empty value and a time in the past.
تاقیگەکە: کاتێک بەکارهێنەران دەچنە دەرەوە، بە هەمان ، هەمان ناوی ، بەتاڵ و کاتێکی ڕابردوو بسڕەوە.
//check for cookie is not set then redirect to login.php page
if (!isset($_COOKIE['user_id'])) {
header(“Location: login.php”);
} else {
// Delete the cookies:
setcookie ('user_id', ' ', time()-3600, '/', '', 0, 0);
}The lab’s cookie version, and why it is unsafeوەشانی کووکیی تاقیگەکە و بۆچی ناسەلامەتە
The three steps with the syntax fixed (still unsafe)سێ هەنگاوەکە دوای ڕاستکردنەوەی داڕستن (هێشتا ناسەلامەتن)
With the syntax fixed, the three steps run without errors (each was run with PHP 8.5). They are still unsafe: fixing the syntax does not fix the idea.
دوای ڕاستکردنەوەی ، سێ هەنگاوەکە بەبێ کار دەکەن (هەریەکەیان بە PHP 8.5 کارپێکراوە). هێشتا ناسەلامەتن: ڕاستکردنەوەی بیرۆکەکە چاک ناکات.
// UNSAFE (Lab 9 cookie version, for learning only): anyone can create this cookie by hand
setcookie('user_id', $row['id'], time() + 3600, '/', '', false, true);<?php
// Lab 9 cookie version (UNSAFE, for learning only): it only checks that a cookie exists
if (!isset($_COOKIE['user_id'])) {
header('Location: login.php'); // redirect user to login page
exit;
}
echo "<h1>Welcome! You are logged in as user number " . htmlspecialchars($_COOKIE['user_id']) . ".</h1>";<?php
// Lab 9 cookie version (UNSAFE, for learning only)
// check for the cookie: if it is not set, go to the login page
if (!isset($_COOKIE['user_id'])) {
header('Location: login.php');
exit;
}
// Delete the cookie: same name, path and domain as setcookie() used, empty value, time in the past
setcookie('user_id', '', time() - 3600, '/', '', false, true);
header('Location: login.php');
exit;HTTP/1.1 302 Found Set-Cookie: user_id=deleted; expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; path=/; HttpOnly Location: login.php
The lab’s cookie version, and why it is unsafeوەشانی کووکیی تاقیگەکە و بۆچی ناسەلامەتە
Why the cookie version is not a real loginبۆچی وەشانی کووکی چوونەژوورەوەی ڕاستەقینە نییە
Look at what the server checks: only that a cookie named user_id exists. A cookie lives in the visitor's browser, and the visitor controls it completely. Anyone can open the developer tools, create a cookie user_id=1 by hand, and open index.php. We tried it on the fixed cookie version, with a browser that never logged in:
سەیری ئەوە بکە کە دەیپشکنێت: تەنها ئەوەی کە بە ناوی user_id هەیە. لە سەردانکەردا دەژی و سەردانکەر بە تەواوی کۆنترۆڵی دەکات. هەر کەسێک دەتوانێت ئامرازەکانی پەرەپێدەر بکاتەوە، بە دەست user_id=1 دروست بکات و index.php بکاتەوە. لەسەر وەشانی ڕاستکراو تاقیمان کردەوە، بە کە هەرگیز نەچووبووە ژوورەوە:
GET /index.php HTTP/1.1
Host: localhost
Cookie: user_id=1
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
<h1>Welcome! You are logged in as user number 1.</h1>Admins and users: a login with rolesبەڕێوەبەر و بەکارهێنەر: چوونەژوورەوە بە ڕۆڵ
A student's project has two kinds of users, “Admin” and “User”. The login form has a where the visitor chooses one, and the query checks it. This is the part of the code that is visible in the screenshot (one long line is cut off):
پرۆژەی خوێندکارێک دوو جۆر هەیە، «Admin» و «User». هەیە کە سەردانکەر یەکێکیان تێدا هەڵدەبژێرێت و دەیپشکنێت. ئەمە ئەو بەشەی کۆدەکەیە کە لە وێنەکەدا دیارە (دێڕێکی درێژ بڕاوە):
include 'connection.php';
if (isset($_POST['LoginButton']))
{
$username = $_POST['usernameInput'];
$password = $_POST['passwordInput'];
$adminOrUser = $_POST['AdminOrUserRadio'];
$selectQuery = mysqli_query($connect, "select * from UserManagementTable where Username='$username' and Password='$password' and AdminOrUser='$adminOrUser'" // (the line is cut off in the screenshot)
while ($row = mysqli_fetch_assoc($selectQuery))
{
if ($selectQuery)
{
if ($row['AdminOrUser'] == "Admin")
{
//If admin logged in
//echo "Admin with the username of $username and password of $password has logged in.";
// setcookie("user_id", $row['UserID'], time() + 1000);
// setcookie("username", $row['Username'], time() + 1000);
session_start();
$_SESSION["user_id"] = $row['UserID'];
$_SESSION["username"] = $row['Username'];
header('Location: usermanagement.php');
exit();
}
else if ($row['AdminOrUser'] == "User")
{
//If user logged in
echo "A user with the username of $username and password of $password has logged in.";
}Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 6
The role comes from a radio button, so the visitor chooses it.
ڕۆڵەکە لە دێت، بۆیە سەردانکەر هەڵیدەبژێرێت.
- Lineدێڕی 7
Plain-text password and SQL injection in one line.
دەقی سادە و لە یەک دێڕدا.
- Lineدێڕی 11
Always true inside the loop: the loop runs only when there are rows.
لە ناو هەمیشە ڕاستە: تەنها کاتێک کار دەکات کە ڕیز هەبێت.
- Lineدێڕی 31
It prints the password back on the screen.
دیسان لەسەر شاشەکە چاپ دەکات.
Admins and users: a login with rolesبەڕێوەبەر و بەکارهێنەر: چوونەژوورەوە بە ڕۆڵ
Roles done safelyڕۆڵ بە شێوەی سەلامەت
The corrected design: add a role column to the users table, let the database decide each user's role, copy it into the session at login, and check it on admin pages. Checking the role is ; checking the login is .
دیزاینی ڕاستکراو: role بۆ users زیاد بکە، با ڕۆڵی هەر دیاری بکات، لە کاتی بیگوازەرەوە بۆ ناو و لە لاپەڕەکانی بەڕێوەبەردا بیپشکنە. پشکنینی ڕۆڵ ؛ پشکنینی .
ALTER TABLE users ADD role VARCHAR(10) NOT NULL DEFAULT 'user';
-- make one account an administrator
UPDATE users SET role = 'admin' WHERE email = 'shilan@example.com';In login.php, select the role as well (SELECT id, username, password, role FROM users WHERE email = ?), and replace the success block with this:
لە login.phpـدا ڕۆڵەکەش هەڵبژێرە (SELECT id, username, password, role FROM users WHERE email = ?) و بلۆکی سەرکەوتن بەمە بگۆڕە:
if (password_verify($password, $row['password'])) {
session_regenerate_id(true);
$_SESSION['user_id'] = $row['id'];
$_SESSION['username'] = $row['username'];
$_SESSION['role'] = $row['role']; // from the database, not from the form
if ($row['role'] === 'admin') {
header('Location: usermanagement.php');
} else {
header('Location: index.php');
}
exit;
}<?php
session_start();
// 1. Authentication: is anybody logged in?
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
// 2. Authorization: is this user allowed to see this page?
if (($_SESSION['role'] ?? '') !== 'admin') {
http_response_code(403); // 403 = Forbidden
exit('Only administrators can open this page.');
}
?>
<!DOCTYPE html>
<html>
<body>
<h1>User management</h1>
<p>Welcome, administrator <?php echo htmlspecialchars($_SESSION['username']); ?>.</p>
</body>
</html>Admin Shilan: 200 User management — Welcome, administrator Shilan. Normal user Aram: 403 Only administrators can open this page. Not logged in: 302 Location: login.php
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 5-8
Authentication: anybody logged in?
: کەسێک چووەتە ژوورەوە؟
- Lineدێڕی 11-14
Authorization: is this user an admin? If not, answer 403 Forbidden and stop.
: ئەم بەکارهێنەرە بەڕێوەبەرە؟ ئەگەر نا، بە 403 Forbidden بدەرەوە و بوەستە.
Deciding what a logged-in user is allowed to see or do, for example letting only an admin delete users. It comes after .
بڕیاردانە لەسەر ئەوەی بەکارهێنەرێکی چووەژوورەوە بۆی هەیە چی ببینێت یان چی بکات، بۆ نموونە تەنها بەڕێوەبەر بتوانێت بەکارهێنەرەکان بسڕێتەوە. دوای دێت.
From the lab code to safe codeلە کۆدی تاقیگەکەوە بۆ کۆدی سەلامەت
| Lab / SessionLabتاقیگە / SessionLab | Why it is dangerousبۆچی مەترسیدارە | Safe versionوەشانی سەلامەت |
|---|---|---|
Passwords in plain text, sha1() or md5() بە دەقی سادە، sha1() یان md5() | A stolen table reveals or quickly gives up every password دزراو هەموو ئاشکرا دەکات یان بە خێرایی دەیاندات | password_hash() / password_verify() |
| User input pasted into SQL ڕاستەوخۆ لە ناو SQL | SQL injection: log in as anyone without a password: وەک هەر کەسێک بەبێ بچۆ ژوورەوە | mysqli_prepare() + mysqli_stmt_bind_param() |
session_start() / header() after HTMLsession_start() / header() دوای HTML | Works only with output buffering; breaks elsewhereتەنها بە کار دەکات؛ لە شوێنی تر تێک دەچێت | PHP block at the top of the fileبلۆکی PHP لە سەرەوەی |
No exit after header('Location: …')هیچ exitێک دوای header('Location: …') | The protected page is still sentلاپەڕە پارێزراوەکە هێشتا دەنێردرێت | header(…); exit; |
| Same session ID before and after loginهەمان پێش و دوای | Session fixation | session_regenerate_id(true) |
user_id in a plain cookieuser_id لە سادەدا | Anyone can type user_id=1هەر کەسێک دەتوانێت user_id=1 بنووسێت | $_SESSION['user_id']$_SESSION['user_id'] |
Logout without deleting the cookie, without exit بەبێ سڕینەوەی ، بەبێ exit | The old session ID stays usable as an empty sessionناسێنەرە کۆنەکەی وەک بەتاڵ بەکارهێنراو دەمێنێتەوە | $_SESSION = [], delete the cookie, session_destroy(), exit$_SESSION = []، سڕینەوەی ، session_destroy()، exit |
| “error occured” or the MySQL error for a wrong password«error occured» یان MySQL بۆ هەڵە | Confuses users, leaks detailsبەکارهێنەران سەرلێشێواو دەکات، وردەکاری ئاشکرا دەکات | “Wrong email or password.”پەیامی «Wrong email or password.» |
Password field type="text" type="text" | The password is visible on the screen لەسەر شاشەکە دەبینرێت | type="password" |
| Printing user data without escapingچاپکردنی بەبێ پاراستن | XSSهێرشی XSS | htmlspecialchars() |
Common mistakesهەڵە باوەکان
- Curly quotes from a word processor (
‘ ’ “ ”) in code: fatal errors. Copy code from a code editor, not from Word or a PDF. <? Phpinstead of<?php: a parse error, or the code is shown as text.- Forgetting
session_start()on a protected page:$_SESSIONdoes not exist on that page, so the check fails and every visitor is sent to the login page, even after logging in. - Different key names, such as
$_SESSION['userId']in login.php and$_SESSION['user_id']in index.php. Use one name everywhere. - A password column that is too short (for example
VARCHAR(40)):password_hash()needsVARCHAR(255), or the hash is cut and no login ever works. - Comparing the hash in SQL (
WHERE password = ?with a newpassword_hash()): it never matches, because every hash has its own salt. Usepassword_verify(). - File-name case:
View.phpandview.phpare the same file on Windows and macOS, but different on a Linux server.
- خوار لە پرۆگرامی نووسینەوە (
‘ ’ “ ”) لە کۆددا: . کۆد لە کۆپی بکە، نەک لە Word یان PDF. <? Phpلە جیاتی<?php: ، یان کۆدەکە وەک دەق پیشان دەدرێت.- لە بیرچوونی
session_start()لە لاپەڕەیەکی پارێزراودا:$_SESSIONلەو لاپەڕەیەدا بوونی نییە، بۆیە پشکنینەکە شکست دەهێنێت و هەموو سەردانکەرێک دەنێردرێت بۆ لاپەڕەی ، تەنانەت دوای چوونەژوورەوەش. - ناوی جیاواز بۆ ، وەک
$_SESSION['userId']لە login.php و$_SESSION['user_id']لە index.phpـدا. لە هەموو شوێنێک یەک ناو بەکاربهێنە. - زۆر کورت (بۆ نموونە
VARCHAR(40)):password_hash()پێویستی بەVARCHAR(255)هەیە، ئەگینا هاشەکە دەبڕدرێت و هیچ هەرگیز کار ناکات. - بەراوردکردنی هاش لە SQLـدا (
WHERE password = ?لەگەڵpassword_hash()ێکی نوێ): هەرگیز یەک ناگرێتەوە، چونکە هەر هاشێک خۆی هەیە.password_verify()بەکاربهێنە. - قەبارەی پیتی ناوی :
View.phpوview.phpلەسەر Windows و macOS یەک فایلن، بەڵام لەسەر Linux جیاوازن.
Lab tasks and exercisesئەرکەکانی تاقیگە و ڕاهێنانەکان
These are the tasks of Lab 9, in order, with worked answers. Together they build SessionLab.
ئەمانە ئەرکەکانی تاقیگەی ٩ن، بە ڕیزبەندی، لەگەڵ وەڵامی تەواو. پێکەوە SessionLab دروست دەکەن.
Lab tasks and exercisesئەرکەکانی تاقیگە و ڕاهێنانەکان
Lab tasks 1–8: the session versionئەرکەکانی تاقیگە ١–٨: وەشانی سێشن
Create a database in MySQL with the name session.
لە MySQLـدا بە ناوی session دروست بکە.
Show answerوەڵامەکە پیشان بدە
CREATE DATABASE session;Or in phpMyAdmin: Databases → name session → Create.
یان لە phpMyAdminدا: Databases ← ناوی session ← Create.
Create the table for the users, with the fields id, username, email and password.
بۆ بەکارهێنەران دروست بکە، بە id، username، email و password.
Show answerوەڵامەکە پیشان بدە
See Database and table: id is an INT AUTO_INCREMENT PRIMARY KEY, email is UNIQUE, and password is VARCHAR(255) for the hash.
بنکەدراوە و خشتە ببینە: id بریتییە لە INT AUTO_INCREMENT PRIMARY KEY، email UNIQUEـە و password بۆ هاشەکە VARCHAR(255)ـە.
Create a connection file in your NetBeans project and name it connection.php.
لە پرۆژەی NetBeansـەکەتدا پەیوەندی دروست بکە و ناوی بنێ connection.php.
Show answerوەڵامەکە پیشان بدە
<?php
// connection.php: the database connection, included by the other pages.
// Since PHP 8.1, mysqli throws an exception when something fails. We say so clearly:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
// server, MySQL user, password, database name (XAMPP: user "root", empty password)
$connect = mysqli_connect('localhost', 'root', '', 'session');
mysqli_set_charset($connect, 'utf8mb4');
} catch (mysqli_sql_exception $e) {
// Do not show technical details to visitors
die('Cannot connect to the database. Is MySQL running?');
}Create register.php, used by people who want to create an account. Its HTML form asks for username, email address, password and password confirmation.
register.php دروست بکە، کە ئەو کەسانە بەکاری دەهێنن کە دەیانەوێت هەژمار دروست بکەن. HTMLـەکەی ناوی ، ، و دووپاتکردنەوەی دەپرسێت.
Show answerوەڵامەکە پیشان بدە
The full answer is the corrected register.php in Step 2. Its heart:
وەڵامی تەواو register.phpـی ڕاستکراوە لە هەنگاوی ٢دا. ناوەڕۆکی سەرەکیی:
$hash = password_hash($password, PASSWORD_DEFAULT);
$stmt = mysqli_prepare($connect, 'INSERT INTO users (username, email, password) VALUES (?, ?, ?)');
mysqli_stmt_bind_param($stmt, 'sss', $username, $email, $hash);
mysqli_stmt_execute($stmt);Create login.php, used when users want to log in. It needs an HTML form asking for email and password, and PHP code for logging in that uses a SELECT query.
login.php دروست بکە، کە کاتێک بەکارهێنەران دەیانەوێت بچنە ژوورەوە بەکار دێت. پێویستی بە HTML هەیە کە و بپرسێت و کۆدی PHP بۆ کە SELECT بەکار دەهێنێت.
Show answerوەڵامەکە پیشان بدە
See The corrected login.php. The query looks the user up by email only, with a prepared statement; the password is then checked with password_verify().
login.phpـی ڕاستکراو ببینە. تەنها بە و بە بەدوای بەکارهێنەرەکەدا دەگەڕێت؛ پاشان بە password_verify() دەپشکنرێت.
In login.php, inside the success check, create the session for the user who logged in successfully, and redirect to index.php.
لە login.phpـدا، لە ناو پشکنینی سەرکەوتندا، بۆ ئەو بەکارهێنەرە دروست بکە کە بە سەرکەوتوویی چووەتە ژوورەوە و ئاڕاستە بکەرەوە بۆ index.php.
Show answerوەڵامەکە پیشان بدە
session_regenerate_id(true);
$_SESSION['user_id'] = $row['id'];
$_SESSION['username'] = $row['username'];
header('Location: index.php');
exit;Check the session in all other pages of the project. If the user has not logged in, redirect to the login page. Example: index.php.
لە هەموو لاپەڕەکانی تری پرۆژەکەدا بپشکنە. ئەگەر نەچووبێتە ژوورەوە، ئاڕاستە بکەرەوە بۆ لاپەڕەی . نموونە: index.php.
Show answerوەڵامەکە پیشان بدە
<?php
// auth.php: require this file at the very top of every protected page
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}When users log out, delete the session (and any cookie) you created. Create logout.php.
کاتێک بەکارهێنەران دەچنە دەرەوە، ئەو (و هەر ) بسڕەوە کە دروستت کردووە. logout.php دروست بکە.
Show answerوەڵامەکە پیشان بدە
<?php
session_start(); // open the current session so that we can end it
$_SESSION = []; // 1. remove all session variables
// 2. delete the session cookie in the browser (same name, path and domain; time in the past)
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params['path'], $params['domain'], $params['secure'], $params['httponly']);
session_destroy(); // 3. delete the session data on the server
header('Location: login.php'); // 4. back to the login page
exit;Explained in Final step.
لە هەنگاوی کۆتاییدا ڕوون کراوەتەوە.
Lab tasks and exercisesئەرکەکانی تاقیگە و ڕاهێنانەکان
Lab tasks 9–11: the cookie version (unsafe, for learning only)ئەرکەکانی تاقیگە ٩–١١: وەشانی کووکی (ناسەلامەت، تەنها بۆ فێربوون)
Use a cookie instead of the session: after a successful login, create a cookie user_id for one hour with setcookie().
لە جیاتی بەکاربهێنە: دوای سەرکەوتوو، بە setcookie() user_id بۆ یەک کاتژمێر دروست بکە.
Show answerوەڵامەکە پیشان بدە
// UNSAFE (Lab 9 cookie version, for learning only): anyone can create this cookie by hand
setcookie('user_id', $row['id'], time() + 3600, '/', '', false, true);At the top of every page, check for the cookie and redirect to login.php if it is missing.
لە سەرەوەی هەموو لاپەڕەیەکدا بپشکنە و ئەگەر نەبوو ئاڕاستە بکەرەوە بۆ login.php.
Show answerوەڵامەکە پیشان بدە
<?php
// Lab 9 cookie version (UNSAFE, for learning only): it only checks that a cookie exists
if (!isset($_COOKIE['user_id'])) {
header('Location: login.php'); // redirect user to login page
exit;
}
echo "<h1>Welcome! You are logged in as user number " . htmlspecialchars($_COOKIE['user_id']) . ".</h1>";The lab's version lacked a semicolon and exit, and used curly quotes. It is still unsafe: the page only checks that a cookie named user_id exists, and anyone can create one by hand (why).
وەشانەکەی تاقیگە نە تێدا بوو نە exit و خواری بەکار هێنابوو. هێشتا ناسەلامەتە: لاپەڕەکە تەنها دەپشکنێت کە بە ناوی user_id هەیە و هەر کەسێک دەتوانێت بە دەست یەکێک دروست بکات (بۆچی).
In logout.php, delete the cookie with setcookie(): the same name, an empty value and a time in the past.
لە logout.phpـدا بە setcookie() بسڕەوە: هەمان ناو، بەتاڵ و کاتێکی ڕابردوو.
Show answerوەڵامەکە پیشان بدە
<?php
// Lab 9 cookie version (UNSAFE, for learning only)
// check for the cookie: if it is not set, go to the login page
if (!isset($_COOKIE['user_id'])) {
header('Location: login.php');
exit;
}
// Delete the cookie: same name, path and domain as setcookie() used, empty value, time in the past
setcookie('user_id', '', time() - 3600, '/', '', false, true);
header('Location: login.php');
exit;Lab tasks and exercisesئەرکەکانی تاقیگە و ڕاهێنانەکان
More exercises and a quick checkڕاهێنانی زیاتر و پشکنینی خێرا
Make the login form remember the last email address for 30 days, so the user does not have to type it again. The cookie must not log anybody in; it only fills in the email field.
وا بکە دوایین بۆ ٣٠ ڕۆژ لە بیر بگرێت، بۆ ئەوەی پێویست نەکات دیسان بینووسێتەوە. نابێت کەس بخاتە ژوورەوە؛ تەنها خانەی پڕ دەکاتەوە.
Hintئاماژە
A cookie is fine for conveniences that are harmless if someone changes them. Escape the value when you print it.
بۆ ئەو ئاسانکارییانە باشە کە ئەگەر کەسێک بیانگۆڕێت زیانیان نییە. کاتێک چاپی دەکەیت بپارێزە.
Show answerوەڵامەکە پیشان بدە
Two changes in login.php. After a successful login, next to the session lines:
دوو گۆڕانکاری لە login.phpـدا. دوای سەرکەوتوو، لە تەنیشت دێڕەکانی :
// A harmless convenience cookie: only pre-fills the form, proves nothing
setcookie('last_email', $email, time() + 30 * 24 * 60 * 60, '', '', false, true);And in the form:
و لە :
Email: <input type="email" name="emailInput" value="<?php echo htmlspecialchars($_COOKIE['last_email'] ?? ''); ?>"><br>HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
Email: <input type="email" name="emailInput" value="aram@example.com"><br>Tested: after login the server sent Set-Cookie: last_email=aram%40example.com; …; HttpOnly, and after logout the form came back with the email filled in.
تاقیکراوە: دوای Set-Cookie: last_email=aram%40example.com; …; HttpOnlyی نارد و دوای بە ئیمەیڵە پڕکراوەکەوە گەڕایەوە.
Add profile.php, which only logged-in users may open, and which shows “You are logged in as (name).”. Reuse auth.php.
profile.php زیاد بکە، کە تەنها بەکارهێنەرانی چووەژوورەوە بۆیان هەیە بیکەنەوە و «You are logged in as (ناو).» پیشان دەدات. auth.php دووبارە بەکاربهێنەرەوە.
Show answerوەڵامەکە پیشان بدە
<?php
require 'auth.php'; // only logged-in users get past this line
?>
<!DOCTYPE html>
<html>
<body>
<h1>Your profile</h1>
<p>You are logged in as <?php echo htmlspecialchars($_SESSION['username']); ?>.</p>
</body>
</html>Your profile You are logged in as Aram.
Not logged in: 302 to login.php.
نەچووەتە ژوورەوە: 302 بۆ login.php.
Summary and reviewکورتە و پێداچوونەوە
- A login system has five files:
connection.php,register.php,login.php, protected pages such asindex.php(withauth.php), andlogout.php. - Register: validate the input,
password_hash(), INSERT with a prepared statement, handle a duplicate email. - Log in: SELECT by email with a prepared statement,
password_verify(),session_regenerate_id(true), store the user id in$_SESSION, redirect andexit. - Protect:
session_start(), check$_SESSION['user_id'], otherwise redirect andexit. - Log out: empty
$_SESSION, delete the session cookie,session_destroy(), redirect andexit. - Never use a plain cookie such as
user_id=1as a login: it can be forged. - Roles come from the database; check them on every admin page (authorization).
- سیستەمی پێنج هەیە:
connection.php،register.php،login.php، لاپەڕە پارێزراوەکانی وەکindex.php(لەگەڵauth.php) وlogout.php. - : پشتڕاست بکەرەوە،
password_hash()، INSERT بە ، دووبارە مامەڵە بکە. - : SELECT بە و ،
password_verify()،session_regenerate_id(true)، ناسێنەری لە$_SESSIONدا هەڵبگرە، ئاڕاستە بکەرەوە وexit. - پاراستن:
session_start()،$_SESSION['user_id']بپشکنە، ئەگینا ئاڕاستە بکەرەوە وexit. - :
$_SESSIONبەتاڵ بکە، بسڕەوە،session_destroy()، ئاڕاستە بکەرەوە وexit. - هەرگیز سادەی وەک
user_id=1وەک بەکار مەهێنە: دەتوانرێت ساختە بکرێت. - ڕۆڵەکان لە دێن؛ لە هەموو لاپەڕەیەکی بەڕێوەبەردا بیانپشکنە ().
What is the difference between authentication and authorization in this project?جیاوازی نێوان پشتڕاستکردنەوەی ناسنامە و ڕێگەپێدان لەم پرۆژەیەدا چییە؟
Authentication is login.php checking the email and password. Authorization is a page deciding whether this logged-in user may see it, for example usermanagement.php allowing only role = 'admin'.
ئەوەیە کە login.php و دەپشکنێت. ئەوەیە کە لاپەڕەیەک بڕیار دەدات ئایا ئەم بەکارهێنەرە چووەژوورەوەیە بۆی هەیە بیبینێت، بۆ نموونە usermanagement.php تەنها ڕێگە بە role = 'admin' دەدات.
Why does the lab PDF's login.php work on XAMPP but not on every server?بۆچی login.phpـی PDFـی تاقیگەکە لەسەر XAMPP کار دەکات بەڵام لەسەر هەموو ڕاژەکارێک نا؟
Its form HTML is output before session_start() and header(). XAMPP's output buffering hides this; without buffering the headers are already sent and both calls fail. (It also has curly quotes, which must be fixed first.)
HTMLـی فۆرمەکەی پێش session_start() و header() . XAMPP ئەمە دەشارێتەوە؛ بەبێ ڕاگرتن سەردێڕەکان پێشتر نێردراون و هەردوو بانگکردنەکە شکست دەهێنن. (هەروەها خواری تێدایە، کە دەبێت سەرەتا ڕاست بکرێنەوە.)
How did the email x' OR '1'='1' -- log in to SessionLab?ئیمەیڵی x' OR '1'='1' -- چۆن چووە ناو SessionLabـەوە؟
The text was pasted into the SQL. The quote closed the email string, OR '1'='1' made the condition true for every row, and -- commented out the password test, so the first user was returned.
دەقەکە ڕاستەوخۆ خرایە ناو SQL. ئیمەیڵەکەی داخست، OR '1'='1' مەرجەکەی بۆ هەموو ڕیزێک ڕاست کرد و -- پشکنینی کرد بە ، بۆیە یەکەم گەڕێنرایەوە.
Why is the password column VARCHAR(255)?بۆچی ستوونی تێپەڕەوشە VARCHAR(255)ـە؟
It stores a password_hash() hash (60 characters with bcrypt, possibly longer with future algorithms), never the password itself.
هاشێکی password_hash() هەڵدەگرێت (٦٠ پیت بە bcrypt، لەوانەیە بە داهاتوو درێژتر)، هەرگیز خودی نا.
What three things does a complete logout do, and why each?چوونەدەرەوەیەکی تەواو چ سێ کارێک دەکات و بۆچی هەریەکەیان؟
It empties $_SESSION (no data is left for the rest of the request), deletes the PHPSESSID cookie (the browser forgets the ID), and calls session_destroy() (the data on the server is deleted). Then it redirects and exits.
$_SESSION بەتاڵ دەکات (هیچ بۆ باقی نامێنێتەوە)، PHPSESSID دەسڕێتەوە ( ناسێنەرەکە لە بیر دەکات) و session_destroy() بانگ دەکات ( سەر دەسڕدرێنەوە). پاشان ئاڕاستە دەکاتەوە و دەوەستێت.
Why should the login page say “Wrong email or password.” instead of “This email does not exist”?بۆچی لاپەڕەی چوونەژوورەوە دەبێت بڵێت «Wrong email or password.» لە جیاتی «This email does not exist»؟
So that an attacker cannot use the login form to find out which email addresses have accounts.
بۆ ئەوەی هێرشبەر نەتوانێت بەکاربهێنێت بۆ دۆزینەوەی ئەوەی کام هەژماریان هەیە.
Videoڤیدیۆ
Watch a videoسەیری ڤیدیۆیەک بکە
A video that viewers liked a lot and praised in their comments, chosen for this lesson.ڤیدیۆیەک کە بینەران زۆریان پێ خۆش بووە و لە کۆمێنتەکانیاندا ستایشیان کردووە، بۆ ئەم وانەیە هەڵبژێردراوە.
A login built step by step: find the user with a prepared statement, check password_verify(), save the user in the session, then log out.
هەنگاو بە هەنگاو سیستەمێکی دروست دەکات: دۆزینەوەی بە ، پشکنین بە password_verify()، هەڵگرتنی لە ، پاشان .
Watch 1:26:47–1:54:12: login script, logged-in pages and logout.
لە 1:26:47 تا 1:54:12 سەیر بکە: ، دوای و .
Lesson complete!وانەکە تەواو بوو!
Well done. Review the key terms, then go on to the next lesson.دەستخۆش. سەیرێکی زاراوە سەرەکییەکان بکەرەوە، پاشان بڕۆ بۆ وانەی داهاتوو.
Key termsزاراوە سەرەکییەکان
Web APIs and JSONAPIـی وێب و JSON
Related lecturesوانە پەیوەندیدارەکان
- Securing Data in PHP and MySQLپاراستنی دراوە لە PHP و MySQL
- Redirects: Sending the Browser Somewhere Elseئاڕاستەکردنەوە: ناردنی تۆڕگەڕ بۆ شوێنێکی تر
- Including Files: include and requireهاوردەکردنی فایل: include و require
- Form Validationپشتڕاستکردنەوەی فۆرم
- Building a CRUD Application with PHP and MySQLدروستکردنی بەرنامەیەکی CRUD بە PHP و MySQL
- Creating a database with phpMyAdminدروستکردنی بنکەدراوە بە phpMyAdmin
References and further readingسەرچاوەکان و خوێندنەوەی زیاتر
This lecture is a first edition. The Kurdish text and terminology are waiting for review by Kurdish IT teachers.ئەم وانەیە چاپی یەکەمە. دەقە کوردییەکە و زاراوەکانی چاوەڕێی پێداچوونەوەی مامۆستایانی کوردی بواری IT ن.