7. Persistenz, Autorisierung, Cookies

7.3 Ein einfacher Messageboard-Server mit Login-Funktion

Paradebeispiel für eine Anwendung von Cookies ist, dass der Server erkennt, ob der Client sich überhaupt angemeldet hat. Beispielsweise könnte der Server dem Client beim Login ein Cookie setzen von der Form session_token = 134102381929836 und sich serverseitig eine Liste aller gültigen Session-Tokens speichern (zusammen mit einem Dictionary, welcher Token zu welchem User gehört). Nur Clients, die als Cookie einen gültigen Session-Token vorweisen können, werden bedient. Alle anderen werden zur Login-Seite weitergeleitet. Die Code-Beispiele, die ich im folgenden verwende, bauen auf dem Blogpost Understanding Cookies and Implementing them in Node.js von Catherine Macharia auf. Als erstes können Sie sich hier ein "Skelett" runterladen: 12-my-login-server-no-functionality.zip. Starten Sie den Server mit
npm run dev

Falls das fehlschlägt, müssen Sie eventuell das Projekt "per Hand aufsetzen", wie im Blogpost beschrieben. Wenn Sie gerade im Kurs sind, lassen Sie es mich bitte wissen. Das Projekt besteht aus vier Views: home, login, register und main. Ich habe hier schematisch dargestellt, wie der Navigationsfluss zwischen diesen vier Views aussieht:

Im View main.ejs können Sie Nachrichten posten, die dann öffentlich sichtbar sind (einfach, damit irgendeine Form von Funktionalität existiert).

Übung Fügen Sie meiner Anwendung 12-my-login-server-no-functionality.zip Funktionalität für Autorisierung hinzu. Der Server soll eine Liste von Benutzern mit Passwörtern und, falls eingeloggt, Session-IDs führen. Der Navigationsfluss soll nun so aussehen:
Bei erfolgreichem Login soll eine Session-ID erstellt und als Cookie gespeichert werden. Bei jedem Versuch, das View main.ejs zu laden oder einen Beitrag zu posten, soll der Server überprüfen, ob Benutzername und Session-ID stimmen.

Das Input-Feld für den Benutzernamen im View main.ejs kann jetzt wegfallen.

Bei Logout soll das Cookie für den betreffenden Benutzer gelöscht werden.

Zur Erinnering:
  • So können Sie serverseitig einen Cookie-Eintrag setzen:
  • app.post("/process_login", (req, res) => {
        ...                                 
        res.cookie("session_id", session_id);                                
        ... 
    }
  • So können Sie serverseitig aus dem Cookie lesen:
    app.get("/main", (req, res) => {
      // get the username
      let username = req.cookies.username;
      ... 
    }
  • So können Sie serverseitig ein Cookie löschen:
    app.get("/logout", (req, res) => {
      res.clearCookie("session_id");
      ... 
    }

Meine Implementierung

Meine Implementierung finden Sie unter 13-login-server.zip. Entzippen Sie ihn, gehen in das Verzeichnis und starten Sie den Server per npm run dev. Der Server bietet folgende Endpoints an:

  • GET / schaut, ob Sie bereits eingeloggt sind. Falls ja, schickt es Sie zum Main-View (wo Sie alle Beiträge lesen und einen neuen schreiben können); wenn nein, dann schickt Sie zum Startbildschirm. Beim Startbildschirm können Sie zwischen "create account" und "login" wählen.
  • GET /register schickt Sie zum Registrierungs-Formular.
  • POST /process_create_new_account überprüft die Daten des Registrierungsformulars, schaut zum Beispiel, ob der Username bereits existiert etc. und leitet Sie dann zum Main-View weiter oder mit einer Fehlermeldung wieder zurück zum Hauptbildschirm.
  • GET /login schickt Sie zum Login-Bildschirm.
  • POST /process_login schaut, ob die übermittelten Daten (username und password) mit der "Datenbank" übereinstimmen. Falls ja, setzt der Server relevante Cookies (username und session_id) und leitet Sie zum Main-View weiter; falls nein, dann werden Sie mit einer Fehlermeldung zum Login-Bildschirm geschickt.
  • GET /logout: der Server löscht die Cookies und schickt Sie zum Login-Bildschirm zurück.
  • GET /main schickt Sie zur Hauptseite. Dort gibt es ein Textfeld für die neue Nachricht und eine Tabelle mit allen bisherigen Chat-Nachrichten. Die Tabelle ist erst einmal leer.
  • GET /get-messages: der Server schickt eine Liste von Nachrichten (im Json-Format).
  • POST /add-message: der Server fügt der Liste von Nachrichten die neue Nachricht hinzu und leitet Sie wieder zum Main-View weiter.

Bei den letzten drei Endpoints (/main, /get-messages und /add-message) überprüft der Server, ob die im Requeste mitgelieferten Cookies "stimmen", ob also die Werte username und session_id im Cookie vorhanden sind, und falls ja, ob die session_id mit der übereinstimmt, die sich der Server lokal im Arbeitsspeicher für diesen username gemerkt hat. Stimmt das Cookie nicht, wird der Nutzer mit einer Fehlermeldung wieder zum Login-Bildschirm zurückgeschickt. Es gibt bestimmt ausgefeilte Weisen, dieses Verhalten formalisiert darzustellen. Damit kenne ich mich allerdings nicht aus. Daher hier meine selbst ausgedachte Darstellung:

Der Bereich in der grauen Wolke rechts ist der "geschützte" Bereich, der nur dann betreten / genutzt werden kann, wenn der Browser das richtige Cookie geschickt hat.

Client-seitig müssen Sie den Knopf refresh message klicken, damit neue Nachrichten angezeigt werden (ich habe aus Bequemlichkeit auf Polling und Websockets verzichtet). Der Knopfdruck ruft die Funktion refreshMessages() auf, der per Ajax die Liste von Nachrichten vom Server anfordert und diese dann in der Tabelle anzeigt. Beim Laden der Seite wird auch schon einmal refreshMessages() aufgerufen. Der Server überprüft dabei natürlich immer, ob die im Cookie enthalteten Werte für Benutzername und Session-ID zusammenpassen).

Übung Ich starte jetzt den Server für 14-my-login-server-with-polling.zip auf 193.174.103.62:4014.

"Hacken" Sie ihn. Das heißt, posten Sie auf dem schwarzen Brett im View main.ejs etwas unter meinem Benutzernamen. "Mein" Benutzername ist der, den ich im Kurs vor Ihren Augen neu anlegen werde.

Hier nochmal zur Übersicht: