PHP: Warum funktioniert das Login-Script mit 'password_verify' nicht?

2 Antworten

Vom Beitragsersteller als hilfreich ausgezeichnet

Zuerst einmal sind mir ein paar Fehler/Punkte aufgefallen, die du auf jeden Fall korrigieren solltest:

1) Dein Doctype ist unvollständig, das Ausrufezeichen fehlt.

<!doctype html>

2) Beende dein Skript stets nach einer header-Weiterleitung. Andernfalls wird dieses vom Server möglicherweise noch weiter ausgeführt, auch wenn der Nutzer davon nichts mehr mitbekommt.

header('Location: /dashboard');
exit;

3) Du schreibst im Fehlerfall (deaktiviertes Konto) HTML außerhalb der HTML-Struktur und erzeugst somit invalides Markup.

// ...
elseif ($user !== false && $status == 0 && password_verify($passwort, $user['passwort'])) {
  echo // ...
}
// ...

Beim else machst du es in der Hinsicht bereits besser. Jedoch würde ich generell mehr zwischen Markup und Skript trennen.

$userDeactivated = false;
$wrongInput = false;
// ...
elseif (/* ... */) {
  $userDeactivated = true;
}
else {
  $wrongInput = true;
}
// ...

<!--markup -->
<?php if ($userDeactivated): ?>
  <div class='konto-deaktiv-meldung'>ACHTUNG: <!-- ... -->
<?php endif; ?>
<!-- ... -->
<?php if ($wrongInput): ?>
  <div class='error-message'>E-Mail <!-- ... -->
<?php endif; ?>

Die Maskierung (einfache Anführungszeichen) ist dann nicht mehr notwendig.

Was du noch ändern könntest:

1) Der Methode fetch kannst du noch ein Argument zum fetch style übergeben. Standardmäßig wird das ResultSet als Array geliefert, welches Zugriff auf seine Elemente via Index oder Key erlaubt. Da du aber eh nur den Key verwendest, reicht PDO::FETCH_ASSOC.

2) Wieso nutzt du denn beim Wertvergleich für $status keinen strikten Vergleich? Es ist jetzt nicht so wichtig, doch hat es einen bestimmten Grund?

if ($user !== false && $status == 1 && password_verify($passwort, $user['passwort']))

3) Der Code-Block mitsamt der folgenden leeren Anweisung ist nicht nötig.

{
  header('Location: /dashboard');
};  

4) Wo verwendest du $errorMessage eigentlich?

5) Welche Werte kann die Variable $status denn annehmen? Nur 0 und 1? Dann ließe sich die Überprüfung des Passworts vereinfachen:

if ($user !== false && password_verify($passwort, $user['passwort'])) {
  if ($status == 1) {
    // ...
  }
  else {
    // ...
  }
}
else {
  // ...
}                  

Zu deinem Problem:

  • Speicherst du denn den Hash-Wert des Passworts in der Datenbank?
  • Wieso stehen Logindaten in einer SQL-Tabelle kunden_mailverteiler? Zumindest der Name der Tabelle erscheint mir etwas unpassend.
  • Lasse dir vor der Passwortprüfung folgende Werte ausgeben:
$user
$status
$passwort
$user['passwort']

Verwende die Funktion var_export, um den Wert eines boolean auszugeben. Andernfalls führt PHP einen impliziten Typecast um, der nicht in deinem Interesse liegt.

Anhand der Daten kannst du schon einmal prüfen, ob es wirklich an password_verify liegt. Wenn die Hashes tatsächlich unterschiedlich sein sollten, kämen spontan drei Möglichkeiten in Betracht:

  • Den Nutzer gibt es mehr als einmal.
  • Deine Systeme (Datenbank, Markup, PHP) arbeiten nicht mit dem selben Zeichensatz (UTF-8).
  • Du speicherst das Passwort einfach falsch ab.

christian99975 
Beitragsersteller
 24.11.2018, 11:11

