Menandatangani Token
Termasuk bagian Autentikasi dan Keamanan dalam perjalanan Node.js di Coddy. Pelajaran 15 dari 24.
Sebuah token hanya bernilai jika tidak ada orang lain yang bisa membuatnya. Dengan HS256, jose mencampur header dan payload dengan rahasia yang hanya diketahui server, lalu melakukan hash pada hasilnya. Hash itu adalah tanda tangan. Tanpa rahasia tersebut, tidak ada yang bisa membuat token dengan tanda tangan yang cocok.
Rahasia berasal dari variabel lingkungan, process.env.JWT_SECRET, bukan dari kode: kode dibagikan, disalin, dan di-commit. Pelajaran ini menetapkan JWT_SECRET untukmu. jose menginginkannya dalam bentuk byte.
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 menerima teks, dan setExpirationTime menerima rentang waktu seperti '30m' atau '2d'.
Tantangan
MudahAplikasi yang memanggil API-mu, seperti aplikasi ponsel, tidak menyimpan cookie seperti yang dilakukan browser. Berikan token sebagai gantinya: JWT bertanda tangan yang dikirim kembali bersama setiap permintaan. POST /token adalah cara login untuk aplikasi tersebut.
- Di
server.js, imporSignJWTdarijosebersamadecodeJwt. Sebelum rute, ubahprocess.env.JWT_SECRETmenjadi byte denganTextEncoder, seperti yang ditunjukkan pelajaran ini, karenajosemenandatangani dengan byte. Pelajaran ini menetapkanJWT_SECRETuntukmu. - Tambahkan
POST /token. Endpoint ini menerima body yang sama seperti login,{"username": "ada", "password": "correct horse"}, lalu memeriksa nama pengguna dan kata sandi dengan cara yang sama sepertiPOST /login. Jika keduanya salah, kirim respons dengan status 401 dan{"error": "Wrong username or password"}. - Jika keduanya benar, tanda tangani token dan kirim respons yang berisi token tersebut sebagai
{"token": "..."}. Token harus:- menggunakan algoritma HS256, ditandatangani dengan byte
JWT_SECRET; - menyimpan klaim
usernamedi payload-nya; - menggunakan
_idpengguna dalam bentuk teks sebagai subject-nya. GunakantoHexString(), karenajosemenolak subject yang bukan teks; - kedaluwarsa satu jam setelah dibuat.
Token dari pelajaran ini terlihat seperti ini:
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkYSIsInN1YiI6IjY2ZjFjMmE5ZTRiMGExYjJjM2Q0ZTVmNiIsImlhdCI6MTc5MzM2MTYwMCwiZXhwIjoxNzkzMzY1MjAwfQ.EdCrjAKSjZowcjE3-IjKttBLVK1VKKbL9DPnJDLdgug - menggunakan algoritma HS256, ditandatangani dengan byte
Coba: Tekan Jalankan. Kirim POST /signup dengan {"username": "ada", "password": "correct horse"}. Kirim POST /token dengan body yang sama: status 200 dan {"token": "..."}. Salin token tersebut lalu kirim GET /inspect?token= diikuti token itu. Kamu akan melihat sesuatu seperti {"username": "ada", "sub": "66f1c2a9e4b0a1b2c3d4e5f6", "iat": 1793361600, "exp": 1793365200}: exp adalah 3600 detik setelah iat. Kirim POST /token dengan {"username": "ada", "password": "wrong horse"}: status 401.
Coba sendiri
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 });
});
// Menampilkan isi 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' });
}
});
// Halaman admin. Jangan percaya klaim apa pun sebelum tanda tangannya diperiksa.
app.get('/admin', (req, res) => {
res.status(403).json({ error: 'Admins only' });
});
app.listen(3000, () => {
console.log('Vault is running on port 3000');
});
Semua pelajaran di Autentikasi dan Keamanan
Latihan sendiri: Playground Express