Tokens verifizieren
Teil des Abschnitts Authentifizierung und Sicherheit der Node.js-Journey von Coddy. Lektion 16 von 24.
Ein API-Client sendet sein Token bei jeder Anfrage in einem Header mit: Authorization: Bearer <token>. Wenn du es mit decodeJwt ausliest, würdest du jedes Token akzeptieren, das jemand eingegeben hat. jwtVerify(token, key) berechnet die Signatur mit deinem geheimen Schlüssel neu und löst einen Fehler aus, wenn sie nicht übereinstimmt, wenn das Token abgelaufen ist oder wenn es überhaupt kein JWT ist. Erst wenn die Funktion zurückkehrt, darfst du payload vertrauen.
async function requireDevice(req, res, next) {
const token = (req.get('authorization') ?? '').replace(/^Bearer /, '');
try {
const { payload } = await jwtVerify(token, deviceKey);
req.device = payload.sub;
} catch {
return res.status(401).json({ error: 'Unknown device' });
}
next();
}Aufgabe
EinfachAkzeptiere jetzt die von dir signierten Tokens. Eine App sendet ihr Token bei jeder Anfrage in einem Header, und der Server prüft die Signatur, bevor er irgendetwas darin vertraut.
- Importiere in
server.jsjwtVerifyausjose. - Schreibe eine Middleware
requireUser(req, res, next). Sie liest denAuthorization-Header. Dieser Header enthält das WortBearer, ein Leerzeichen und anschließend das Token. Hole das Token daraus heraus. - Prüfe das Token mit
jwtVerifyund verwende dabei denselben Schlüssel, mit dem du signierst.jwtVerifylöst einen Fehler aus, wenn die Signatur nicht übereinstimmt, wenn das Token abgelaufen ist oder wenn der Text überhaupt kein Token ist. Antworte in all diesen Fällen und auch dann, wenn kein Header vorhanden ist, mit Status 401 und{"error": "Log in first"}. - Wenn das Token gültig ist, setze
req.userauf ein Objekt mitid, das aus demsub-Claim des Tokens stammt, undusername, das aus dessenusername-Claim stammt. Rufe dannnext()auf. - Füge
GET /memitrequireUserhinzu. Es gibtreq.userzurück:{"id": "66f1c2a9e4b0a1b2c3d4e5f6", "username": "ada"}.
Probiere es aus: Drücke auf „Ausführen“. Sende GET /me: Status 401 und {"error": "Log in first"}. Sende POST /signup und dann POST /token, jeweils mit {"username": "ada", "password": "correct horse"}, und kopiere das Token. Füge im Feld „Headers“ einen Header mit dem Namen Authorization hinzu. Sein Wert ist Bearer, ein Leerzeichen und dein Token. Sende GET /me: Status 200, mit deiner ID und "username": "ada". Ändere nun einen Buchstaben in der Mitte des Tokens, zwischen den beiden Punkten, und sende erneut GET /me: Status 401, weil die Signatur nicht mehr übereinstimmt.
Probier es selbst
import express from 'express';
import { SignJWT, decodeJwt } from 'jose';
import { users } from './db.js';
import { hashPassword, verifyPassword } from './passwords.js';
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
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 });
});
app.post('/token', 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' });
}
const token = await new SignJWT({ username: user.username })
.setProtectedHeader({ alg: 'HS256' })
.setSubject(user._id.toHexString())
.setIssuedAt()
.setExpirationTime('1h')
.sign(secret);
res.json({ token });
});
// Zeigt den Inhalt eines Tokens an: 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. Keiner Angabe darf vertraut werden, bevor ihre 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');
});
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
Übe selbstständig: Express-Playground