Menu
CoddyTech

Verificare i token

Fa parte della sezione Autenticazione e sicurezza del percorso Node.js di Coddy. Lezione 16 di 24.

Un client API invia il proprio token in un'intestazione a ogni richiesta: Authorization: Bearer <token>. Leggerlo con decodeJwt accetterebbe qualsiasi token inserito da chiunque. jwtVerify(token, key) ricalcola la firma con il tuo segreto e genera un errore quando non corrisponde, quando il token è scaduto o quando non è affatto un JWT. Solo dopo che la funzione ha restituito il risultato puoi fidarti di payload.

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();
}
challenge icon

Sfida

Facile

Ora accetta i token che firmi. Un'app invia il proprio token in un'intestazione a ogni richiesta e il server verifica la firma prima di fidarsi di qualsiasi dato contenuto al suo interno.

  1. In server.js, importa jwtVerify da jose.
  2. Scrivi un middleware requireUser(req, res, next). Legge l'intestazione Authorization. Questa intestazione contiene la parola Bearer, uno spazio e poi il token. Estrai il token.
  3. Verifica il token con jwtVerify, usando la stessa chiave con cui lo firmi. jwtVerify genera un errore quando la firma non corrisponde, quando il token è scaduto o quando il testo non è affatto un token. In tutti questi casi, e quando manca l'intestazione, rispondi con lo stato 401 e {"error": "Log in first"}.
  4. Quando il token è valido, imposta req.user su un oggetto con id, ricavato dal claim sub del token, e username, ricavato dal suo claim username. Poi chiama next().
  5. Aggiungi GET /me con requireUser. Restituisce req.user: {"id": "66f1c2a9e4b0a1b2c3d4e5f6", "username": "ada"}.

Provalo: premi Esegui. Invia GET /me: stato 401 e {"error": "Log in first"}. Invia POST /signup, poi POST /token, entrambi con {"username": "ada", "password": "correct horse"}, e copia il token. Nel campo Headers, aggiungi un'intestazione chiamata Authorization il cui valore è Bearer, uno spazio e il tuo token. Invia GET /me: stato 200, con il tuo id e "username": "ada". Ora cambia una lettera nella parte centrale del token, tra i due punti, e invia di nuovo GET /me: stato 401, perché la firma non corrisponde più.

Provalo tu

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 });
});

// Mostra il contenuto di un token: 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' });
    }
});

// La pagina di amministrazione. Nessuna dichiarazione può essere considerata attendibile prima che ne venga verificata la firma.
app.get('/admin', (req, res) => {
    res.status(403).json({ error: 'Admins only' });
});

app.listen(3000, () => {
    console.log('Vault is running on port 3000');
});
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Autenticazione e sicurezza

Esercitati da solo: Playground Express