Menu
CoddyTech

JWT Nedir

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

Bir oturumda her istek için veritabanında arama yapılması gerekir. JWT (JSON Web Token), bilgileri kendi içinde taşır: noktalarla birleştirilmiş üç bölümden oluşur. Her bölüm, baytları harfler, rakamlar, - ve _ ile yazmanın bir yolu olan base64url biçiminde yazılır. Üst bilgi algoritmayı belirtir, yük iddiaları (sub tokenın kime ait olduğunu, exp ise ne zaman sona ereceğini belirtir) içerir ve imza, tokenı sunucunun oluşturduğunu kanıtlar.

Base64url bir kodlamadır, şifreleme değildir. Bir tokenı elinde bulunduran herkes yükünü okuyabilir:

const payload = token.split('.')[1];
JSON.parse(Buffer.from(payload, 'base64url').toString());
// { sub: '42', name: 'Lin', exp: 1767225600 }

JWT paketi jose içindeki decodeJwt(token) de aynı şeyi yapar. İmzayı doğrulamadan yükü okur ve metin bir JWT olmadığında hata fırlatır. Bu yüzden yüke asla gizli bilgi koyma ve decodeJwt işlevinin verdiği bir iddiaya dayanarak asla karar verme: herkes istediği iddiayı yazabilir.

challenge icon

Görev

Kolay

Bu bölüm, API'nizi çağıran uygulamalar için belirteçlerle oturum açmanın ikinci bir yolunu ekler. Kayıt ve oturum açma özellikleri olan, ancak oturumları veya notları olmayan sunucunun daha küçük bir kopyasıyla başlar. JWT, bilgilerini kendi içinde taşır ve herkes bunları okuyabilir. Bir belirtecin ne söylediğini gösteren bir rota oluşturun ve belirtecin iddia ettiği her şeye güvenen bir yönetici sayfasını kapatın.

  1. server.js içinde GET /inspect yazın. Bu rota, sorgu dizgesinden bir belirteç okur; örneğin GET /inspect?token=eyJ... ve içindeki talepleri JSON olarak döndürür. Talepleri, zaten içe aktarılmış olan decodeJwt ile okuyun. Yükü {"sub": "lin", "plan": "free"} olan bir belirteç için rota tam olarak {"sub": "lin", "plan": "free"} döndürür.
  2. Metin bir JWT olmadığında decodeJwt hata verir. Bu hatayı yakalayın ve 400 durum koduyla {"error": "Not a JWT"} yanıtını verin. Örneğin, GET /inspect?token=hello bu 400 yanıtını alır.
  3. GET /admin rotasına bakın. Bu rota, belirteci decodeJwt ile okur ve belirteç "admin": true dediğinde bir gizli bilgiyi gösterir. Ancak decodeJwt imzayı hiçbir zaman doğrulamaz, bu yüzden herkes "admin": true diyen bir belirteç yazabilir. Bu sunucu henüz imzaları doğrulayamaz. Bu nedenle GET /admin rotasının, aldığı belirteç ne olursa olsun her isteğe 403 durum kodu ve {"error": "Admins only"} yanıtını vermesini sağlayın. 403 Forbidden, "bunu göremezsiniz" anlamına gelir. 4. bölümde yönetici sayfası yeniden açılır; bu kez yalnızca gerçekten yönetici olanlar için.

Deneyin: “Oyna” düğmesine basın. İstek çubuğu GET /admin?token= ile başlar ve ardından şu belirteç gelir: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYWxsb3J5IiwiYWRtaW4iOnRydWV9.c2lnbmF0dXJl mallory adlı bir saldırgan bunu gizli anahtar olmadan elle yazdı. Hiçbir şeyi değiştirmeden önce gönderin: başlangıç kodu {"secret": "The vault key is 42"} döndürür. Değişikliğinizden sonra “Oyna”ya yeniden basın ve aynı isteği gönderin: 403 durum kodu ve {"error": "Admins only"}. GET /inspect?token= isteğini, ardından aynı belirteci gönderin: hiçbir anahtar olmadan okunmuş {"sub": "mallory", "admin": true}. GET /inspect?token=hello isteğini gönderin: 400 durum kodu ve {"error": "Not a JWT"}.

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 ne söylediğini gösterir: GET /inspect?token=...
app.get('/inspect', (req, res) => {
    res.status(501).json({ error: 'Not written yet' });
});

// Yönetici sayfası.
app.get('/admin', (req, res) => {
    const claims = decodeJwt(String(req.query.token ?? ''));
    if (claims.admin === true) {
        return res.json({ secret: 'The vault key is 42' });
    }
    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