06_Login

Code-Dateien

DateinameAktion
CODECode_bank.zipDownload
CODECode_pizza.zipDownload
CODECode_student.zipDownload

Videos

DateinameAktion
VIDEOVideo_Bank_DAbspielen
VIDEOVideo_Pizza_EAbspielen

Lernmaterialien

Stateless

In the context of a website or web application, stateless means:

The server does not remember anything about the client between two HTTP requests.

Each request is handled independently, as if it were the first one.


What does this mean in practice?

Stateless

  • Client sends a request → server responds

  • Next request → server has no stored memory of the previous one

  • If the server needs to know who you are, the client must send the information with every request

    • e.g. an authentication token (JWT) or a cookie with each request

Stateful

  • The server stores state (for example, a session) about the user

  • The client sends a session ID on each request

  • The server looks up the session to know who the user is

  • Harder to scale, because session data must be shared or centralized


Common examples

  • REST APIs are typically stateless

  • Web apps using server-side sessions are stateful


Simple definition (one sentence)

Stateless means the server does not keep user state between requests; every request must contain all required information.

Session

A session is a way to keep track of a user across multiple HTTP requests.

It allows the server to remember state (for example “logged in”) while the user navigates through a website.


How a session typically works

1) Login

The user sends a username and password to the server.

2) Server creates a session

The server generates a session ID (for example abc123) and stores session data such as:

  • userId

  • loggedIn = true

  • user role

This data is stored on the server (memory, Redis, or a database).

The server sends a cookie to the browser:

Set-Cookie: sessionId=abc123; HttpOnly; Secure

4) Subsequent requests

For every later request, the browser automatically sends:

Cookie: sessionId=abc123

The server uses the session ID to look up the stored session data and recognize the user.


Session vs JWT (quick comparison)

Session JWT
Server stores session data Server stores no session data
Stateful Stateless
Cookie usually only contains session ID Token contains user info
Easy to revoke Harder to revoke

Why sessions are useful

  • Users stay logged in across pages

  • Server can keep user-specific state (e.g. shopping cart, permissions)


One-sentence definition

A session is a server-side mechanism that keeps user state across multiple HTTP requests using a session ID, usually stored in a cookie.

Cookies

Cookies are small pieces of data that a website stores in your browser and sends back to the server on later requests.

A cookie helps a website “remember” something about you (for example: that you’re logged in, your language choice, or items in a cart).


How cookies work (simple flow)

  1. Server sends a cookie to your browser in the HTTP response header:
Set-Cookie: sessionId=abc123; HttpOnly; Secure
  1. Browser stores it.

  2. On the next request to the same website, the browser automatically includes it:

Cookie: sessionId=abc123

That’s how the server can recognize you across requests.


What cookies are used for

  • Login sessions (keep you logged in)

  • Preferences (language, theme)

  • Analytics (tracking usage)

  • Shopping carts


Types of cookies

1) Session cookies

  • Temporary

  • Deleted when you close the browser

  • Common for login sessions

2) Persistent cookies

  • Stored until an expiration date (e.g., days/months)

  • Used for “Remember me” and preferences

3) First-party vs third-party

  • First-party: set by the site you visit

  • Third-party: set by another domain (often ads/trackers), increasingly blocked by browsers


One-sentence definition

Cookies are browser-stored key/value data that are automatically sent to the same website with future requests to maintain state (like sessions).

Middleware

In web development (especially with Express.js), middleware is a function that runs between the incoming request and the final response.

Think of the request handling like a pipeline:

Request → middleware(s) → route handler → Response


What middleware can do

A middleware function can:

  • read/modify the request (req) (e.g., parse JSON, check auth)

  • read/modify the response (res)

  • end the request by sending a response

  • or pass control to the next middleware using next()


Express middleware signature

function myMiddleware(req, res, next) {
  // do something
  next(); // pass control to the next middleware/route
}

Implementation login

Accounts

accounts.json

[
  {
    "username": "thomas",
    "password": "thomas"
  },
  {
    "username": "susi",
    "password": "susi"
  },
  {
    "username": "test",
    "password": "test"
  },
  {
    "username": "admin",
    "password": "admin"
  },
  {
    "username": "demo",
    "password": "demo"
  },
  {
    "username": "guest",
    "password": "guest"
  }
]

server.ts

import accounts from "./accounts.json" with { type: "json" };
import session from "express-session";

shell

deno add npm:express-session
005.png

Login