Ich habe nun soweit die Fehler/Punkte behoben und noch ein, zwei Rückfragen zu den anderen Punkten von dir bei "Was du noch ändern könntest:".

  1. Habe ich behoben und PDO::FETCH_ASSOC eingesetzt
  2. Was meinst du genau mit einem 'strikten' Vergleich?
  3. Warum ist dieser Block nicht nötig? Dort ist doch die Weiterleitung drin, die nach dem erfolgreichen Login auf den internen Bereich leiten soll. Oder hab ich einen Denkfehler?
  4. Aktuell nirgends, außer in dem else .
  5. $status kann nur 0 oder 1 sein. Habe die Abfrage wie bei dir umgebaut :)

Zu meinem Problem:

  • Der Hash-Wert wird so in der Datenbank gespeichert, JA.
  • Ich habe der Tabelle diesen Namen gegeben, da dort zwar quasi alles an Zugangsdaten des Users stehen aber eben auch hauptsächlich mail-details

Hab die Ausgabe testweise eingebaut und bin mir nicht sicher. Das eingegebene PW ist '123456' und der Hash eben ein Hash. Kann es daran liegen? Muss ich das eingegebene PW erst in einen Hash umwandeln, um ihn mit dem Hash aus der DB abzugleichen (um dann password_verify nutzen zu können)?

Die Datebank ist auf die Kollation 'utf8_unicode_ci' eingestellt und die Datei eben auf 'UTF-8'.

Das Passwort speichere ich so in die DB ein:

  • ' $passwort = $_POST['passwort']; '
  • ' $passwort_hash = password_hash($passwort, PASSWORD_DEFAULT)';
  • und eben mit einem normalen insert :-D

Vielen vielen Dank schon einmal für deine Hilfe! :)

0
regex9  24.11.2018, 15:04
@christian99975
Was meinst du genau mit einem 'strikten' Vergleich?

In PHP kann man entweder via == auf Gleichheit prüfen (dabei werden Typumwandlungen implizit von PHP durchgeführt) oder via ===, ob die Werte identisch sind (gleicher Wert, gleicher Datentyp). Letzterer wird auch als strikter Vergleich bezeichnet. Siehe dazu auch hier.

Warum ist dieser Block nicht nötig?

Da war ich unachtsam beim Formulieren und Aufzeigen. Den Aufruf der header-Funktion behältst du, den Block drumherum (bzw. die geschweiften Klammern) mitsamt Semikolon kannst du herausnehmen.

Muss ich das eingegebene PW erst in einen Hash umwandeln (...)

Aja, das Wichtigste vergessen: Für den Test ja. Lasse dir den Wert von

password_hash($passwort, PASSWORD_DEFAULT)';

ausgeben. Dann kannst du manuell einmal prüfen, ob die Hashes gleich sind oder nicht.

Was sagen dir indes die anderen Werte? Wird password_verify überhaupt aufgerufen, oder ist der Nutzer ($user) bspw. bereits false?

Für die spätere Überprüfung in password_verify ist keine Umwandlung in einen Hash notwendig. Dies macht die Funktion schon selbst. Siehe dazu auch hier (und noch ein Beispiel für die Vorgehensweise, die du auch bereits nutzt).

1
christian99975 
Beitragsersteller
 24.11.2018, 16:02
@regex9

Super! Danke für die Erklärung. Die Hashes sind tatsächlich unterschiedlich, obwohl das Passwort dahinter gleich ist. Wie kann das sein? Habe es jetzt sogar mal mit dem eingegebenen Hash getestet aber dann habe ich wieder unterschiedliche Werte vom Hash. O_o

0
regex9  24.11.2018, 17:37
@christian99975

Ich gehe davon aus, dass der Hash noch falsch gespeichert wird.

  • Haben die Hashes die gleiche Länge?
  • Geh am besten einmal in das Registrierungsskript und lasse dir eingegebenes Passwort und gebildeten Hash ausgeben. Vergleiche diesen dann mit dem Hash, der in der Datenbank steht (bestenfalls direkt, z.B. über phpMyAdmin).
  • Wie viele Nutzer findet die Datenbank zu der E-Mail-Adresse, die du eingibst?
1
christian99975 
Beitragsersteller
 24.11.2018, 18:20
@regex9

Oh weia...

