Przejdź do treści

Resetowanie hasła przez e-mail (tokeny)

  • Użytkownicy i logowanie
  • poziom: zaawansowany
  • aktualizacja: 12 lipca 2026

Reset hasła wydaje się prosty, ale łatwo o groźne błędy — np. zapisanie tokenu w bazie „jawnie" albo zdradzanie, czy dany e-mail ma konto. Ten skrypt robi to porządnie: token generuje kryptograficznie, do bazy trafia wyłącznie jego hasz SHA-256, link wygasa po godzinie i jest jednorazowy.

Całość zamyka się w jednym pliku obsługującym oba kroki: prośbę o link oraz ustawienie nowego hasła.

PHP 152 linii Pobierz .php
<?php
/**
 * Resetowanie hasła przez e-mail — skryptyPHP.com
 * Zapisz jako: reset-hasla.php
 * Wymaga tabeli `uzytkownicy` (skrypt „Rejestracja użytkowników") oraz:
 *
 *   CREATE TABLE resety_hasla (
 *       id            INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
 *       uzytkownik_id INT UNSIGNED NOT NULL,
 *       token_hash    CHAR(64) NOT NULL,
 *       wazny_do      DATETIME NOT NULL,
 *       INDEX (token_hash),
 *       FOREIGN KEY (uzytkownik_id) REFERENCES uzytkownicy(id) ON DELETE CASCADE
 *   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 */

session_start();
require 'baza.php'; // skrypt „Połączenie z bazą (PDO)"

$pdo = polacz_z_baza();

// ── USTAWIENIA ──────────────────────────────────────────────
$adres_strony  = 'https://twojadomena.pl'; // bez ukośnika na końcu
$adres_nadawcy = 'powiadomienia@twojadomena.pl';

$token      = $_GET['token'] ?? '';
$komunikat  = '';
$blad       = '';

// ═══ KROK 2: użytkownik kliknął link i ustawia nowe hasło ═══
if ($token !== '') {

    // Szukamy WAŻNEGO tokenu po jego haszu
    $stmt = $pdo->prepare(
        'SELECT r.id, r.uzytkownik_id
           FROM resety_hasla r
          WHERE r.token_hash = ? AND r.wazny_do > NOW()'
    );
    $stmt->execute([hash('sha256', $token)]);
    $reset = $stmt->fetch();

    if (!$reset) {
        $blad = 'Link jest nieprawidłowy lub wygasł. Poproś o nowy.';
        $token = '';
    } elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $haslo  = $_POST['haslo']  ?? '';
        $haslo2 = $_POST['haslo2'] ?? '';

        if (mb_strlen($haslo) < 8) {
            $blad = 'Hasło musi mieć co najmniej 8 znaków.';
        } elseif ($haslo !== $haslo2) {
            $blad = 'Hasła nie są identyczne.';
        } else {
            // Zapis nowego hasła…
            $pdo->prepare('UPDATE uzytkownicy SET haslo_hash = ? WHERE id = ?')
                ->execute([
                    password_hash($haslo, PASSWORD_DEFAULT),
                    $reset['uzytkownik_id'],
                ]);
            // …i unieważnienie WSZYSTKICH tokenów tego użytkownika
            $pdo->prepare('DELETE FROM resety_hasla WHERE uzytkownik_id = ?')
                ->execute([$reset['uzytkownik_id']]);

            $komunikat = 'Hasło zmienione! Możesz się zalogować.';
            $token = '';
        }
    }
}

