Menu
CoddyTech

JWT란 무엇인가

Coddy Node.js 여정의 인증과 보안 섹션에 포함된 레슨. 17개 중 9번째.

세션은 매 요청마다 데이터베이스를 조회해야 합니다. JWT(JSON 웹 토큰)는 필요한 정보를 자체적으로 담습니다. base64url 형식의 세 부분이 점으로 연결되어 있습니다. 헤더에는 알고리즘이 적혀 있고, 페이로드에는 클레임(토큰의 주체를 나타내는 sub, 만료 시점을 나타내는 exp)이 들어 있으며, 서명은 서버가 토큰을 만들었음을 증명합니다.

Base64url은 인코딩이지 암호화가 아닙니다. 토큰을 가진 사람은 누구나 페이로드를 읽을 수 있습니다.

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

jose의 decodeJwt(token)도 같은 작업을 하며 서명은 확인하지 않습니다. 따라서 페이로드에 비밀 정보를 절대 넣지 말고, decodeJwt가 반환한 클레임을 근거로 어떤 결정도 내려서는 안 됩니다. 누구나 어떤 클레임이든 작성할 수 있습니다.

challenge icon

챌린지

쉬움
  • GET /inspect?token=...은 토큰의 클레임을 JSON으로 반환하거나, 400 { "error": "Not a JWT" }를 반환합니다.
  • GET /admin은 모든 요청에 403 { "error": "Admins only" }를 반환합니다.

먼저 Mallory의 토큰을 GET /admin?token=에 보내고 무엇이 열리는지 확인하세요: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYWxsb3J5IiwiYWRtaW4iOnRydWV9.c2lnbmF0dXJl. 토큰을 수정한 다음, 같은 토큰을 /inspect에 보내세요.

직접 해보기

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) => {
    res.status(501).json({ error: 'Not written yet' });
});

// 관리자 페이지.
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 icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

인증과 보안의 모든 레슨