Skrypt rejestracji i logowania może być przydatny w wielu różnych sytuacjach. Jest to często spotykana funkcjonalność na stronach i w aplikacjach gdzie użytkownicy zakładają własne konta użytkownika. To najczęściej tego typu funkcjonalność motywuje twórców stron do nauki programowania w języku PHP. Nie możliwe jest uzyskanie czegoś takiego w samym HTML, CSS i JavaScript kodowanego po stronie przeglądarki a jest to dość częsta i przydatna rzecz w różnego rodzaju serwisach internetowych.
Małe sprostowanie: istnieje możliwość zabezpieczenia hasłem stron za pomocą wymyślnych skryptów JS, które przekształcają hasło w nazwę pliku.html – tak jak to było przykładowo wykonane w słynnych grach Hackme ale nie są to ani zbyt popularne ani bezpieczne rozwiązania.
Funkcjonalność tę można zwyczajnie zepsuć – zapisując hasła jako plain text w bazie danych lub nie zabezpieczając aplikacji przed podawaniem pustego hasła bądź przed atakami SQL Injection tworzona aplikacja może być anty przykładem bezpiecznej aplikacji. W tym artykule nauczysz się wykonywać rejestrację i logowanie w PHP i MySQL od podstaw bez żadnego frameworka. Dodatkowo, aplikacja będzie odporna na popularne ataki i wolna od błędów, które znacząco obniżają jej bezpieczeństwo.
Zabezpieczenie strony hasłem w PHP
Sam proces logowania nie jest potrzebny jeżeli chcemy zabezpieczyć stronę hasłem. Załóżmy, że mamy stronę sekret.php, która zawiera dane, które chcemy udostępnić tylko tym użytkownikom, którzy znają niezmienne i współdzielone hasło. Taka funkcja jest zaszyta w wielu systemach CMS. Przykładowo, ja udostępniam klientom w taki sposób wzory umów bądź cenniki.
Zabezpieczenie strony polega wtedy tak naprawdę na prostym porównaniu dwóch ciągów: tego, który użytkownik wprowadza w formularzu z tym, który zakodował autor strony bezpośrednio w skrypcie lub pliku konfiguracyjnym.
<?php
if(isset($_POST["submit"])){
if($_POST["haslo"] == "sekret1"){
?>
Tutaj jest chroniona zawartość
...
<?php
}
else{
echo "Nieprawidłowe hasło";
}
}
else{
echo "Wprowadź hasło";
}
?>
<form method="POST">
<input type="password" name="haslo" placeholder="Podaj hasło">
<input type="submit" name="submit" value="Wyślij">
</form>
Ten przykład można dopracować na wiele sposobów, np. hasło może być elegancko zapisane w osobnym pliku config.php. Jeżeli zapisalibyśmy hasło w bazie danych, hasło można byłoby zmieniać za pomocą panelu administratora – tak jak to rzeczywiście realizują systemy CMS. Logikę aplikacji można oddzielić od treści za pomocą własnego routingu a sam formularz też mógłby być zapisany do osobnego pliku – chodzi jednak o samą ideę.
W miejscu gdzie pokazujemy chronioną zawartość moglibyśmy dodać linijkę:
session_start(); $_SESSION["poprawne_haslo"] = true;
Wówczas, na podstawie sesji moglibyśmy udostępniać także inne podstrony bez konieczności wprowadzania jeszcze raz tego samego hasła. Przykładowo, w pliku sekret2.php mogłoby już być takie sprawdzenie:
session_start();
if($_SESSION["poprawne_haslo"]){
Tutaj jest chroniona zawartość
...
}
else{
echo "Brak dostępu";
}
Szczegóły implementacji zależą już od naszych preferencji i UX jaki chcemy zapewnić użytkownikowi. Takie zabezpieczenie strony ma jednak wiele wad i jest mało skalowalne. Nie wiemy kto i kiedy wprowadza hasło. Hasła nie można łatwo przypomnieć i w ogóle takie rozwiązanie nie daje tyle radości jak prawdziwe logowanie z własnym hasłem, które użytkownik ustala w trakcie rejestracji.
Rejestracja użytkowników PHP i MySQL
Aby w ogóle istniały konta, które mogą się logować, powinniśmy udostępnić administratorowi bądź samym użytkownikom przyjazny interfejs do tworzenia kont użytkowników często określany mianem formularza rejestracyjnego. Z filozoficznego punktu widzenia, rejestracja na stronie lub w aplikacji to dodanie rekordu bądź rekordów do bazy danych z informacją o użytkowniku i nic ponadto. Zazwyczaj wykonujemy dodatkowe sprawdzenie, czy tak czasem użytkownik posługujący się konkretnym adresem e-mail już nie istnieje w systemie lub wykonujemy inne sprawdzenia – czy np. hasło spełnia jakieś konkretne wymogi bezpieczeństwa. A nóż nasza aplikacja osiągnie krajowy bądź światowy sukces – warto wtedy zrobić jeszcze zabezpieczenie antyspamowe.
Formularz rejestracji na stronie nie różni się zazwyczaj od zwykłego formularza. Ma charakterystyczne dla niego pola i nic ponadto:
<form method="POST" action="register.php"> <input type="text" name="name" placeholder="Imię i nazwisko" required> <input type="email" name="email" placeholder="E-mail" required> <input type="password" name="password" placeholder="Hasło" required> <input type="submit" name="submit" value="Wyślij"> </form>
Żyjemy w czasach po 2020 roku, gdzie przeglądarki same proponują bezpieczne hasła, dlatego nie będziemy dodawać dodatkowego pola „powtórz hasło” – to nieaktualna i rujnująca wygodę praktyka, o której można już zapomnieć.
Aby móc skorzystać z bazy danych musimy ją stworzyć i dodać tabelę users. Można tabęlę wyklikać lub użyć kodu SQL:
CREATE TABLE users ( user_id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, user_fullname varchar(128) NOT NULL, user_email varchar(128) NOT NULL, user_passwordhash varchar(255) NOT NULL, PRIMARY KEY (user_id), UNIQUE KEY (user_email) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Całość powinna wyglądać tak:

UNIQUE przy kolumnie z e-mailami automatycznie zapewnia nam unikalność tego pola i chroni przed ponowną rejestracją tego samego adresu e-mail. Dzięki temu nie musimy w logice aplikacji sprawdzać czy określony użytkownik już istnieje. Nie pozostaje nic innego jak zamieścić i omówić kod PHP, który dokonuje rejestracji.
Przed skorzystaniem z bazy danych powinniśmy nawiązać połączenie i stworzyć zmienną $db_conn:
$db_host = "adres_serwera.mysql.db"; // np. localhost $db_name = "nazwa_bazy_danych"; // np. test $db_user = "nazwa_uzytkownika_mysql"; // np. root $db_pass = "haslo_uzytkownika_do_bazy_danych"; // np. puste "" $db_conn = mysqli_connect($dbhost,$dbuser,$dbpass) or die ("Odpowiedź: Błąd połączenia z serwerem $host"); mysqli_select_db($db_conn, $dbname) or die("Trwa konserwacja bazy danych… Odśwież stronę za kilka sekund.");
Ten kod można umieścić na początku w pliku register.php bądź stworzyć osobny plik dbconnection.php. W przypadku drugiego rozwiązania, w każdym pliku, który wykorzystuje go (np. register.php lub login.php) dodać require(„dbconnection.php”);
Na samym początku, dla wygody, przypiszmy do zmiennych wartości przechowywane w tablicy $_POST. Tablica ta jest tworzona automatycznie w przypadku wysyłania formularza a klucze to wartości atrybutów name w formularzu HTML. W pliku register.php dodajemy następujące linijki:
$user_fullname = mysqli_real_escape_string($db_conn, $_POST["name"]); $user_email = mysqli_real_escape_string($db_conn, $_POST["email"]); $user_password = mysqli_real_escape_string($db_conn, $_POST["password"]);
Hasło nie może być przechowywane w bazie danych w sposób jawny. Wymaga się aby było zaszyfrowane chociaż w taki sposób:
$user_password_hash = password_hash($user_password, PASSWORD_DEFAULT);
Jeżeli połączenie przebiega prawidłowo, możemy wstawić nowy rekord do bazy:
if (mysqli_query($db_conn, "INSERT INTO users (user_fullname, user_email, user_passwordhash) VALUES ('$user_fullname', '$user_email', '$user_password_hash')")){ echo "Rejestracja przebiegła poprawnie"; } else{ echo "Nieoczekiwany błąd - użytkownik już istnieje lub błąd serwera MySQL."; }
Logowanie użytkowników w PHP
Logowanie może być skopiowanym formularzem rejestracji, w którym pozostawiono tylko pole do wpisania e-maila i hasła:
<form method="POST" action="login.php"> <input type="email" name="email" placeholder="E-mail" required> <input type="password" name="password" placeholder="Hasło" required> <input type="submit" name="submit" value="Wyślij"> </form>
Weryfikacja czy hasło jest poprawne to wyciągniecie danych z bazy i sprawdzenie za pomocą funkcji password_verify() czy wygenerowane skróty haseł pasują do siebie. taki kod oczywiście dodajemy do pliku login.php:
$user_password = mysqli_real_escape_string($db_conn, $_POST["password"]); $user_email = mysqli_real_escape_string($db_conn, $_POST["email"]); $query_login = mysqli_query($db_conn, "SELECT * FROM users WHERE user_email ='$user_email'); if(mysqli_num_rows($query_login) > 0) { $record = mysqli_fetch_assoc($query_login); $hash = $record["user_passworhash"]; if (password_verify($user_password, $hash)) { $_SESSION["current_user"] = user_id; } }
Najpierw sprawdzamy czy podany w polu e-mail znajduje się w tabeli users jeżeli tak, pobieramy zapisany hash przy rejestracji i sprawdzamy zgodność haseł za pomocą funkcji password_verify. Funkcja zwraca prawdę jeżeli podane hasło jest poprawne.
Gdziekolwiek będziemy sprawdzać czy użytkownik jest zalogowany możemy wykorzystać zmienną sesyjną
if (isset($_SESSION["current_user"])){ /* Użytkownik jest zalogowany */ } else { /* Użytkownik nie jest zalogowany */ }
Mechanizm ten można „udziwniać” na wiele sposób przykładowo:
- przy rejestracji mogliśmy sprawdzić czy wprowadzany e-mail przechodzi walidację i czy pole nie jest puste,
- dane mogliśmy poddać odpowiedniej obróbce np. trim lub sanitize,
- rejestracja mogła wymagać klikniecia w link aktywacyjny.
Jednak nie o to w tym artykule chodzi a bardziej o pokazanie samej istoty zalogowania.
Resetowanie zapomnianego hasła w aplikacji PHP
Przy kliknięciu w link „Zapomniane hasło”, w dodatkowej kolumnie w tabeli z użytkownikami jest generowany losowy ciąg znaków (np. hash) wygenerowany za pomocą funkcji:
$secret = bin2hex(random_bytes(16)); $recovery_email = $_GET["recovery_email"]; mysqli_query($db_conn, "UPDATE users SET user_secret = '$secret' WHERE user_email = '$recovery_email'");
Link z sekretem wysyłamy na skrzynkę użytkownika. Link może wyglądać tak:
https://moja-domena.xyz/password_reset.php?secret=123123123123&userid=123
Skrypt password_reset.php sprawdza czy przesłano sekret i czy jest on zgodny z tym zapisanym w bazie danych, który został wygenerowany przy kliknięciu w link „Zapomniane hasło”.
$secret = $_GET["secret"]; $user_id = $_GET["userid"];
Jeżeli wszystko się zgadza (np. id użytkownika i sekret przesłany w linku) umożliwi on ustawienie nowego hasła poprzez aktualizację pola user_password zupełnie tak jak podczas rejestracji.
$query = mysqli_query($db_conn, "SELECT * FROM users WHERE user_id ='$user_id' AND user_secret = '$secret'); if(mysqli_num_rows($query) > 0) { /* Tutaj generujemy formularz do resetowania hasła */ }
Tworzymy hash zupełnie tak jak przy rejestracji a potem zostaje aktualizacja rekordu – zerujemy przy okazji sekret lub generujemy nowy:
mysqli_query($db_conn, "UPDATE users SET user_passwordhash = '$new_passwordhash', client_secret = NULL WHERE client_secret = '$secret'" AND user_id = '$user_id');
Jeżeli nie wiesz jak wysyłać e-maila w PHP sprawdź te przewodniki:
Aktywacja konta użytkownika za pomocą e-maila w PHP
Do systemu aktywacji kont wystarczy stworzyć dodatkową kolumnę np. user_status. Może ona przyjmować wartości BOOLEAN, które będą odpowiednio informowały czy konto jest aktywowane lub nie. Można też z tego zrobić pole TINYINT i tam kodować inne statusy np. specjalne uprawnienia lub zbanowanie.
Od razu przy rejestracji tworzymy sekret podobny do tego, który tworzyliśmy przy okazji resetowania hasła a na e-mail wysyłamy link z sekretem, który teraz zmieni status użytkownika a nie będzie umożliwiał zmiany hasła.
mysqli_query($db_conn, "UPDATE users SET user_status = 1, client_secret = NULL WHERE client_secret = '$secret'" AND user_id = '$user_id');
Dodatek 1: Zabezpieczenie formularzy za pomocą CAPTCHA
Formularze rejestracji są podatne na SPAM polegający na zautomatyzowanych próbach tworzenia fikcyjnych kont. Formularze logowania są zaś podatne na ataki brute-force. Skutecznym sposobem na obronę przed spamem jak i na ataki brute-force jest CAPTCHA. Istnieją gotowe mechanizmy CAPTCHA takie jak reCAPTCHA, które możesz zintegrować ze swoją aplikacją PHP. Czytaj więcej w osobnym przewodniku o integracji reCAPTCHA w PHP.
Podsumowanie
Jak widać w PHP stworzenie bezpiecznego mechanizmu logowania na stronę internetową to zadanie na kilka minut. Nic dziwnego, że PHP jest najpopularniejszym językiem do tworzenia aplikacji i portali internetowych. Pamiętaj aby nie przechowywać haseł w postaci zwykłego tekstu bez hashowania.
Odpowiedz lub skomentuj