Einfache PHP & Datenbank (MySQL) basierte Benutzerverwaltung (Login/Logout/Registrierung) - AJAX Kompatibel

  • Ziel: eine einfache Datenbank basierte Benutzerverwaltung erstellen
    Verwendete Techniken: PHP, PDO
    Schwierigkeitsgrad: für Anfänger geeignet, Grundkenntnisse über die Funktionsweisen von PHP und SQL vorausgesetzt
    Anmerkungen: verwendet PDO, wegen dem Sicherheitsfaktor. Keine anwendung von OOP um das ganze möglichst 'primitiv' zu halten.
    Kommentare: Der Code ist mit Zeilenkommentaren ausgestattet. Diese sind nicht entsprechend der Konvention in Deutsch und dienen der Erklärung.
    Fehlerbehandlung: Fehler werden in ein Array geschrieben, außerdem gibt es einen 'globalen' Statuscode der Erfolg/Misserfolg oder Systemfehler darstellen kann. Wenn debug deaktiviert ist, werden diese Fehler am Ende des Scripts gelöscht und nur der Statuscode übergeben.
    Debugging: Wenn aktiviert wird das Fehler-Array mit übergeben.


    1. Passwort Speicherung mit aktueller Technik
    Wir benutzen neue Technik zur Speicherung von Passwörtern um es Hackern möglichst schwer zu machen die Passwörter auszulesen, wenn sie bereits im Besitz des Password-Hashes sind.
    Der Passwort-Hash ist eine Einwegrepräsentation des Passworts. Er kann bis zu 255 Zeichen lang werden und wird in der Datenbank in einem VARCHAR(255) Feld gespeichert.
    Die Funktion password_hash() generiert uns einen, mit dem zweiten Parameter legen wir fest das der neueste Algorithmus benutzt werden soll. Die Ausgabe sieht meist nicht gleich aus, dies liegt in der 'Natur' der Funktion.

    PHP
    1. $entered_password = 'test';//Eingegebenes Passwort
    2. $hashed_password = password_hash( $entered_password, PASSWORD_DEFAULT );
    3. echo $hashed_password;//out: $2y$10$alSE7w6VMVsWMhoy1EIv5OXpK/Xz2CJxeO8nukDYVV2XTzZIMMavq


    Um Hackern die Suppe zu 'verpfeffern' fügen wir zu unserem Passwort noch einen Pfeffer hinzu; er sorgt dafür das Hacker zugriff auf das Dateisystem haben müssen und die Passwörter nicht via 'rainbow table' auslesen können (also durch vergleichen mit eigenen Hashes. Der Pfeffer ist eine Zeichenkette, die an das Passwort angehängt wird und somit das eingegebene Passwort serverseitig 'erweitert'. Der Pfeffer sollte möglichst sicher aufbewahrt werden. Das Passwort qwertz wird mit unserem Pfeffer #h$na+= erweitert. Eine rainbow table enthält den Hash von quertz aber sicher nicht den Hash von qwertz#h$na+=. Bei der späteren überprüfung des Passworts muss der Pfeffer natürlich auch angehängt werden. Vorallem aber sorgt der Pfeffer dafür das der Hacker auch Zugriff auf das Dateisystem hat, um den Pfeffer zu ermitteln, denn sog. Wörterbuch Attacken werden auch schon durch den Salt verhindert.
    Hier gibt es eine schöne Erklärung: http://www.martinstoeckli.ch/hash/de/

    PHP
    1. $entered_password = 'test';//Eingegebenes Passwort
    2. $pepper = 'G$#(bPx!';
    3. $hashed_password = password_hash( $entered_password.$pepper, PASSWORD_DEFAULT );
    4. echo $hashed_password;//out: $2y$10$7V5rI8B5NIUX3dEVKX/Gnen3.ZIq6T06qXOLSBhwVnFukP4PLO6Lm


    Für alles folgende brauchen wir nun ein paar kleine Helferlein um mit Fehlern gut klar zu kommen.
    Zuerst eine Funktion die Fehler notiert und dann eine die den Status des Scriptes bereitstellt.
    Wir definieren uns numerische Statuscodes die wir zur Beschreibung des Status verwenden:

    PHP
    1. //Statuscodes
    2. define('SYS_ERROR' , E_ERROR );// 1 -> SYSTEM FEHLER
    3. define('SYS_WARNING', E_WARNING );// 2 -> SYSTEM WARNUNG
    4. define('SUCCESS' , 0 );// 0 -> ERFOLG
    5. define('FAIL' , 300 );// 300 -> MISSERFOLG
    6. //Basierend auf FAIL // 301 -> Passwort zu KURZ
    7. //Basierend auf FAIL // 302 -> UID zu KURZ
    8. //Basierend auf FAIL // 311 -> falscher BENUTZERNAME
    9. //Basierend auf FAIL // 312 -> falsches PASSWORT
    10. //Basierend auf FAIL // 313 -> Benutzer EXISTIERT BEREITS


    Dann eine Funktion die die Fehler in ein Breitgestelles Array schreibt (Was Bestandteil des Feedback ist).


    Jeztzt können wir einen Fehler folgendermaßen speichern:

    PHP
    1. error('meine Fehlermeldung','#fehlercode');


    Damit wir auch noch an die Fehler drankommen bevor das Script beendet wird function stop()
    Diese setzt den 'globalen' Status des Scripts und ist somit die letzte Aktion


    Nun können wie an die Datenbankverbindung. Wir stellen die Verbindung mit der Datenbank via PDO her.

    PHP
    1. $user = 'dbclient'; //Datenbank Benutzername
    2. $pass = ''; //Datenbank Passwort
    3. $db = 'dbclient_forum'; //Datenbank Name
    4. $host = 'localhost'; //Datenbank Host
    5. $opt = array(); //Optionale Einstellungen
    6. $dbh = new PDO('mysql:host='.$host.';dbname='.$db, $user, $pass, $opt);//Verbindung herstellen und in $dbh speichern


    Um Fehler auffangen zu können verwenden wir ein try/catch


    Ihr müsst kurz in PHPMyAdmin oder in die Konsole/Terminal und euch eine entsprechende Tabelle in der Datenbank erstellen

    Code
    1. CREATE TABLE IF NOT EXISTS `tut_loginsys_users` (
    2. `uid` varchar(50) NOT NULL,
    3. `password` varchar(255) NOT NULL,
    4. PRIMARY KEY (`uid`),
    5. UNIQUE KEY `uid` (`uid`)
    6. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;


    Jetzt benötigen wir eine 'User ID' (UID) für die Benutzer Identfikation, in unserem fall ist der Benutzername die eindeutige Identifizierungsmöglichkeit.
    Jetzt wird der Passworthash zusammen mit der UID in der Datenbank gespeichert.
    Wenn dies fehlschlägt, liegt es entweder daran das der Benutzername schon vorhanden ist oder an einem anderen Fehler.


    Fertig, der Benutzer test mit passwort test ist nun in der Datenbank gespeichert


    2. Passwort Validieren mit aktueller Technik
    Wir benutzen hier auch die Funktionen aus der Registrierung und dazu starten wir noch eine Session:

    PHP
    1. session_start();


    Um das Passwort zu validieren reicht es nicht die Eingabe auch zu hashen und dann zu vergleichen, da der Algo. verschiedene Hashes für den gleichen Wert errechnet.
    Wir nutzen also password_verify(). Als erster Parameter kommt das eingegebene Passwort, als zweiter Parameter der gespeicherte Hash. Den Pfeffer sollte man auchnicht vergessen, da sonnst die Passwörter nie übereinstimmen.

    PHP
    1. $entered_password = 'test';//Eingegebenes Passwort
    2. $stored_password = '$2y$10$7V5rI8B5NIUX3dEVKX/Gnen3.ZIq6T06qXOLSBhwVnFukP4PLO6Lm';//Gespeichertes Passwort
    3. echo password_verify( $entered_password, $stored_password );//out: 0 // <- da der Pfeffer fehlt


    Jetzt brauchen wir nur noch eine Abfrage aus der DB wie der UID zugehörige Passworthash ist.
    Hier wird geprüft ob der Benutzer Existiert und ob das Passwort stimmt.


    Fertig, nun können wir Benutzer anlegen und Passwörter überprüfen.
    Außerdem können wir anhand der Session prüfen ob der Benutzer eingeloggt ist.


    3. Logout
    Das logout ist recht simpel gehalten mit einem einfachen

    PHP
    1. $_SESSION['login'] = false;


    4. Zusammenfassung in ein Script
    Alles zusammen in einem Script gibt die Möglichkeit das ganze via AJAX aufzurufen oder auch ganz normal via HTTP oder eben via Include (parameter setzen).
    Die Variablen befinden sich jetzt alls im array $X, dies erschwert zwar die Leserlichkeit des Codes, erleichtert aber den Zugruff in Funktionen auf Variablen.
    Lest noch Zeilen 7-9 und dann benutzt es! ^^


    Wenn etwas unbeantwortet geblieben ist, oder schlecht beantwortet wurde, FRAGT nach, ansonnsten viel Spas! :)

  • Hallo Wolf, großartiges Tutorial. Das System hat alle nötigen Funktionen.


    Versuche mich gerade daran dieses System auf meiner Webseite einzubinden, bin aber auf ein Problem gestoßen. (Bin leider noch Anfänger)


    Und zwar habe ich 3 HTML Seiten, einmal eine Login Seite, Register Seite und zuletzt die Nutzseite (wohin man gelangt nachdem man sich erfolgreich eingeloggt hat) erstellt.
    Ich habe nun deinen Code aufgeteilt in 2 Php Dateien einmal MySQL Verbindung und den Rest in einer anderen Php Datei. Diese beiden werden dann auf jeder HTML Seite eingebunden.


    Den "name" der Input Felder habe ich gleich wie in den von dir gegebenen Funktionen angepasst: "username", "password". An deinem Code habe ich noch nichts verändert mit Ausnahme des $X['secure']['pepper']="".


    Beim Öffnen einer Html Seite bekomme ich noch anfangs mehrere Bemerkungen, wie Undefined index:password, ...username , ...action (liegt vermutlich noch daran dass ich das Formular nicht abgesendet habe und keine Werte übergeben werden). Nachdem ich das Formular abgebe, verschwinden diese Bemerkungen mit Ausnahme der "Undefined index: action" und bekomme immer die Meldung "Check your Input!" egal ob in der register oder login html Seite. Und beide Funktionen scheinen nicht zu funktionieren, habe auch in der Datenbank geguckt, keine Beiträge sind hinzugefügt worden (die table habe ich erzeugen lassen). Die Login Funktion konnte ich nicht testen, da ich nicht weis wie das Passwort gehashed aussieht.


    Könnte es daran liegen, dass die Funktion meinen Wert nicht bekommt, ob die Seite login oder register Seite ist? Habe versucht in der Form und im Button als "name" -> "login" anzuhängen ohne Erfolg.



    MfG,
    Danny94.

  • Könntest du bitte das HTML der Formulare noch posten? :)
    Wie du richtig erkannt hast, liegt das daran, dass das Formular noch nicht abgesendet wurde, allerdings sollten alle drei Notices verschwinden nach dem absenden, wahrscheinlich hast du dich einfach nur Vertippt oder einen Bug gefunden ^^ ;)

  • Ups ja ohne Quellcode kannst du mir natürlich nicht helfen, tut mir Leid ;D.
    Ich habe für jede Html Seite eine Template Seite die noch vor jeder Ausgabe durchgearbeitet wird, deshalb auch 2 Dateien für beispielsweise der login seite.


    Template Login:
    #Klick#


    Html Teil:
    #Klick#



    Register template ist identisch..


    Register html:
    #Klick#


    Wollte den Code direkt hier posten, habe es aber nicht hingekriegt neue Zeilen im Code einzufügen oO.

  • Hi, du must noch einen Input mit der action einbauen

    HTML
    1. <form>
    2. <input type="hidden" name="action" value="login">
    3. <input type="email" name="username" required>
    4. <input type="passoword" name="password" required>
    5. <input type="submit">
    6. </form>

    Z.B. so.. -> undefined index meint, dass im array ein schlüssel/wert-paar nicht gefunden werden konnte.. in diesem Fall im $_POST array der schlüssel 'action'

  • Hallo Wolf und alle anderen,
    danke für das gute Tutorial. Eigentlich ist ja alles ziemlich gut erklärt.


    ansich habe ich das loginscript per php include im header eingebunden.
    Dann folgt in der registrieren.php das Formular zum registrieren:

    HTML
    1. <form id="login" method="post" action="action">
    2. <input name="username" placeholder="Nutzername" class="user" required /> <br>
    3. <input name="password" placeholder="Password" class="password" type="password" required /> <br>
    4. <input name="submit" class=" btn-outline-inverse btn-lg loginbtn" type="submit" value="Registrieren" /> <br>
    5. </form>


    Das Loginformular sieht fast genauso aus.


    Öffne ich die Seite mit dem Formular, werden mir die Folgenden Fehlermeldungen ausgegeben:

    Zitat

    Notice: Undefined index: username in C:\xampp\htdocs\loginscript.php on line 54


    Notice: Undefined index: password in C:\xampp\htdocs\loginscript.php on line 55


    Notice: Undefined index: action in C:\xampp\htdocs\loginscript.php on line 57
    Check your Input!


    wie genau kann ich die beheben? Die Variablen werden ja normalerweise erst durch das ausfüllen des Formulars definiert?

  • Hi, Dein Fehler ist, dass du das Script in der Seite includest, in der Du das Formular bereit stellst. Das muss aber eine extra Datei sein.


    Dann ist Dein HTML Formular falsch. Das action Attribut im form tag muss der Dateinamen des Login Scripts sein.


    Und zuguterletzt erwartet das Login Script noch ein hidden input Feld mit Namen "action" und value "login", das wurde schon einem vorigen Frager in diesem Thread gezeigt.

  • Habe diese Anleitung mit Script gefunden, weil ich genau das gesucht habe. Daenbankverbindung klappt, Registrerung von neuen Nutzern auch. Nur der Login will nicht klappen. Ich bekomme immer "Check your Input!" eingeblendet. Ich habe das Ganze in Verbindung mit Bootstrap folgendermaßen implementiert:


    <nav class="navbar navbar-inverse navbar-fixed-bottom">

    <div class="container" style="display: flex; justify-content: space-between;">

    <div id="navbar" class="navbar-collapse collapse">

    <form action="./lib/loginscript.php" method="POST class="navbar-form navbar-right">

    <div class="form-group">

    <input type="text" name="uid" placeholder="Benutzer" class="form-control">

    <input type="hidden" name="action" value="login">

    </div>

    <div class="form-group">

    <input type="password" name="password" placeholder="Passwort" class="form-control">

    </div>

    <button type="submit" name="absenden" class="btn btn-success">Anmelden</button>

    </form>

    </div><!--/.navbar-collapse -->

    </div>

    </nav>


    Alternativ habe ich versucht das Script direkt einzubinden und die login-Funktion über

    define( "PATH_SOURCE", realpath( dirname( __FILE__ ) ) . '/lib/' );

    include_once(PATH_SOURCE . "loginscript.php");

    if(isset($_POST['ausführen'])) {login();}

    aufzurufen.


    Das hat aber auch nicht geklappt.
    Was mache ich falsch?

    Liebe Grüße,
    Berndi

  • Habe den Fehler entdeckt.
    Einer der POST-Variablen war falsch (uid anstatt username)

    Jetzt muss ich nur noch irgendwie herausfinden, wie ich meiner Webseite beibringe, dass ein User eingeloggt ist und was da den Unterschied macht.

    Bedeutet:
    Wie verarbeite ich denn nun das erfolgreiche Login?
    Gibt es irgendwelche Rückgabewerte, die ich abfragen und weiterverwerten kann?

    bzw...

    Wie müsste denn so ein AJAX-Aufruf für dieses Script aussehen?

    Sorry, dass ich so blöd frage...

    Gruß,
    Berndi

  • Gut, habe noch ein wenig recherchiert und eine "typische" jQuery-Einbindung gefunden.
    Jetzt sieht das bei mir so aus:


    <head>

    <script>  

      $(document).ready(function() { 

       $('#ParadenLogin').submit(function() { 

        $.post( './lib/loginscript.php', $(this).serialize(), function(data) { 

         $('#response').html(data); 

         }) 

        .fail(function () { 

         alert( "Fehler" ); 

         }); 

       }); 

     }); 

    </head> 

    <body>

    <nav class="navbar navbar-inverse navbar-fixed-bottom"> 

     <div class="container" style="display: flex; justify-content: space-between;"> 

      <div id="navbar" class="navbar-collapse collapse"> 

       <form id="ParadenLogin" class="navbar-form navbar-right"> 

        <div class="form-group">

         <input Type="text" name="username" placeholder="Benutzer" class="form-control">

        </div> 

        <div class="form-group"> 

         <input type="password" name="password" placeholder="Passwort" class="form-control"> 

        </div> 

        <input type="hidden" name="action" value="login"> 

        <input type="hidden" name="json" value="true"> 

        <button type="submit" name="absenden" class="btn btn-success">Anmelden</button> 

       </form> 

      </div><!--/.navbar-collapse --> 

     </div> 

    </nav> 

    <div id="response"></div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js">

    </script> <script src="./js/bootstrap.min.js"></script>

    </body>


    Leider funktioniert es nicht. :-(
    Der "response"-Container bleibt leer.