JWT란 무엇인가
JWT(JSON Web Token)는 서버와 클라이언트 간에 인증 정보를 전달하기 위한 토큰 기반 인증 방식입니다.
기존 세션 기반 인증과 비교했을 때 JWT는 다음과 같은 특징을 가집니다.
- 서버가 세션 상태를 직접 저장하지 않아도 됩니다.
- 토큰 자체에 인증 정보가 포함됩니다.
- 여러 서비스 간 인증 전달에 유리합니다.
- 모바일 앱, SPA, API 서버 구조에서 자주 사용됩니다.
하지만 편리하다고 해서 무조건 안전한 것은 아닙니다. 구조를 정확히 이해하고, 저장 방식과 검증 흐름까지 제대로 설계해야 실제 운영에서 문제를 줄일 수 있습니다.
JWT의 기본 구조
JWT는 아래와 같은 3개 파트로 구성됩니다.
HEADER.PAYLOAD.SIGNATURE
각 영역은 . 으로 구분되며, 각각 Base64Url 형식으로 인코딩됩니다.
1. Header
Header에는 토큰의 타입과 어떤 알고리즘으로 서명했는지가 들어갑니다.
{
"alg": "HS256",
"typ": "JWT"
}
주요 필드:
alg: 서명 알고리즘typ: 토큰 타입
2. Payload
Payload에는 실제로 전달하고 싶은 데이터가 들어갑니다.
{
"userId": "zino",
"role": "admin",
"iat": 1710000000,
"exp": 1710003600
}
주요 필드:
userId: 사용자 식별자role: 사용자 권한iat: 발급 시각exp: 만료 시각
주의: Payload는 암호화가 아니라 인코딩입니다. 민감한 비밀번호나 비밀키를 넣으면 안 됩니다.
3. Signature
Signature는 토큰 위변조를 막는 핵심 부분입니다.
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
서버는 이 Signature를 검증해서 토큰이 중간에 변경되지 않았는지 확인합니다.
JWT 인증 흐름
일반적인 흐름은 다음과 같습니다.
1. 사용자가 로그인 요청
2. 서버가 사용자 인증 성공
3. 서버가 JWT 발급
4. 클라이언트가 토큰 저장
5. 이후 요청마다 토큰 전달
6. 서버가 토큰 검증 후 응답
단계별로 풀어보면
- 사용자가 아이디와 비밀번호를 서버에 전송합니다.
- 서버는 DB에서 사용자를 확인합니다.
- 인증이 성공하면 JWT를 생성합니다.
- JWT를 쿠키 또는 클라이언트 저장소에 저장합니다.
- 이후 보호된 API 요청 시 JWT를 같이 보냅니다.
- 서버는 JWT를 검증하고, 유효하면 요청을 처리합니다.
이미지 예시 1
아래는 인증 흐름 다이어그램이나 구조 이미지를 넣기 좋은 위치입니다. 나중에 직접 주소만 바꿔서 넣으면 됩니다.
Node.js 기준 JWT 생성 예시
아래는 jsonwebtoken 패키지를 사용하는 가장 기본적인 예시입니다.
import jwt from "jsonwebtoken";
const payload = {
userId: "zino",
role: "admin",
};
const token = jwt.sign(payload, process.env.JWT_SECRET, {
expiresIn: "1h",
issuer: "zino.kr",
});
console.log(token);
이 코드에서 중요한 점은 다음과 같습니다.
JWT_SECRET은.env.local같은 환경변수에서 읽어야 합니다.expiresIn을 반드시 설정하는 것이 좋습니다.- 발급자(
issuer)나 audience 같은 정보를 추가하면 검증을 더 엄격하게 할 수 있습니다.
JWT 검증 예시
import jwt from "jsonwebtoken";
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
issuer: "zino.kr",
});
console.log("verified:", decoded);
} catch (error) {
console.error("invalid token", error.message);
}
검증 시에는 단순히 토큰 문자열만 보는 것이 아니라 다음도 함께 점검해야 합니다.
- 서명 일치 여부
- 만료 여부
- issuer 일치 여부
- audience 일치 여부
Express 미들웨어 예시
실제 서비스에서는 보통 미들웨어 형태로 JWT 검증을 분리합니다.
import jwt from "jsonwebtoken";
export function requireAuth(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({ ok: false, error: "missing-token" });
}
const token = authHeader.slice(7);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
issuer: "zino.kr",
});
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ ok: false, error: "invalid-token" });
}
}
이렇게 해두면 보호가 필요한 라우트에서 재사용하기 편합니다.
브라우저 저장 방식 비교
JWT를 어디에 저장할지는 보안에 큰 영향을 줍니다.
1. localStorage
장점:
- 구현이 단순합니다.
- 프론트엔드에서 다루기 쉽습니다.
단점:
- XSS에 취약합니다.
- 스크립트로 쉽게 접근 가능합니다.
2. HttpOnly Cookie
장점:
- 자바스크립트에서 직접 접근할 수 없습니다.
- 상대적으로 안전합니다.
단점:
- CSRF 대응을 함께 고려해야 합니다.
- 쿠키 옵션 설정이 중요합니다.
실무에서는 보통 HttpOnly + Secure + SameSite 쿠키 조합이 더 권장됩니다.
res.cookie("auth_token", token, {
httpOnly: true,
secure: true,
sameSite: "lax",
path: "/",
maxAge: 1000 * 60 * 60,
});
JWT를 사용할 때 자주 하는 실수
민감한 정보를 Payload에 넣는 경우
Payload는 누구나 디코딩해볼 수 있습니다. 따라서 아래와 같은 값은 넣으면 안 됩니다.
- 비밀번호
- API Secret
- 주민번호 등 민감정보
만료시간을 너무 길게 두는 경우
토큰 탈취 시 피해 범위가 커집니다.
검증 시 알고리즘을 너무 느슨하게 두는 경우
서명 검증을 엄격하게 하지 않으면 우회 가능성이 생길 수 있습니다.
이미지 예시 2
Access Token과 Refresh Token
JWT를 실무에서 쓸 때는 보통 Access Token 하나만 쓰지 않고, Refresh Token 구조를 같이 둡니다.
기본 아이디어
- Access Token: 수명이 짧음 (예: 15분)
- Refresh Token: 수명이 김 (예: 7일 ~ 30일)
로그인 성공
→ Access Token 발급
→ Refresh Token 발급
→ Access Token 만료 시 Refresh Token으로 재발급
이 구조를 쓰면 보안성과 사용성을 어느 정도 균형 있게 맞출 수 있습니다.
JWT vs Session 비교
| 항목 | JWT | Session |
|---|---|---|
| 서버 상태 저장 | 필요 없음 | 필요함 |
| 확장성 | 높음 | 상대적으로 낮음 |
| 로그아웃 처리 | 상대적으로 어려움 | 쉬움 |
| 구현 난이도 | 중간 | 중간 |
| 탈취 시 대응 | 별도 전략 필요 | 세션 만료/삭제 가능 |
JWT가 무조건 더 좋은 것은 아닙니다. 서비스 구조와 요구사항에 따라 선택해야 합니다.
로그아웃과 토큰 무효화 문제
JWT의 대표적인 단점 중 하나는 한 번 발급한 토큰을 서버가 즉시 폐기하기 어렵다는 점입니다.
대표적인 대응 방식:
- 짧은 만료시간 사용
- Refresh Token 별도 관리
- 블랙리스트 저장소 운영
- 토큰 버전(version) 필드 활용
예를 들어 DB에 토큰 버전을 두고 사용자가 비밀번호를 바꾸면 버전을 올려 기존 토큰을 무효 처리할 수 있습니다.
const payload = {
userId: user.id,
tokenVersion: user.tokenVersion,
};
검증 시 DB의 현재 tokenVersion과 비교하면 됩니다.
실무 관점 체크리스트
JWT를 운영에 넣기 전에 아래 정도는 꼭 확인하는 것이 좋습니다.
- 만료시간(exp)을 넣었는가
- secret을 환경변수로 분리했는가
- HttpOnly Cookie 저장을 우선 검토했는가
- Refresh Token 전략이 있는가
- 로그아웃/강제 무효화 전략이 있는가
- Payload에 민감정보를 넣지 않았는가
마무리
JWT는 현대 웹 서비스에서 매우 널리 사용되지만, 구조를 모르고 복붙만 하면 오히려 보안 문제가 생길 수 있습니다.
핵심만 다시 정리하면:
- Header / Payload / Signature 구조를 이해할 것
- Payload는 암호화가 아니라 인코딩이라는 점을 기억할 것
- 저장 위치와 검증 로직이 실제 보안에 더 중요하다는 점을 놓치지 말 것
JWT는 편리한 도구이지만, 어떻게 저장하고 어떻게 검증하느냐가 진짜 핵심입니다.
MDX Rendering Test Section
Inline code: jwt.sign(payload)
Link: JWT 공식 사이트
Bold text / Italic text / Strikethrough
Nested List
- Level 1
- Level 2
- Level 3
- Level 2
Ordered List
- First
- Second
- Third
Checklist
- Token issued
- Token verified
- Refresh rotation tested
Code with filename
export const token = jwt.sign(payload, secret);
Long code line
const veryLongAuthorizationHeaderExample = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.long-long-long-long-long-token-value-for-overflow-test";
Blockquote Extended
⚠️ Warning This is a multiline blockquote used for testing rendering.
Divider Variants
Table (Extended)
| Feature | Description | Notes |
|---|---|---|
| JWT | Token | Basic |
| OAuth | Framework | Complex |
| Long text example | This is a very long text to test wrapping behavior in table cells | Wrap test |
Image Figure Test

Raw HTML Link Test
JWT Raw HTML Link
JWT는 상태 없는 인증에 강력하지만, 만료시간·서명검증·저장방식까지 함께 설계해야 안전하게 사용할 수 있습니다.