Der Hash vom eingegebenen Passwort ist nicht der selbe, wie er in die Datenbank geschrieben wird. :/

Pro E-Mail-Adresse gibts nur einen Nutzer.

0
regex9  24.11.2018, 18:29
@christian99975

Na dann sind wir dem Problem doch schon mal recht nah.

  • Hat die Spalte in der Datenbanktabelle den Datentyp VARCHAR (oder meinetwegen TEXT)?
  • Gibt es Beschränkungen in der inhaltlichen Länge der Spalte? VARCHAR(255) sollte ausreichend sein. Ob es Probleme in der Länge gibt ist auch daran erkennbar, ob der Hash nach dem Speichern in der DB plötzlich kürzer ist oder nicht.
  • Welcher Zeichensatz ist für die Spalte gesetzt? Ebenso UTF-8? Ich gehe nicht davon aus, dass dies der Fehler ist, doch zur Sicherheit kann man das ruhig einmal mit prüfen.

Poste des Weiteren einmal den Code für dein Login via pastebin. Bereits der Query kann doch noch Fehler aufweisen.

1
christian99975 
Beitragsersteller
 25.11.2018, 08:43
@regex9

Die DB-Spalte ist ein varchar mir 255 Zeichen für den Inhalt und ebenso auf utf8_unicode_ci eingestellt.

Ich denke mal du meintest das Registrierungs-Script statt dem Login... :)

Hier wäre es dann: https://pastebin.com/7fV2FWTE

0
regex9  25.11.2018, 14:38
@christian99975

Stimmt genau, gut mitgedacht.

Beim oberflächlichen Drüberschauen sind mir nochmal Fehler aufgefallen und wahrscheinlich auch die Fehlerquelle. Deine Webseite solltest du am besten via Validator nochmal prüfen. Gerade bei Login/Registrierung u.ä. ist das wichtig.

  • Die Funktion session_start wird mehrmals aufgerufen. Einmal sollte es ausreichen und dann als allererste Aktion, mindestens vor jeglichem Response.
  • Der erste PHP-Abschnitt gibt unter Umständen ein div-Element aus, außerhalb des body-Elements.
  • Der style-Tag gehört in das head-Element.
  • Insgesamt ist die allgemeine HTML-Struktur etwas verquer. Du rufst anfangs eine Funktion get_header auf, dem folgt der jQuery-Include und ein body, danach startest du mit dem Doctype das eigentliche Dokument. Das solltest du dir unbedingt noch einmal genauer anschauen und korrigieren.
  • Als Zeichensatz gibst du im meta-Element ISO-8859-1 an. Ändere das auf utf-8. Generell würde ich den Tag auf HTML5-Stand bringen:
<meta charset="utf-8" />
0
christian99975 
Beitragsersteller
 26.11.2018, 17:59
@regex9

Ok super. Habe mir jeden Punkt angesehen und es dem entsprechend umgebaut. Komisch ist nun aber, da der Hast nun noch unterschiedlich zur Eingabe und beide unterschiedlich lang ist. Hast du noch eine Idee? :/

0
regex9  26.11.2018, 18:19
@christian99975

Er wird immer noch falsch gespeichert? Wird der Hash stets an der gleichen Stelle abgeschnitten (prüfe mit Notepad++, dort wird in der Statusleiste die Zeichen- bzw. Spaltenanzahl angezeigt)?

Bitte schicke mir nochmals den aktuellen Stand des Registrierungsskriptes und prüfe in dem Zuge, ob die PHP-Datei selbst im UTF-8 Format gespeichert wurde.

Des Weiteren wäre es gut, wenn ein Beispielpasswort mit gebildetem Hash und in der DB gespeicherten Hash posten könntest. Vielleicht lässt sich dabei etwas ablesen.

0
christian99975 
Beitragsersteller
 01.12.2018, 17:40
@regex9

