Routen hinter einer Anmeldung
Teil des Abschnitts Authentifizierung und Sicherheit der Node.js-Journey von Coddy. Lektion 11 von 24.
Jede geschützte Route benötigt den Sitzungsabruf von GET /me. Wird er in jede Route kopiert, wird er irgendwann vergessen, und diese Route ist für alle zugänglich. Schreibe ihn stattdessen einmal als Middleware.
Middleware wird vor der Route ausgeführt. Wenn sie etwas an req anhängt, etwa req.user, liest die Route es von dort aus. Die _id eines Benutzers ist ein ObjectId; user._id.toHexString() gibt sie als Text zurück. Die Middleware antwortet entweder und beendet die Verarbeitung oder hängt das Gefundene an req an und ruft next() auf. Übergib sie an jede Route, die sie benötigt, vor dem Handler:
function requireAdult(req, res, next) {
if (Number(req.query.age) < 18) {
return res.status(403).json({ error: 'Adults only' });
}
req.adult = true;
next();
}
app.get('/wines', requireAdult, (req, res) => res.json(wines));Das return ist wichtig: Ohne es würde auch die Route ausgeführt.
Aufgabe
EinfachNotizen sind weiterhin für alle zugänglich. Mach sie privat: Nur angemeldete Benutzer können Notizen lesen oder hinzufügen, und jeder Benutzer sieht nur seine eigenen.
- Schreibe in
server.jseine Middleware-FunktionrequireUser(req, res, next). Verschiebe die Sitzungssuche ausGET /medorthin: Lies das Cookiesid, suche die Sitzung und anschließend den zugehörigen Benutzer.- Wenn es keinen solchen Benutzer gibt, antworte mit Status 401 und
{"error": "Log in first"}und brich dort ab. - Andernfalls weise den Benutzer der Anfrage als
req.userzu, einem Objekt mit zwei Feldern, und rufe dannnext()auf. Die beiden Felder sindid, die_iddes Benutzers als Text, undusername. Die MethodetoHexString()einer ObjectId liefert den Text, zum Beispiel"66f1c2a9e4b0a1b2c3d4e5f6".
- Wenn es keinen solchen Benutzer gibt, antworte mit Status 401 und
- Übergib
requireUseranGET /me,GET /notesundPOST /notes, jeweils vor der eigenen Funktion der Route.GET /meantwortet dann mit{"username": "ada"}, ausgelesen ausreq.user. - Speichere in
POST /notesjede Notiz mit einem Feldowner, dasreq.user.identhält. Die Antwort ist die neue Notiz:{"owner": "66f1c2a9e4b0a1b2c3d4e5f6", "text": "Door code 1234", "_id": "66f1c2b0e4b0a1b2c3d4e5f7"} - Gib in
GET /notesnur die Notizen zurück, derenownerder angemeldete Benutzer ist.
Probiere es aus: Drücke „Ausführen“. Sende GET /notes: Status 401 und {"error": "Log in first"}. Sende POST /signup und anschließend POST /login, beide mit {"username": "ada", "password": "correct horse"}. Sende POST /notes mit {"text": "Door code 1234"}: Status 201 und die Notiz mit ihrem owner. Sende GET /notes: eine Liste mit dieser einen Notiz. Registriere dich nun und melde dich mit {"username": "bob", "password": "correct horse"} an. GET /notes gibt [] zurück, weil bob noch nichts geschrieben hat.
Probier es selbst
import express from 'express';
import { randomBytes } from 'node:crypto';
import { parseCookies } from './cookies.js';
import { notes, sessions, 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' });
}
const sid = randomBytes(24).toString('base64url');
await sessions.insertOne({ _id: sid, userId: user._id, createdAt: new Date() });
res.cookie('sid', sid, { httpOnly: true });
res.json({ username: user.username });
});
app.get('/me', async (req, res) => {
const { sid } = parseCookies(req.get('cookie'));
const session = sid && (await sessions.findOne({ _id: sid }));
if (!session) {
return res.status(401).json({ error: 'Log in first' });
}
const user = await users.findOne({ _id: session.userId });
res.json({ username: user.username });
});
// Zählt, wie oft dieser Browser GET /visit aufgerufen hat.
app.get('/visit', (req, res) => {
const cookies = parseCookies(req.get('cookie'));
const visits = Number(cookies.visits ?? 0) + 1;
res.cookie('visits', String(visits));
res.json({ visits });
});
// Notizen. Vorerst kann jeder sie lesen und schreiben.
app.get('/notes', async (req, res) => {
res.json(await notes.find().toArray());
});
app.post('/notes', async (req, res) => {
const note = { text: String(req.body.text ?? '') };
await notes.insertOne(note);
res.status(201).json(note);
});
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
2Sitzungen und Cookies
Mit Cookies erinnernEine Sitzung nachschlagenAnmeldesitzungenRouten hinter einer AnmeldungÜbe selbstständig: Express-Playground