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.
Görev
KolayBu 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.
server.jsiçindeGET /inspectyazın. Bu rota, sorgu dizgesinden bir belirteç okur; örneğinGET /inspect?token=eyJ...ve içindeki talepleri JSON olarak döndürür. Talepleri, zaten içe aktarılmış olandecodeJwtile okuyun. Yükü{"sub": "lin", "plan": "free"}olan bir belirteç için rota tam olarak{"sub": "lin", "plan": "free"}döndürür.- Metin bir JWT olmadığında
decodeJwthata verir. Bu hatayı yakalayın ve 400 durum koduyla{"error": "Not a JWT"}yanıtını verin. Örneğin,GET /inspect?token=hellobu 400 yanıtını alır. GET /adminrotasına bakın. Bu rota, belirtecidecodeJwtile okur ve belirteç"admin": truedediğinde bir gizli bilgiyi gösterir. AncakdecodeJwtimzayı hiçbir zaman doğrulamaz, bu yüzden herkes"admin": truediyen bir belirteç yazabilir. Bu sunucu henüz imzaları doğrulayamaz. Bu nedenleGET /adminrotası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');
});
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