토큰 서명하기
Coddy Node.js 여정의 인증과 보안 섹션에 포함된 레슨. 17개 중 10번째.
토큰은 다른 누구도 만들 수 없을 때만 가치가 있습니다. jose는 HS256으로 토큰에 서명합니다. 이는 서버만 아는 비밀 키를 사용하는 헤더와 페이로드의 HMAC입니다.
비밀 키는 환경 변수인 process.env.JWT_SECRET에서 가져오며, 코드에서 가져오지 않습니다. 코드는 공유되고, 복사되고, 커밋되기 때문입니다. 이 강의에서는 JWT_SECRET을 설정합니다. jose는 이를 바이트로 받습니다.
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에는 텍스트를 전달하고, setExpirationTime에는 '30m' 또는 '2d'와 같은 기간을 전달합니다.
챌린지
쉬움POST /token에{ username, password }를 보내면{ token }이 반환됩니다. HS256으로 서명되며,JWT_SECRET을 사용하고,username클레임과 텍스트 형식의 사용자 ID를 subject로 포함하며, 1시간 후 만료됩니다.- 로그인 정보가 틀리면 401과
{ "error": "Wrong username or password" }가 반환됩니다.
가입한 뒤 토큰을 받아 GET /inspect?token=에 붙여 넣으세요.
직접 해보기
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 });
});
// 토큰의 내용을 보여줍니다: 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' });
}
});
// 관리자 페이지입니다. 서명을 검증하기 전에는 어떤 클레임도 신뢰할 수 없습니다.
app.get('/admin', (req, res) => {
res.status(403).json({ error: 'Admins only' });
});
app.listen(3000, () => {
console.log('Vault is running on port 3000');
});