login.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Login</title>
  <link rel="stylesheet" href="styles.css">
  <link rel="stylesheet" href="style_login.css">
</head>
<body>
  <header>
    <h1>Students - Login</h1>
  </header>

  <main>
    <div class="login-container">
      <h2>Login</h2>

      <form action="/login" method="post">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" required />

        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required />

        <div class="button-group">
          <button type="submit">Sign In</button>
          <button type="reset">Clear</button>
        </div>
      </form>

      <div class="back-link">
        <a href="/index.html">← Back to Students</a>
      </div>
    </div>
  </main>
</body>
</html>

style_login.css

.login-container {
  max-width: 350px;
  margin: 50px auto;
  background: white;
  padding: 30px;
  border-radius: 8px;
  box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}

h2 {
  text-align: center;
}

label {
  display: block;
  margin-top: 10px;
}

input, button {
  width: 100%;
  padding: 10px;
  margin: 5px 0;
  border: 1px solid #ccc;
  border-radius: 8px;
  box-sizing: border-box;
}

.button-group {
  display: flex;
  gap: 8px;
  margin-top: 10px;
}

button {
  border: none;
  cursor: pointer;
  background: #ddd;
}

button[type="submit"] {
  background: #0078ff;
  color: white;
}

.back-link {
  text-align: center;
  margin-top: 15px;
}

server.ts

import session from "express-session";

app.use(express.urlencoded({ extended: true }));
app.use(
    session({
        secret: process.env.SESSION_SECRET ||
            "your-secret-key-change-in-production",
        resave: false,
        saveUninitialized: false,
        name: "cookie_gt",
        cookie: {
            maxAge: 5 * 60 * 1000, // 5 minutes
            secure: process.env.NODE_ENV === "production",
            httpOnly: true,
        },
    }),
);

import session from "express-session";

Lädt das Middleware-Paket express-session. Damit kann Express pro Nutzer eine Session verwalten (z. B. req.session.userId).

app.use(express.urlencoded({ extended: true }));

Middleware, die Formulardaten aus dem Request-Body liest, z. B. aus HTML-Forms:

  • Content-Type: application/x-www-form-urlencoded

  • Ergebnis: Du kannst dann req.body.name, req.body.password usw. verwenden.

extended: true erlaubt “reichere” Strukturen (verschachtelte Felder) im Body.

app.use(session({...}))

Das ist die Session-Middleware. Sie macht:

  • Setzt/liest ein Cookie (hier heißt es cookie_gt)

  • Verknüpft dieses Cookie mit einer Session

  • Hängt die Session an req.session

Wichtige Optionen:

secret: ...

Schlüssel zum Signieren des Session-Cookies (damit es nicht manipuliert werden kann).

  • In Produktion unbedingt per ENV setzen (SESSION_SECRET) und stark/zufällig wählen.

resave: false

Speichert die Session nicht bei jedem Request neu, wenn sich nichts geändert hat. (Gut für Performance.)

saveUninitialized: false

Speichert keine leeren/neuen Sessions, solange du nichts in req.session ablegst.

  • Gut für Datenschutz (kein Cookie für Besucher, die nie “Session-Daten” bekommen).

Name des Cookies, das im Browser gesetzt wird.

  • Standard wäre connect.sid, du gibst hier einen eigenen Namen.

Cookie-Eigenschaften:

  • maxAge: 5 * 60 * 1000
    Session-Cookie läuft nach 5 Minuten ab (inaktiv/abgelaufen, je nach Store/Browser).

  • secure: process.env.NODE_ENV === "production"
    Cookie wird nur über HTTPS gesendet (in Produktion).

  • httpOnly: true
    Cookie ist nicht per JavaScript im Browser lesbar (document.cookie), schützt gegen XSS-Cookie-Diebstahl.

Middleware

auth.ts

import { NextFunction, Request, Response } from "express";

export function authMiddleware(
    req: Request,
    res: Response,
    next: NextFunction,
) {
    if (req.session && req.session.user) {
        return next();
    }
    // For APIs, return 401. For MVC/Handlebars, use res.redirect('/login')
    res.status(401).json({ error: "Unauthorized" });
}

Der Code definiert eine Express-Middleware, die prüft, ob der Benutzer eingeloggt/autorisiert ist, bevor er eine Route benutzen darf.

Import

import { NextFunction, Request, Response } from "express";

