Passwort vergessen Funktion – „Challenge-Response“-Verfahren (Teil 1)
Man sieht sie überall und fragt sich wie sowas wohl funktioniert. Man klickt auf einen Link, gibt seine E-Mail-Adresse und bekommt ein neues Passwort zugeschickt.
Vor allem jetzt, wo die Passwörter möglichst lang und kompliziert sein müssen, verlieren oder vergessen immer mehr Benutzer ihre Zugangsdaten. Sie werden beim ersten Login einfach im Passwort-Tresor des Browsers abgespeichert und vergessen. Das Dilemma ist dann besonders groß, wenn die Passwörter durch ein Versehen, oder nach einem Systemcrash gelöscht wurden. In diesem Fall ist eine solche „Passwort vergessen“-Funktion Gold wert.
Doch wie funktionieren solche Funktionen und wie sollte man sie umsetzten?
Es sei gleich gesagt, dass der Ansatz, den Benutzer durch eine Frage in der Art von „Wie heißt Ihr Hund?“ zu identifizieren, falsch ist. Diese Fragen lassen sich sehr leicht beantworten.
Sollte ein Benutzer sein Passwort vergessen haben, sollte er immer eine Mail an die bei Ihnen gespeicherte E-Mail Adresse bekommen. Hierzu müsste der Benutzer seinen Benutzernamen oder gleich seine E-Mail Adresse in ein Formularfeld eingeben. Sie müssen jedoch sicher sein, dass die E-Mail Adresse auch wirklich nur von einem Benutzer verwendet werden kann.
Bitte vermeiden Sie das Zusenden von Passwörtern direkt per E-Mail. Der Benutzer sollte weder das alte noch ein neues Passwort unverschlüsselt per Mail bekommen! Sie fragen sich sicherlich: Wieso nicht?“. Der Grund ist recht einfach. Die E-Mails werden fast immer unverschlüsselt übertragen und bieten so einem Angreifer, der die Leitung abhört, freien Zugang zum Account.
Außerdem könnten „Spaßvögel“ die User ärgern, indem sie ständig neue Passwörter zuschicken und somit die alten Passwörter unbrauchbar machen. Somit kann sich der Benutzer vorerst nicht mehr einloggen. Und nicht jeder guckt ständig nach neuen E-Mails.
Meiner Meinung nach ist das sogenannte „Challenge-Response“-Verfahren die beste Lösung für solche Probleme.
Das Verfahren läuft mehrstufig ab und ist trotzdem relativ einfach:
- Der Benutzer fordert ein neues Passwort an
- In die Datenbank wird eine Challenge-ID, zusammen mit der User-ID abgelegt
- Es wird eine E-Mail mit einem Aktivierungslink an den Benutzer verschickt
- Der Benutzer hat 24 Stunden Zeit auf den Aktivierungslink zu klicken
- Auf der Verlinkten Seite kann der Benutzer ein neues Passwort eingeben
Die Umsetzung dieser Funktion gestaltet sind mit PHP gar nicht so schwer. Man benötigt lediglich zwei Dateien, passvergessen.php und challenge.php. Die erste Datei soll lediglich die eingegebene E-Mail Adresse entgegennehmen, überprüfen, ob diese E-Mail Adresse auch wirklich in Ihrer Datenbank vorhanden ist, trägt die Challenge-ID in die Datenbank ein und verschickt eine E-Mail mit dem Aktivierungslink an den Benutzer. Die Datei challenge.php besteht aus zwei Funktionen, die eine soll das Formular zum ändern des Passwortes ausgeben und die andere die Challenge-ID ungültig machen.
Die Tabelle müsste dann folgendermaßen aussehen:
| Field | Type | Null | Key | Default | Extra |
|---|---|---|---|---|---|
| userid | int(11) | YES | NULL | ||
| challenge | varchar(50) | YES | MUL | NULL | |
| valid | tinyint(1) | YES | NULL | ||
| timestamp | int(11) | YES | NULL |
Das Script passvergessen.php erledigt folgendes:
- Nimmt die E-Mail Adresse entgegen
- Überprüft diese, ob sie syntaktisch gültig und in der Datenbank vorhanden ist.
- Wenn ja, erstellt es eine Challenge-ID und fügt diese in die Datenbank hinzu
- Sendet eine E-Mail mit den URL
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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | < ?php include_once 'config.php'; function validate_email($e_mail) { // Die Mailadresse wird zunächst nur auf äusserliche richtigkeit geprüft: if(!preg_match("^[_a-zA-Z0-9-]+(.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+.([a-zA-Z0-9-]{2,4})$",$e_mail)){ // Wenn die äusserliche Form nicht richtig ist wird FALSE zurückgegeben: return FALSE; } else { return TRUE; } } if(isset($_POST['do_send']) && !empty($_POST['user_email'])) { $user_email= escape_string(strip_tags(trim($_POST['user_email']))); if(!validate_email($user_email)) { echo "Keine gültige E-Mail Adresse!"; exit(); } $pruefung = mysql_query(" SELECT user.`user_id` FROM `user` WHERE (`user_email` = '$user_email') LIMIT 1") or die(mysql_error()); if(mysql_num_rows($pruefung) === 1) { $user = mysql_fetch_assoc($pruefung); $change_challenge = md5(md5((string)mt_rand() . $_SERVER['REMOTE_ADDR'])); $timestamp= time(); $challenge_eintragen = mysql_query(" INSERT INTO `changepass` SET `userid` = '".$user['user_id']."', `challenge` = '$change_challenge', `valid` = 1, `timestamp` = '$timestamp'") or die(mysql_error()); $empfaenger = $user_email; $betreff = "Aktivierungsmail zur Passwortändeung - ".$_SERVER['SERVER_NAME'].""; $header = "MIME-Version: 1.0n"; $header .= "Content-type: text/html; charset=iso-8859-1n"; $header .= "From: ".$betreff." - ".$user_email."rn"; $nachricht = ' Du erhälst nun einen Aktivierungslink der 24 Stunden nach dem Beantragen gültig ist. Nach dem Klicken auf den Link, hast du die Möglichkeit ein neues Passwort zu wählen. '.$_SERVER["HTTP_HOST"].'?UserID='.$user["user_id"].'&challenge='.$change_challenge.''; $send = mail($empfaenger, $betreff, $nachricht, $header); if((bool)$challenge_eintragen === TRUE && (bool)$send === TRUE) { echo '<h1>E-Mail versendet'; echo '<p>Es wurde eine E-Mail an Sie versendet. Diese Enthält einen Link zum Ändern von Ihrem Passwort. Dieser Link ist 25 Stunden ab dem Beantragen gültig. Danach muss ein neues Link beantragt werden.</p>'; } else { echo '<h1>Fehler</h1>'; echo '<p>Beim generieren der Challenge oder beim Verschicken der E-Mail ist ein Fehler aufgetreten.</p>'; } } else { echo '<h1>Fehler</h1>'; echo '<p>Die eingegebene E-Mail Adresse befindet sich nicht in der Datenbank.</p>'; } } else { echo ' <h1>Passwort vergessen</h1> <form action="" method="post"> <dl> <dt><label for="user_email">E-Mail:</label></dt> <dd><input type="text" name="user_email" id="user_email" value="" /></dd> </dl> <dl> <dt>Optionen:</dt> <dd> <input class="button" type="submit" name="do_send" id="do_send" value="Jetzt ändern!" /> <input class="button" type="reset" name="reset" id="reset" value="Doch nicht..." /> </dd> </dl> </form>'; } ?> |
Bitte beachten Sie, dass dieses Script nur als Beispiel dienen soll und somit nicht alles ausführlich behandelt!
Der zweite Teil von dem Artikel “Passwort vergessen Funktion – „Challenge-Response“-Verfahren”
¶
Hallo,
nur als Vorschlag: statt md5() würde ich sha1() benutzen. Das erzeugt längere Zeichenketten, die dann natürlich auch schwerer zu erraten sind.
Für reguläre Ausdrücke sind die Funktionen der preg_*-Familie besser und nicht die veralteten ereg_*- und eregi_*-Ausdrücke.
Jetzt nicht direkt zum Thema: könntest du den Code-Blöcken eine horizontale Scrollbar spendieren? Dann ragen die nicht so weit über den rechten Rand hinaus.
Ansonsten: well done!
¶
Hallo Manko10,
vielen Dank für dein Kommentar. Ich werde deinen Ratschlag befolgen und den Code anpassen.
Stimmt, das mit den Scrollbalken… Darauf bin ich einfach noch nicht gekommen. Werde es auch so schnell wie möglich einrichten.
Liebe Grüße
Alex
¶
Hi!
Sehr ausführlicher Code! Allerdings rate ich dir Codesnippets während der Erklärung nach und nach aufzubauen und am Ende entweder den vollständigen Code zusammenzufassen oder ihn als Download anzubieten. So muss man viel hin und herscrollen und sich durch unkommentierten Code wühlen *g*
Trotzdem sehr schön zu lesen und von der Methode her ausgereift, auch wenn ich statt “challenge” in der Datenbank eher “token” sagen würde .. !
Noch etwas zu deinem Stil: Nicht so viel Englisch mit Deutsch mischen, ansonsten gefällts mir.
Was ist denn der Vorteil von mt_rand()? Ist das wirklich lohnenswert schneller?
lG
¶
Hallo php0kid!
Vielen Dank für dein Kommentar. Das mit dem “Code über den ganzen Artikel verteilen” finde ich eine echt gute Idee. Werde es denke ich mal in Zukunft so machen. Dnake.
Über die Begriffe kann man sich denk ich mal streiten, oder?
An meinem Deutsch/Englisch arbeite ich bereits, da meine neuen Kollegen das auch nicht sooooo toll finden.
¶
So, ich habe mir nun die Zeit genohmen und die Code-Box
richtig angepasst. Jetzt kommt lediglich ein horizontaler
Scroll-Balken wenn der Code zu viel Platz braucht. Das ist
echt viel übersichtlicher.
¶
Отличная идея, но надо бы подумать о рекламе на блоге. По-моему ее слишком много
Хотя, конечно – это не мое дело
¶
Спасибо за комментарий. почему ты считаешь что ее слишком много? Только Google AdSense.
Как-то надо-же оплачивать сервер.
¶
Hallo.
Eine Funktion für die Validierung einer E-Mail schreiben??? Laut meinem schlauen Buch gibts in PHP da schon eine Funktion:
filter_var($email. FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE);
Karl
¶
Frohes Neues Karl!
Ja, die gibt es in der Tat und die soltest du auch benutzen. Jedoch ist PHP 5.2 Voraussetzung dafür.
Als ich das Script geschrieben haben, stand mir diese Funktion leider nicht zur Verfügung. Inzwischen benutze ich auch nur “filter_var”.
LG, Alex
¶
Hallo, ich hab noch ne kleine Frage,
was hat es mit der config.php auf sich ?! Warum wird die included ?
lg Stefan
¶
Hallo Stefan,
da wird eigentlich nur die Datenbankverbindung aufgebaut.
Gruß
Alex