Tut mir leid für die späte Antwort :(

Hier der Link zum aktuellen Registrierungsskript: https://pastebin.com/k1wHACkP Bei mir steht alles auf UTF-8...

Beispiele

PW-User-Eingabe: 123456aA

PW-Hash-Formular: $2y$10$NGMpJNbwLoF7nj2ZsuEFle8Zx6b8FPtDaOG2EDJPsm.4MabZjs/tu

PW-Hash nach dem Registrieren aus der DB:$2y$10$NGMpJNbwLoF7nj2ZsuEFle8Zx6b8FPtDaOG2EDJPsm.4MabZjs/tu

PW-Hash-LOGIN-Form (EINGABE):
$2y$10$4HkMW8JOPvZu9NR/U.5/.e5PrmVPyKz/74OXrSCFiwOMmHv4CkcpK

PW-Hash-LOGIN-FORM (ABFRAGE AUS DER DB):
$2y$10$NGMpJNbwLoF7nj2ZsuEFle8Zx6b8FPtDaOG2EDJPsm.4MabZjs/tu

0
regex9  02.12.2018, 05:13
@christian99975

Ok, dieses Testergebnis wiederum sagt uns, dass die Registrierung ganz fantastisch läuft. 😁

Ich konnte das Problem anhand des Logins (aus deinem ersten Link oben) und dem Registrierungsformular (von deinem letzten Kommentar) reproduzieren. Danach habe ich begonnen, beide umzuschreiben, so wie ich es auch z.T. schon empfohlen hatte + weiteren Änderungen (ich gehe noch darauf ein).

Zum Testen habe ich allerdings keine Datenbank verwendet, sondern einfach nur eine Textdatei, in der nur das Passwort zwischengespeichert wurde. Ich poste zwei Versionen des Endresultats.

Der Stand, wie er mit DB aussehen sollte:

Der Stand, den ich zum Testen verwendet habe (ich habe die dafür notwendigen Änderungen mit einem //MOCK-Kommentar ausgestattet):

Bei mir waren die Teststände zuletzt erfolgreich. Ich habe nochmals den Zeichensatz in beiden Dokumenten explizit gesetzt.

Wie das halt so ist, sind mir in dem Zuge einmal wieder Punkte aufgefallen, die man besser machen kann. Einige Punkte habe ich da auch schon direkt umgesetzt (beim Registrierungsformular).

  • Ich habe das CSS in eine eigene Datei ausgelagert.
  • Bei 2-3 Styles hast du ein Leerzeichen zwischen zwei Zahlen vergessen, ich denke es war beim margin für die select-Elemente.
  • Ist !important wirklich in jedem Fall notwendig? Dies würde ich erst einmal anzweifeln.
  • Oft arbeitest du noch mit br-Elementen. Ich habe die Formularfelder stattdessen in eigene Boxen gepackt, die Beschriftung wird mit label-Elementen umrahmt, die auf das jeweilige Formularfeld referenziert. Via CSS bekommen die Boxen einen Abstand von ca. 15px nach oben.
  • Die Datenschutzerklärung habe ich über den Senden-Button positioniert, da der Lesefluss von oben nach unten geht.
  • Ich habe den Feldnamen durchweg englische Bezeichner gegeben. Bisher war es ein Mix aus Deutsch und Englisch.
  • Mir ist aufgefallen, dass es einige Keys nicht gibt, die du aus dem $_POST-Array versuchst auszulesen: newsletter, nichtgelisteteregion, bereich, meldungsbereich. Ich habe sie vorerst auskommentiert.
  • Für deinen Aktivierungscode lässt du dir eine Zufallszahl generieren. Diese kann bis zu fünf Stellen einnehmen - muss sie aber nicht. Ich weiß nicht, wie wichtig der Code ist, doch solltest du dir überlegen, ob einstellige Codes, die ja auch vorkommen können, in Ordnung wären. Sicher wären sie jedenfalls nicht.
  • Bei dem Feld mit der ID suggest fehlte ein Leerzeichen zwischen Slash und required-Attribut.
  • Für deine Eingabefelder gibst du eine maximale Zeichenlänge von 250 Zeichen an. Ist das nicht ein bisschen viel? Es ist nun nicht dramatisch, doch es hat mich verwundert.
  • Bei dem Eingabefeld für den Nutzernamen würde ich die Beschriftung (Nur Vorname oder Nutzername?) überdenken. Zudem hast du dich beim title-Attribut verschrieben (titel), das habe ich korrigiert.
  • Das Eingabefeld für den Meldungsbereich hatte sein name-Attribut doppelt definiert.
  • Bei dem JavaScript würde ich vorsichtig sein, wenn es darum geht, anhand von Sonderzeichen Strings zu splitten. Das kann Probleme bei der Textkodierung machen.
  • In der E-Mail setzt du einen Link und darin einen Button. Da dies zwei eigenständige Aktionselemente sind, dürfen sie nicht ineinander verschachtelt werden. Ich habe vorerst den Button entfernt und seine Inline-Styles an das Anchorelement übertragen. Möglicherweise muss da nochmal weiteres Styling vorgenommen werden.
  • Was die head.php beinhaltet, müsstest du nochmal bei dir abgleichen. Ich habe an der Stelle bisher nur einen HTML-Kommentar gemacht.
1
christian99975 
Beitragsersteller
 08.12.2018, 15:49
@regex9

Vielen Dank. Ich schaue mir das an und versuche es zu übernehmen. Aktuell habe ich dadurch einige PHP-Fehler und bin dabei diese zu beheben. :)

0
christian99975 
Beitragsersteller
 08.12.2018, 15:53
@christian99975

Habe schon mehrere behoben, die z.B. durch fehlende Klammern entstanden sind und nun sitze ich vor diesem Fehler "Uncaught Error: Call to a member function prepare() on null" in Zeile 17 deiner register.php. Das ist die Select-Abfrage der function alreadyRegistered.

0
regex9  08.12.2018, 16:43
@christian99975

In Zeile 79 habe ich aufgrund eines Kommentars eine Klammer nicht geschlossen (ebenso fehlt dann das Semikolon nach der Anweisung). Das ist der einzige Syntaxfehler. Such zudem nach den zwei Vorkommen von //TODO im Quelltext - in meiner Liste hatte ich zu region, etc. geschrieben, die ich vorerst auskommentiert hatte.

Bezüglich deines null-Fehlers: Ergänze die Funktionen, die $pdo nutzen (register, alreadyRegistered), um einen Parameter $pdo.

function alreadyRegistered($mail, $pdo) { // ...

In den Fällen, in denen sie aufgerufen werden, muss die Variable $pdo übergeben werden (Zeile 64, 84).

1
christian99975 
Beitragsersteller
 08.12.2018, 23:53
@regex9

Oh gott, ja stimmt. Nun scheint das Skript zu laufen. Allerdings nur bis zum else von "if (register($userData, $pdo))" Es wird also ausgegeben das ein Fehler aufgetreten ist. Das werde ich mir nun aber erst morgen anschauen... :)

0
regex9  09.12.2018, 01:37
@christian99975

Ändere diese Zeile:

return $statement->execute(array($data));

zu:

return $statement->execute($data);
0
christian99975 
Beitragsersteller
 10.12.2018, 12:55
@regex9

Daran lag es leider nicht. Ich muss aber jetzt erst mal wiede re an die Arbeit...

0

Ist das alles? Du hast dich ja noch gar kein PDO Objekt.

Desweiteren solltest du im query sicherheitshalber noch ein "LIMIT 1" anhängen.


regex9  24.11.2018, 02:10

Der FS holt sich mit einem einzelnen fetch nur einen Eintrag aus dem ResultSet, dies ist dir möglicherweise entgangen. Ob dieses einen Eintrag beinhaltet oder mehrere, macht daher logisch betrachtet keinen Unterschied. Sinnvoll wäre ein Begrenzung, wenn eine E-Mail-Adresse für mehrere Accounts erlaubt wäre. Dann jedoch wäre die Logik des Login deutlich zu hinterfragen, denn die fußt bisher auf der Annahme, dass jede E-Mail-Adresse nur einmal vorkommt.

1
Kieselsaeure  23.11.2018, 20:35

Ps: keine Ahnung ob der GF Editor die Formatierung versaut hat aber es wäre nett wenn du das nochmal ordentlich formatiert mit syntax highlighting auf pastebin.org hochlädst. Zumindest mit dem Handy blickt man nur schwer durch.

0