Masa Kedaluwarsa Token
Termasuk bagian Autentikasi dan Keamanan dalam perjalanan Node.js di Coddy. Pelajaran 12 dari 17.
Server tidak dapat menarik kembali token: tidak ada yang disimpan, jadi token yang dicuri akan tetap berfungsi sampai kedaluwarsa. Buat token berumur pendek, hitungan menit, bukan hari, dan biarkan client meminta token baru.
jwtVerify sudah menolak token yang exp-nya telah berlalu. Fungsi ini melempar errors.JWTExpired, jadi kamu dapat membedakan token yang kedaluwarsa dari token palsu dan memberi tahu client: client yang membaca Token expired tahu bahwa mereka harus login lagi.
import { errors, jwtVerify } from 'jose';
try {
await jwtVerify(inviteToken, inviteKey);
} catch (err) {
if (err instanceof errors.JWTExpired) {
return res.status(410).json({ error: 'This invite has run out' });
}
return res.status(400).json({ error: 'Not a real invite' });
}Tantangan
Mudah- Token dari
POST /tokenkedaluwarsa setelah 15 menit. - Token yang kedaluwarsa menghasilkan respons 401
{ "error": "Token expired" }. - Semua token tidak valid atau token yang tidak ada lainnya tetap menghasilkan respons 401
{ "error": "Log in first" }.
Dapatkan token dan baca exp-nya dengan GET /inspect.
Coba sendiri
import express from 'express';
import { SignJWT, decodeJwt, jwtVerify } 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 });
});
// Memeriksa token dalam header Authorization, atau mengembalikan 401.
async function requireUser(req, res, next) {
const token = (req.get('authorization') ?? '').replace(/^Bearer /, '');
let payload;
try {
({ payload } = await jwtVerify(token, secret));
} catch {
return res.status(401).json({ error: 'Log in first' });
}
req.user = { id: payload.sub, username: payload.username };
next();
}
app.get('/me', requireUser, (req, res) => {
res.json(req.user);
});
// 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 percayai 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
4Pemilik dan Keamanan
Hanya CatatanmuPeran AdminLatihan sendiri: Playground Express