Webtechnologien Wintersemester 2024

 

Express

  • Populärstes Web-Framework für Node.js
import express from 'express';

const app = express();

app.get('/', (req, res) => { // app.METHOD(PATH, HANDLER)
  res.send('Hello World!');
});

app.listen(3000, () => {
  console.log('Example app listening on port 3000!');
});
  • erleichtert den Umgang mit Node.js HTTP und beschleunigt dadurch die Entwicklung
  • app-Object das verschiedene Funktionen bereit stellt
  • reichhaltiges Öko-System

Query-Parameter

import express from 'express';

const app = express();

// GET /search?q=immobilienkredite
app.get('/search', (req, res) => {
  res.send(`You searched for: ${req.query.q}`);
});

app.listen(3000, () => {
  console.log('Example app listening on port 3000!');
});
  • nodemon vorstellen

Middlewares

app.use((req, res, next) => {
   console.log('before');
   next();
});

app.get('/', (req, res, next) => {
   res.send('Hello');
   next();
});

app.use((req, res, next) => {
   console.log('after');
   next();
});
  • mächtiges Werkzeug: Middlewares
  • Middleware: Funktionen die bei Ein- und Austritt durchlaufen werden
  • Request-Handler: request, response, next

Middleware Beispiel

import express from 'express';

const app = express();

const requestTime = (req, res, next) => {
  req.requestTime = Date.now();
  next();
};

app.use(requestTime);

app.get('/', (req, res) => {
  res.send(`Page requested at ${req.requestTime}`);
});

app.listen(3000);
  • das Beispiel fügt jedem Request eine Uhrzeit bei
  • Beispiel umbauen
    • Zeit zur Erzeugung der Antwort berechnen

Statische Dateien

  • Middleware static liefert statische Dateien aus
app.use(express.static('public'));
touch public/styles.css
<link rel="stylesheet" href="/styles.css">
  • noch mal: es geht in dieser Schulung nicht um Node, sondern um Web-Technologien – was wir uns anschauen, lässt sich auch auf andere Sprachen übertragen
  • integrierte Middleware: static liefert Dateien aus

Templates

app.set('view engine', 'ejs'); // views sind vom typ "ejs"
app.set('views', 'views'); // … und liegen im ordner "views" (default)

// render "views/profile.ejs" mit daten aus `user`
res.render('profile', { user: req.user });
<h1>Hallo <%= user.name %></h1>
  • Trennung von Geschäftslogik und Präsentation
  • HTML mit eingebetteten Datenfeldern und ein paar Kontrollstrukturen
  • Aufgaben:
    • Darstellung von Variablen
    • Kontrollstrukturen (if, else)
    • Iteration (for each)
    • Sicherheit: automatisches Escaping (kommen wir gleich zu)
  • viele Umsetzungen: EJS (default), Pug, Handlebars, Hogan

Formulare

import bodyParser from 'body-parser';

// für `application/x-www-form-urlencoded` (normale formulare)
app.use(bodyParser.urlencoded({ extended: false }));
// für `application/json` (später wichtig für ajax-zeugs)
app.use(bodyParser.json());

app.post('/login', (req, res) => {
  console.log(req.body.username, req.body.password);
});
  • parsed übermittelte formulardaten und macht sie unter req.body verfügbar
  • niemals trauen! all user input is evil!

Validierung von Formulardaten

  • Validierung: Validierung ist der Prozess der Sicherstellung, dass gegebene Eingabedaten in den Bereich der gültigen Eingabewerte fallen.
  • Leicht falsch zu machen, schwer richtig, und nie mit Gewissheit.
  • Sanitization (filtering, cleaning): Entfernen von potentiell gefährlichen Zeichen
    • warum? Was ist das Ziel?
    • Namen lassen sich quasi nicht validieren (zu kurz? Li; keine Zahlen? George III, nur ASCII? René Šliževičiūtė)
    • E-Mails lassen sich nur durch Versand eines Links validieren, alles andere ist zu kurz gedacht
  • Escaping: Umwandeln von potentiell gefährlichen Zeichen in andere Darstellungsform
  • Escape-on-Input vs Escape-on-Output
    • Ist <script>alert(1)</script> ein valider Benutzername? Ist "; drop table users;-- ein valider Suchbegriff? Risiko von Fehler in Presentation-Layer an einer Stelle vs. Double-Escaping
    • Antwort: On Output. Weil
      • a) unterschiedliche Ziel-Umgebungen, z.B. HTML, JSON, URL, PDF, Excel und
      • b) sonst intern falsche Darstellung (suche nach amp findet &)
      • HTML: ‘ < > &
      • URLs: / : & ? #, text starting with javascript:
      • Javascript:
      • SQL:
      • JSON:
    • Noch schwieriger, wenn eins in anderem enbedded
  • immer im Backend!
  • Zusammenfassung: Validierung aller Inputs und Escaping aller Outputs vor Darstellung
  • Express escaped automatisch

Cookies

import cookieParser from 'cookie-parser';

app.use(cookieParser());

app.use((req, res, next) => {
  req.trackingId = req.cookies.trackingId;

  if (!req.trackingId) {
    const trackingId = Date.now().toString(32);
    res.cookie('trackingId', trackingId);
    req.trackingId = trackingId;
  }

  next();
});
  • schauen wir uns auch Drittanbieter-Middlewares an:
    • cookie-parser (können auch direkt aus HTTP-Header ausgelesen werden, aber so viel leichter)
    • body-parser für Body (e.g. Formulardaten)
  • Beispiel: Tracking / Wiedererkennen von Besuchern (z.B. zur Analyse von User-Flows)

Signed Cookies

  • Signiert (HMAC), nicht verschlüsselt
import cookieParser from 'cookie-parser';

app.use(cookieParser('ein geheimnis')); // geheimnis definieren

res.cookie('key', value, { signed: true }); // cookies signiert setzen

const { value } = req.signedCookies; // signierte cookies auslesen
  • man sollte nie im klartext daten in cookies speichern, weil diese verändert werden können
  • signierung: folgender wert ist garantiert von uns geschrieben; aber wert ist offen lesbar
  • https://github.com/tj/node-cookie-signature/blob/master/index.js#L19

Benutzerverwaltung

import * as argon2 from "argon2";

app.post('/register', (req, res) => {
  argon2.hash(req.body.password).then((hash) => {
    addUser(req.body.email, hash);
    res.redirect('/login');
})});
app.post('/login', (req, res) => {
  argon2.verify(getUser(req.body.email).hash, req.body.password)
    .then(() => { // ✅ korrektes Passwort
      const session = generateToken();
      addSession(session, email);
      res.cookie('session', session, { httpOnly: true, secure: true });
      return res.redirect('/');
    }).catch(() => { // ❌ falsches Passwort
      res.sendStatus(401);
    });
});