// ═══ KROK 1: użytkownik podaje e-mail, wysyłamy link ═══
if ($token === '' && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['email'])) {
    $email = mb_strtolower(trim($_POST['email']));

    $stmt = $pdo->prepare('SELECT id FROM uzytkownicy WHERE email = ?');
    $stmt->execute([$email]);
    $uzytkownik = $stmt->fetch();

    if ($uzytkownik) {
        // 32 losowe bajty -> 64 znaki hex; w bazie tylko hasz tokenu
        $nowyToken = bin2hex(random_bytes(32));

        $pdo->prepare(
            'INSERT INTO resety_hasla (uzytkownik_id, token_hash, wazny_do)
             VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 1 HOUR))'
        )->execute([$uzytkownik['id'], hash('sha256', $nowyToken)]);

        $link  = $adres_strony . '/reset-hasla.php?token=' . $nowyToken;
        $tresc = "Cześć!\n\nAby ustawić nowe hasło, kliknij poniższy link "
               . "(ważny przez 1 godzinę):\n\n$link\n\n"
               . "Jeśli to nie Ty prosiłeś o zmianę hasła, zignoruj tę wiadomość.";

        $naglowki = 'From: ' . $adres_nadawcy . "\r\n"
                  . 'Content-Type: text/plain; charset=UTF-8' . "\r\n";

        mail($email, '=?UTF-8?B?' . base64_encode('Reset hasła') . '?=',
             $tresc, $naglowki);
    }

    // Ten sam komunikat niezależnie od tego, czy konto istnieje!
    $komunikat = 'Jeśli podany adres istnieje w naszej bazie, '
               . 'wysłaliśmy na niego link do zmiany hasła.';
}
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Reset hasła</title>
<style>
    .formularz { max-width: 420px; font-family: sans-serif; margin: 20px auto; }
    .formularz label { display: block; margin: 12px 0 4px; font-weight: bold; }
    .formularz input { width: 100%; padding: 8px; box-sizing: border-box; }
    .formularz button { margin-top: 16px; padding: 10px 24px; cursor: pointer; }
    .info { color: #1b7a2f; }
    .blad { color: #b00020; }
</style>
</head>
<body>

<div class="formularz">
    <h2>Reset hasła</h2>

    <?php if ($komunikat): ?><p class="info"><?= htmlspecialchars($komunikat) ?></p><?php endif; ?>
    <?php if ($blad): ?><p class="blad"><?= htmlspecialchars($blad) ?></p><?php endif; ?>

    <?php if ($token !== ''): ?>
        <!-- KROK 2: formularz nowego hasła -->
        <form method="post" action="?token=<?= htmlspecialchars(urlencode($token)) ?>">
            <label for="haslo">Nowe hasło (min. 8 znaków)</label>
            <input type="password" id="haslo" name="haslo" required minlength="8"
                   autocomplete="new-password">

            <label for="haslo2">Powtórz nowe hasło</label>
            <input type="password" id="haslo2" name="haslo2" required minlength="8"
                   autocomplete="new-password">

            <button type="submit">Ustaw nowe hasło</button>
        </form>
    <?php else: ?>
        <!-- KROK 1: formularz z adresem e-mail -->
        <form method="post" action="reset-hasla.php">
            <label for="email">Adres e-mail Twojego konta</label>
            <input type="email" id="email" name="email" required>

            <button type="submit">Wyślij link do resetu</button>
        </form>
    <?php endif; ?>
</div>

</body>
</html>

Jak użyć tego skryptu

  1. Utwórz tabelę resety_hasla zapytaniem z komentarza (wymaga istniejącej tabeli uzytkownicy).
  2. W ustawieniach wpisz adres swojej strony i adres nadawcy w swojej domenie, po czym zapisz plik jako reset-hasla.php.
  3. Podlinkuj stronę z formularza logowania: <a href="reset-hasla.php">Nie pamiętam hasła</a>.
  4. Przetestuj cały obieg: prośba → e-mail → link → nowe hasło → logowanie.

Warto wiedzieć

  • W bazie trzymamy wyłącznie hasz SHA-256 tokenu. Gdyby ktoś wykradł zawartość tabeli, i tak nie zbuduje z niej działających linków.
  • Komunikat po podaniu e-maila jest zawsze taki sam — napastnik nie sprawdzi w ten sposób, kto ma u Ciebie konto.
  • Po udanej zmianie hasła usuwamy wszystkie tokeny użytkownika — każdy link jest jednorazowy.
  • Warto dopisać sprzątanie wygasłych rekordów, np. raz dziennie: DELETE FROM resety_hasla WHERE wazny_do < NOW().
#reset hasła #tokeny #random_bytes #mail #bezpieczeństwo

Podobne skrypty