Menu
CoddyTech

Tokenları Doğrulama

Coddy'nin Node.js Journey'sinin Kimlik Doğrulama ve Güvenlik bölümünün bir parçası. Ders 16 / 24.

Bir API istemcisi her istekte belirtecini bir başlıkta gönderir: Authorization: Bearer <token>. decodeJwt ile okumak, herhangi birinin yazdığı her belirtecin kabul edilmesine yol açar. jwtVerify(token, key) gizli anahtarınızla imzayı yeniden hesaplar ve imza eşleşmediğinde, belirtecin süresi dolduğunda veya belirteç bir JWT olmadığında hata verir. payload öğesine ancak bu işlem başarıyla tamamlandıktan sonra güvenebilirsiniz.

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

Görev

Kolay

Şimdi imzaladığın belirteçleri kabul et. Bir uygulama, belirtecini her istekte bir üstbilgide gönderir ve sunucu, içindeki herhangi bir şeye güvenmeden önce imzayı denetler.

  1. server.js içinde, jose modülünden jwtVerify içe aktar.
  2. requireUser(req, res, next) adlı bir ara yazılım yaz. Authorization üstbilgisini okur. Bu üstbilgide Bearer sözcüğü, ardından bir boşluk ve sonra belirteç bulunur. Belirteci buradan al.
  3. İmza atarken kullandığın anahtarın aynısını kullanarak belirteci jwtVerify ile denetle. İmza eşleşmediğinde, belirtecin süresi dolduğunda veya metin hiç belirteç olmadığında jwtVerify hata verir. Bu durumların tümünde ve üstbilgi olmadığında {"error": "Log in first"} yanıtını 401 durum koduyla gönder.
  4. Belirteç geçerliyse req.user değerini, belirtecin sub talebinden alınan id ve username talebinden alınan username alanlarını içeren bir nesneye ayarla. Ardından next() çağrısını yap.
  5. requireUser ile GET /me uç noktasını ekle. Bu uç nokta req.user değerini döndürür: {"id": "66f1c2a9e4b0a1b2c3d4e5f6", "username": "ada"}.

Deneyin: Çalıştır düğmesine bas. GET /me isteğini gönder: durum kodu 401 ve {"error": "Log in first"}. POST /signup isteğini, ardından da POST /token isteğini gönder; her ikisinde de {"username": "ada", "password": "correct horse"} kullan ve belirteci kopyala. Başlıklar alanına, adı Authorization olan bir üstbilgi ekle; değeri Bearer, bir boşluk ve belirtecinden oluşsun. GET /me isteğini gönder: durum kodu 200; kimliğin ve "username": "ada" yanıtla birlikte gelir. Şimdi belirtecin orta bölümündeki, iki noktanın arasındaki bir harfi değiştir ve GET /me isteğini yeniden gönder: imza artık eşleşmediği için durum kodu 401.

Kendin dene

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

// Bir token'ın içeriğini gösterir: 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' });
    }
});

// Yönetici sayfası. İmzası doğrulanmadan hiçbir beyana güvenilemez.
app.get('/admin', (req, res) => {
    res.status(403).json({ error: 'Admins only' });
});

app.listen(3000, () => {
    console.log('Vault is running on port 3000');
});
quiz iconKendini test et

Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.

Kimlik Doğrulama ve Güvenlik bölümündeki tüm dersler

Kendi başına pratik yap: Express playground