Das sind TypeScript-Typen für Express:

  • Request: das Request-Objekt (req)

  • Response: das Response-Objekt (res)

  • NextFunction: die Funktion next(), mit der man zur nächsten Middleware/Route weitergeht

Middleware-Funktion

export function authMiddleware(req, res, next) {

export heißt: Du kannst sie in anderen Dateien importieren, z. B.:

import { authMiddleware } from "./authMiddleware";

Auth-Check (Session-basiert)

if (req.session && req.session.user) {
  return next();
}
  • Der Code erwartet, dass du express-session verwendest.

  • Wenn req.session.user existiert, gilt der User als eingeloggt.

  • next() bedeutet: “OK, weiter zur eigentlichen Route”.

Wenn nicht eingeloggt

res.status(401).json({ error: "Unauthorized" });
  • Antwortet mit HTTP 401 Unauthorized

  • und einem JSON-Fehler.

Der Kommentar sagt: In einer klassischen Website (MVC/Handlebars) würdest du statt JSON oft umleiten:

res.redirect("/login");

Kurz: Die Middleware lässt Requests nur durch, wenn in der Session ein user gespeichert ist.

server.ts

import { authMiddleware } from "./auth.ts";

routes

/login

server.ts

app.post("/login", (req: Request, res: Response) => {
    const { username, password } = req.body;

    const account = accounts.find(
        (acc) => acc.username === username && acc.password === assword,
    );

    if (account) {
        req.session.user = { username: account.username };
        return res.json({ message: "Login successful" });
    }

    res.status(401).json({ error: "Invalid credentials" });
});

Anmerkungen:

  • Liest username und password aus dem Request-Body (z. B. aus einem Formular oder JSON).

  • Sucht in accounts (ein Array mit Benutzerkonten) nach einem passenden Eintrag.

  • Wenn gefunden:

    • Speichert in der Session: req.session.user = { username: ... }

    • Damit ist der Benutzer “eingeloggt” (bei späteren Requests ist req.session.user vorhanden).

    • Antwortet mit { message: "Login successful" }

  • Wenn nicht gefunden:

    • Antwortet mit 401 und { error: "Invalid credentials" }

Wichtig: Das ist eine Lern-/Demo-Variante. In echt speichert man Passwörter nicht im Klartext, sondern gehasht.

/loginstatus

server.ts

app.get("/loginstatus", (req: Request, res: Response) => {
    if (req.session.user) {
        return res.json({ loggedIn: true, user: req.session.user });
    }
    res.json({ loggedIn: false });
});

Anmerkungen:

  • Prüft, ob req.session.user existiert.

  • Wenn ja: Benutzer ist eingeloggt → gibt { loggedIn: true, user: ... } zurück.

  • Wenn nein: { loggedIn: false }.

Das ist praktisch für Frontend: “Bin ich noch eingeloggt?”

/safe

server.ts

app.get("/safe", authMiddleware, (req: Request, res: Response) => {
    res.json({ message: "This is a safe route", user: req.session.user });
});

Anmerkungen:

  • Diese Route ist durch authMiddleware geschützt.

  • Ablauf:

    • authMiddleware prüft, ob req.session.user gesetzt ist.

    • Wenn nicht: 401 “Unauthorized”

    • Wenn ja: Route läuft weiter und gibt die “safe” Antwort zurück.

index.html

    </div>
      <p>
        <a href="/login.html">login</a> | 
        <a href="/loginstatus">loginstatus</a> |
        <a href="/safe">safe route</a>
      </p>
  </main>

routes

server.ts

app.delete("/students/:id", authMiddleware, async (req: Request, res: Response) => {
  const id = parseInt(req.params.id);

In dieser Zeile hat sich gegenüber deiner früheren Version genau eine Sache geändert: authMiddleware wurde eingefügt

app.delete("/students/:id",authMiddleware, ...

Das bedeutet: Bevor der DELETE-Handler ausgeführt wird, läuft authMiddleware.

  • Wenn req.session.user existiert → next() → der Delete läuft.

  • Wenn nicht → sofort 401 Unauthorized und der Delete wird nicht ausgeführt.

!!! Die Middleware kann nun bei beliebigen routen eingefügt werden !!!

app.get("/students", ...);                 // public
app.get("/students/:id", ...);             // public
app.post("/students", authMiddleware, ...);
app.put("/students/:id", authMiddleware, ...);
app.patch("/students/:id", authMiddleware, ...);
app.delete("/students/:id", authMiddleware, ...);