Menu
CoddyTech

Tokenları İmzalama

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

Bir belirteç, yalnızca başka hiç kimse bir tane oluşturamıyorsa değerlidir. HS256 ile jose, başlığı ve yükü yalnızca sunucunun bildiği bir gizli anahtarla birleştirir ve sonucu özetler. Bu özet imzadır. Gizli anahtar olmadan hiç kimse eşleşen bir imza oluşturamaz.

Gizli anahtar bir ortam değişkeninden, process.env.JWT_SECRET, gelir; asla kodun içinden gelmez: kod paylaşılır, kopyalanır ve depoya kaydedilir. Bu ders JWT_SECRET değişkenini senin için ayarlar. jose bu değeri baytlar olarak ister.

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 metin alır, setExpirationTime ise '30m' veya '2d' gibi bir süre alır.

challenge icon

Görev

Kolay

Telefon uygulaması gibi API'nizi çağıran uygulamalar, çerezleri tarayıcı gibi saklamaz. Bunun yerine onlara bir belirteç verin: her istekle birlikte geri gönderdikleri imzalı bir JWT. POST /token, bu uygulamaların giriş noktasıdır.

  1. server.js dosyasında, SignJWT'yi decodeJwt'nin yanına, jose'den içe aktarın. Rotaların üzerine, derste gösterildiği gibi process.env.JWT_SECRET değerini TextEncoder kullanarak baytlara dönüştürün; çünkü jose baytlarla imzalar. Bu derste JWT_SECRET sizin için ayarlanır.
  2. POST /token rotasını ekleyin. Giriş rotasıyla aynı gövdeyi alır: {"username": "ada", "password": "correct horse"}; kullanıcı adını ve parolayı POST /login ile aynı şekilde kontrol eder. Bilgiler yanlış olduğunda, 401 durum koduyla ve {"error": "Wrong username or password"} yanıtını verin.
  3. Bilgiler doğru olduğunda bir belirteç imzalayın ve bunu {"token": "..."} olarak yanıtlayın. Belirteç şunları yapmalıdır:
    • JWT_SECRET baytlarıyla imzalanmış HS256 algoritmasını kullanmalı;
    • yükünde username talebini içermeli;
    • konu olarak kullanıcının metin biçimindeki _id değerini kullanmalı. toHexString() kullanın; çünkü jose, metin olmayan bir konuyu kabul etmez;
    • oluşturulduktan bir saat sonra sona ermeli.

    Bu dersteki bir belirteç şöyle görünür: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkYSIsInN1YiI6IjY2ZjFjMmE5ZTRiMGExYjJjM2Q0ZTVmNiIsImlhdCI6MTc5MzM2MTYwMCwiZXhwIjoxNzkzMzY1MjAwfQ.EdCrjAKSjZowcjE3-IjKttBLVK1VKKbL9DPnJDLdgug

Deneyin: Çalıştır'a basın. {"username": "ada", "password": "correct horse"} ile POST /signup isteği gönderin. Aynı gövdeyle POST /token isteği gönderin: durum kodu 200 ve yanıt {"token": "..."} olur. Belirteci kopyalayın ve ardından belirteci ekleyerek GET /inspect?token= isteği gönderin. Şuna benzer bir sonuç görürsünüz: {"username": "ada", "sub": "66f1c2a9e4b0a1b2c3d4e5f6", "iat": 1793361600, "exp": 1793365200}: exp, iat değerinden 3600 saniye sonrasıdır. {"username": "ada", "password": "wrong horse"} ile POST /token isteği gönderin: durum kodu 401.

Kendin dene

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

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

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

Kendi başına pratik yap: Express playground