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:

Tabela users – widok z phpMyAdmin

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.

Źródła

Oceń artykuł na temat: Rejestracja i logowanie w PHP – prosty i bezpieczny skrypt
Średnia : 4.7 , Maksymalnie : 5 , Głosów : 26