Tokens signieren
Teil des Abschnitts Authentifizierung und Sicherheit der Node.js-Journey von Coddy. Lektion 15 von 24.
Ein Token ist nur dann etwas wert, wenn niemand sonst einen erstellen kann. Bei HS256 kombiniert jose den Header und die Nutzlast mit einem Geheimnis, das nur der Server kennt, und hasht das Ergebnis. Dieser Hash ist die Signatur. Ohne das Geheimnis kann niemand einen passenden Token erstellen.
Das Geheimnis stammt aus einer Umgebungsvariable, process.env.JWT_SECRET, niemals aus dem Code: Code wird geteilt, kopiert und eingecheckt. Diese Lektion legt JWT_SECRET für dich fest. jose benötigt es als Bytefolge.
const key = new TextEncoder().encode(process.env.INVITE_SECRET);
const invite = await new SignJWT({ room: 'design' })
.setProtectedHeader({ alg: 'HS256' })
.setSubject(guestId)
.setIssuedAt()
.setExpirationTime('2d')
.sign(key);setSubject erwartet Text, und setExpirationTime erwartet eine Zeitspanne wie '30m' oder '2d'.
Aufgabe
EinfachApps, die deine API aufrufen, etwa eine Telefon-App, speichern Cookies nicht wie ein Browser. Gib ihnen stattdessen ein Token: ein signiertes JWT, das sie mit jeder Anfrage mitsenden. POST /token ist der Login für diese Apps.
- Importiere in
server.jsSignJWTausjosenebendecodeJwt. Wandle oberhalb der Routenprocess.env.JWT_SECRETmit einemTextEncoderin Bytes um, wie in der Lektion gezeigt, dennjosesigniert mit Bytes. Diese Lektion setztJWT_SECRETfür dich. - Füge
POST /tokenhinzu. Es empfängt denselben Body wie der Login,{"username": "ada", "password": "correct horse"}, und prüft Benutzername und Passwort genauso wiePOST /login. Wenn sie falsch sind, antworte mit Status 401 und{"error": "Wrong username or password"}. - Wenn sie richtig sind, signiere ein Token und antworte damit als
{"token": "..."}. Das Token muss:- den Algorithmus HS256 verwenden und mit den Bytes von
JWT_SECRETsigniert sein; - den Anspruch
usernamein seiner Payload enthalten; - die
_iddes Benutzers als Text als Betreff haben. VerwendetoHexString(), dajoseeinen Betreff ablehnt, der kein Text ist; - eine Stunde nach seiner Erstellung ablaufen.
Ein Token aus dieser Lektion sieht so aus:
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkYSIsInN1YiI6IjY2ZjFjMmE5ZTRiMGExYjJjM2Q0ZTVmNiIsImlhdCI6MTc5MzM2MTYwMCwiZXhwIjoxNzkzMzY1MjAwfQ.EdCrjAKSjZowcjE3-IjKttBLVK1VKKbL9DPnJDLdgug - den Algorithmus HS256 verwenden und mit den Bytes von
Probiere es aus: Drücke „Ausführen“. Sende POST /signup mit {"username": "ada", "password": "correct horse"}. Sende POST /token mit demselben Body: Status 200 und {"token": "..."}. Kopiere das Token und sende GET /inspect?token=, gefolgt davon. Du siehst etwas wie {"username": "ada", "sub": "66f1c2a9e4b0a1b2c3d4e5f6", "iat": 1793361600, "exp": 1793365200}: exp liegt 3600 Sekunden nach iat. Sende POST /token mit {"username": "ada", "password": "wrong horse"}: Status 401.
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 enthält: GET /inspect?token=...
app.get('/inspect', (req, res) => {
try {
res.json(decodeJwt(String(req.query.token ?? '')));
} catch {
res.status(400).json({ error: 'Not a JWT' });
}
});
// Die Admin-Seite. Keinem Claim kann vertraut werden, bevor seine Signatur überprüft wurde.
app.get('/admin', (req, res) => {
res.status(403).json({ error: 'Admins only' });
});
app.listen(3000, () => {
console.log('Vault is running on port 3000');
});
Alle Lektionen in Authentifizierung und Sicherheit
Übe selbstständig: Express-Playground