Menu
CoddyTech

Was ein JWT ist

Teil des Abschnitts Authentifizierung und Sicherheit der Node.js-Journey von Coddy. Lektion 9 von 17.

Für eine Sitzung ist bei jeder Anfrage eine Datenbankabfrage erforderlich. Ein JWT (JSON Web Token) enthält die Informationen selbst: drei base64url-Teile, die durch Punkte verbunden sind. Im Header wird der Algorithmus angegeben, die Nutzdaten enthalten die Claims (sub dafür, wessen Token es ist, exp dafür, wann es abläuft), und die Signatur beweist, dass der Server es erstellt hat.

Base64url ist eine Kodierung, keine Verschlüsselung. Jeder, der ein Token besitzt, kann dessen Nutzdaten lesen:

const payload = token.split('.')[1];
JSON.parse(Buffer.from(payload, 'base64url').toString());
// { sub: '42', name: 'Lin', exp: 1767225600 }

decodeJwt(token) aus jose macht dasselbe und überspringt die Signaturprüfung. Lege daher niemals ein Geheimnis in die Nutzdaten und entscheide niemals etwas anhand eines Claims, den dir decodeJwt geliefert hat: Jeder kann beliebige Claims verfassen.

challenge icon

Aufgabe

Einfach
  • GET /inspect?token=... gibt die Claims des Tokens als JSON oder den Status 400 mit { "error": "Not a JWT" } zurück.
  • GET /admin antwortet auf jede Anfrage mit dem Status 403 und { "error": "Admins only" }.

Sende zuerst Mallorys Token an GET /admin?token= und sieh nach, was es öffnet: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYWxsb3J5IiwiYWRtaW4iOnRydWV9.c2lnbmF0dXJl. Korrigiere es und sende dann dasselbe Token an /inspect.

Probier es selbst

import express from 'express';
import { decodeJwt } from 'jose';
import { users } from './db.js';
import { hashPassword, verifyPassword } from './passwords.js';

const app = express();
app.use(express.json());

app.post('/signup', async (req, res) => {
    const username = String(req.body.username ?? '').trim().toLowerCase();
    const password = String(req.body.password ?? '');
    if (!username || password.length < 8) {
        return res.status(400).json({ error: 'Pick a username and a password of at least 8 characters' });
    }
    if (await users.findOne({ username })) {
        return res.status(409).json({ error: 'That username is taken' });
    }
    await users.insertOne({ username, passwordHash: await hashPassword(password) });
    res.status(201).json({ username });
});

app.post('/login', async (req, res) => {
    const username = String(req.body.username ?? '').trim().toLowerCase();
    const user = await users.findOne({ username });
    if (!user || !(await verifyPassword(String(req.body.password ?? ''), user.passwordHash))) {
        return res.status(401).json({ error: 'Wrong username or password' });
    }
    res.json({ username: user.username });
});

// Zeigt, was ein Token aussagt: GET /inspect?token=...
app.get('/inspect', (req, res) => {
    res.status(501).json({ error: 'Not written yet' });
});

// Die Admin-Seite.
app.get('/admin', (req, res) => {
    const claims = decodeJwt(String(req.query.token ?? ''));
    if (claims.admin === true) {
        return res.json({ secret: 'The vault key is 42' });
    }
    res.status(403).json({ error: 'Admins only' });
});

app.listen(3000, () => {
    console.log('Vault is running on port 3000');
});
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Authentifizierung und Sicherheit