Passwort vergessen
Dieser Artikel beschreibt, wie ihr für euren Loginscript eine Passwort vergessen Funktion implementiert.
Der Loginscript funktioniert so, dass an den Benutzer eine E-Mail mit einem Link und einem zufällig erzeugten Code gesendet wird. Ruft der User diesen Link auf, dann hat dieser die Möglichkeit ein neues Passwort zu vergeben. Ihr solltet niemals ein Passwort per E-Mail an den Nutzer senden, weder sein bestehendes Passwort noch ein neu generiertes Passwort, da die Übertragung von E-Mails sehr unsicher ist. Mehr Informationen wie man Passwörter sicher speichern sollte findet ihr im Artikel Passwörter sicher speichern und allgemeine Sicherheitstipp für eine sichere Authentifizierung im Artikel Authentifizierung in PHP.
Das komplette Login-Script könnt ihr hier herunterladen.
Inhaltsverzeichnis
Datenbankstruktur
Um die Passwort-Vergessen-Funktion für den Loginscript implementieren zu können, müssen wir die users-Tabelle um zwei Felder erweitern. Ein Feld für den geheimen Code sowie ein Feld wann dieser Code angefordert wurde. Nur wenn der Code nicht älter als 24 Stunden ist, kann der Benutzer sein Passwort mit dem Code verändern.
Mittels phpMyAdmin könnt ihr für die users-Tabelle zwei neue Spalten hinzufügen:
Der entsprechende SQL-Code zur Erstellung dieser Spalten ist:
1 2 |
ALTER TABLE `users` ADD `passwortcode` VARCHAR(255) NULL; ALTER TABLE `users` ADD `passwortcode_time` TIMESTAMP NULL; |
In der Spalte passwortcode wird später der notwendige Code gespeichert, in der Spalte passwordcode_time der Zeitpunkt wann dieser Code angefordert wurde. Wichtig ist, dass ihr für beide Spalten den Null-Wert erlaubt.
PHP-Dateien
Für die Passwort-Vergessen-Funktion brauchen wir zwei PHP-Dateien, beispielsweise passwortvergessen.php und passwortzuruecksetzen.php.
Sollte der Benutzer sein Passwort vergessen haben, so kann er mittels Link den ihr in das Login-Formular einbaut die passwortvergessen.php Seite aufrufen. Nach der Eingabe seiner E-Mail-Adresse erhält der Benutzer einen Link zur passwortzuruecksetzen.php. Dort kann er ein neues Passwort vergeben.
Passwort Vergessen Seite
Nachfolgend die Seite, damit ein Benutzer ein neues Passwort anfordern kann (passwortvergessen.php).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
<?php $pdo = new PDO('mysql:host=localhost;dbname=test', 'username', 'passwort'); function random_string() { if(function_exists('random_bytes')) { $bytes = random_bytes(16); $str = bin2hex($bytes); } else if(function_exists('openssl_random_pseudo_bytes')) { $bytes = openssl_random_pseudo_bytes(16); $str = bin2hex($bytes); } else if(function_exists('mcrypt_create_iv')) { $bytes = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM); $str = bin2hex($bytes); } else { //Bitte euer_geheim_string durch einen zufälligen String mit >12 Zeichen austauschen $str = md5(uniqid('euer_geheimer_string', true)); } return $str; } $showForm = true; if(isset($_GET['send']) ) { if(!isset($_POST['email']) || empty($_POST['email'])) { $error = "<b>Bitte eine E-Mail-Adresse eintragen</b>"; } else { $statement = $pdo->prepare("SELECT * FROM users WHERE email = :email"); $result = $statement->execute(array('email' => $_POST['email'])); $user = $statement->fetch(); if($user === false) { $error = "<b>Kein Benutzer gefunden</b>"; } else { //Überprüfe, ob der User schon einen Passwortcode hat oder ob dieser abgelaufen ist $passwortcode = random_string(); $statement = $pdo->prepare("UPDATE users SET passwortcode = :passwortcode, passwortcode_time = NOW() WHERE id = :userid"); $result = $statement->execute(array('passwortcode' => sha1($passwortcode), 'userid' => $user['id'])); $empfaenger = $user['email']; $betreff = "Neues Passwort für deinen Account auf www.php-einfach.de"; //Ersetzt hier den Domain-Namen $url_passwortcode = 'http://localhost/passwortzuruecksetzen.php?userid='.$user['id'].'&code='.$passwortcode; //Setzt hier eure richtige Domain ein $text = 'Hallo '.$user['vorname'].', für deinen Account auf www.php-einfach.de wurde nach einem neuen Passwort gefragt. Um ein neues Passwort zu vergeben, rufe innerhalb der nächsten 24 Stunden die folgende Website auf: '.$url_passwortcode.' Sollte dir dein Passwort wieder eingefallen sein oder hast du dies nicht angefordert, so bitte ignoriere diese E-Mail. Viele Grüße, dein PHP-Einfach.de-Team'; mail($empfaenger, $betreff, $text, $from); echo "Ein Link um dein Passwort zurückzusetzen wurde an deine E-Mail-Adresse gesendet."; $showForm = false; } } } if($showForm): ?> <h1>Passwort vergessen</h1> Gib hier deine E-Mail-Adresse ein, um ein neues Passwort anzufordern.<br><br> <?php if(isset($error) && !empty($error)) { echo $error; } ?> <form action="?send=1" method="post"> E-Mail:<br> <input type="email" name="email" value="<?php echo isset($_POST['email']) ? htmlentities($_POST['email']) : ''; ?>"><br> <input type="submit" value="Neues Passwort"> </form> <?php endif; //Endif von if($showForm) ?> |
- Zuerst wird ihm ein Formular für seine E-Mail-Adresse angezeigt. (Zeilen 65-78)
- Danach wird überprüft, ob die E-Mail-Adresse zu einem Nutzer in der Datenbank passiert. Falls nicht, wird ihm eine entsprechende Fehlermeldung ausgeben. (Zeilen 22-31)
- Passt die E-Mail-Adresse, dann wird ein zufälliger Code generiert. Der sha1-Hash von diesem Code sowie der aktuelle Zeitpunkt wird in der Datenbank für den Benutzer gespeichert. (Zeilen 33-35)
- Zum Abschluss wird dem Nutzer eine E-Mail gesendet. In dieser findet er den Link zum Zurücksetzen seines Passworts. Dieser Link enthält zum einen sein Benutzer-ID, zum anderen den zuvor generierten Code. (Zeilen 38-54)
Der Code wird als sha1-Hash gespeichert, damit ein Angreifer der mittels SQL Injection keine Passwort-Codes stehlen kann um damit Benutzerpasswörter zu verändern. Ihr könnt dort potentiell auch jede andere Hashfunktion nutzen, dies macht von der Sicherheit keinen Unterschied.
Neues Passwort Vergeben
Nachfolgend die Seite, die ein Benutzer von der erhaltenen E-Mail aufruft um sein neues Passwort zu vergeben (passwortzuruecksetzen.php):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
<?php $pdo = new PDO('mysql:host=localhost;dbname=test', 'username', 'passwort'); if(!isset($_GET['userid']) || !isset($_GET['code'])) { die("Leider wurde beim Aufruf dieser Website kein Code zum Zurücksetzen deines Passworts übermittelt"); } $userid = $_GET['userid']; $code = $_GET['code']; //Abfrage des Nutzers $statement = $pdo->prepare("SELECT * FROM users WHERE id = :userid"); $result = $statement->execute(array('userid' => $userid)); $user = $statement->fetch(); //Überprüfe dass ein Nutzer gefunden wurde und dieser auch ein Passwortcode hat if($user === null || $user['passwortcode'] === null) { die("Es wurde kein passender Benutzer gefunden"); } if($user['passwortcode_time'] === null || strtotime($user['passwortcode_time']) < (time()-24*3600) ) { die("Dein Code ist leider abgelaufen"); } //Überprüfe den Passwortcode if(sha1($code) != $user['passwortcode']) { die("Der übergebene Code war ungültig. Stell sicher, dass du den genauen Link in der URL aufgerufen hast."); } //Der Code war korrekt, der Nutzer darf ein neues Passwort eingeben if(isset($_GET['send'])) { $passwort = $_POST['passwort']; $passwort2 = $_POST['passwort2']; if($passwort != $passwort2) { echo "Bitte identische Passwörter eingeben"; } else { //Speichere neues Passwort und lösche den Code $passworthash = password_hash($passwort, PASSWORD_DEFAULT); $statement = $pdo->prepare("UPDATE users SET passwort = :passworthash, passwortcode = NULL, passwortcode_time = NULL WHERE id = :userid"); $result = $statement->execute(array('passworthash' => $passworthash, 'userid'=> $userid )); if($result) { die("Dein Passwort wurde erfolgreich geändert"); } } } ?> <h1>Neues Passwort vergeben</h1> <form action="?send=1&userid=<?php echo htmlentities($userid); ?>&code=<?php echo htmlentities($code); ?>" method="post"> Bitte gib ein neues Passwort ein:<br> <input type="password" name="passwort"><br><br> Passwort erneut eingeben:<br> <input type="password" name="passwort2"><br><br> <input type="submit" value="Passwort speichern"> </form> |
Das passwortzuruecksetzen.php Script hat den folgenden Aufbau:
- Zuerst wird überprüft, ob eine User-Id und ein Code übergeben wird. Ebenfalls wird überprüft, ob der Nutzer ein Passwordcode angefordert hat (Zeilen 4-19).
- Danach wird überprüft, dass der angeforderte Code noch nicht alter als 24 Stunden ist und natürlich dass der übermittelte Code aus dem GET-Parameter identisch zum abgespeicherten Code in der Datenbank ist. Da in der Datenbank der Hash des Codes gespeichert ist, muss hier erneut die Hashfunktion aufgerufen werden (Zeilen 21 - 29).
- Sollten alle Voraussetzungen erfüllt sein, so wird dem Benutzer ein Formular zum Ändern seines Passworts angezeigt. Hierbei muss er sein neues Passwort zwei mal eingeben (Zeilen 51 - 60).
- Drückt der Benutzer auf absenden, so wird überprüft, dass er zwei mal das gleiche Passwort eingegeben hat. Falls alles stimmt, so wird mittels password_hash() der Hashwert des Passworts gespeichert und in der Datenbank abgespeichert. Ebenfalls wird der Passwortcode gelöscht (Zeilen 33 - 48).