stack-analysis
CHAPTER 102 / 120
읽기 약 2분
FUNCTION
인증 아키텍처: Session vs JWT vs OAuth2
핵심 개념
3가지 모델 비교·refresh token·SSO — 프로덕션 인증 결정.
본문
3가지 인증 모델
Session (서버 저장)
- 쿠키에 sessionId
- 서버 메모리/Redis에 매핑
- 로그아웃 즉시 무효화 가능
- 서버 부하 ↑
JWT (클라이언트 저장)
- 서명된 토큰 (stateless)
- 서버 부하 0
- 만료 시까지 무효화 어려움
- 토큰 도난 시 위험
OAuth2 (위임)
- Google/GitHub/Kakao 로그인
- 비밀번호 관리 안 함
- 외부 의존결정 트리
서버가 1대?
├─ Yes
│ └─ Session (가장 단순)
└─ No (분산)
├─ Redis 사용 가능?
│ ├─ Yes → Session + Redis
│ └─ No → JWT
│
├─ 마이크로서비스?
│ └─ JWT (서비스 간 검증 쉬움)
│
└─ 외부 ID 공급자?
└─ OAuth2 + JWTSession 구현 (권장)
import session from 'express-session';
import RedisStore from 'connect-redis';
app.use(session({
store: new RedisStore({ client: redis, prefix: 'sess:' }),
secret: process.env.SESSION_SECRET!,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: true, // HTTPS만
sameSite: 'lax', // CSRF 보호
maxAge: 1000 * 60 * 60 * 24 * 7,
},
}));
// 로그인
app.post('/login', async (req, res) => {
const user = await authenticate(req.body);
req.session.userId = user.id;
res.json({ ok: true });
});
// 로그아웃 (서버에서 즉시 무효화 가능)
app.post('/logout', (req, res) => {
req.session.destroy(() => res.json({ ok: true }));
});JWT — Access + Refresh
import jwt from 'jsonwebtoken';
const ACCESS_TTL = '15m';
const REFRESH_TTL = '7d';
async function login(email: string, password: string) {
const user = await authenticate(email, password);
const accessToken = jwt.sign(
{ sub: user.id, role: user.role, type: 'access' },
process.env.JWT_ACCESS_SECRET!,
{ expiresIn: ACCESS_TTL },
);
const refreshToken = jwt.sign(
{ sub: user.id, type: 'refresh', jti: crypto.randomUUID() },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: REFRESH_TTL },
);
// refresh는 DB 저장 (revoke 가능)
await db.refreshToken.create({
data: {
userId: user.id,
tokenHash: hash(refreshToken),
expiresAt: new Date(Date.now() + 7 * 86400 * 1000),
},
});
return { accessToken, refreshToken };
}
// 갱신
async function refresh(refreshToken: string) {
const payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!) as any;
// DB에서 유효성 확인 (revoke 가능)
const stored = await db.refreshToken.findUnique({
where: { tokenHash: hash(refreshToken) },
});
if (!stored || stored.revokedAt) throw new UnauthorizedError();
// 회전 (rotation) — 옛 토큰 revoke + 새 토큰 발급
await db.refreshToken.update({
where: { id: stored.id },
data: { revokedAt: new Date() },
});
return issueTokens(payload.sub);
}토큰 저장 — 클라이언트
[보안 우선]
- accessToken: JS 메모리 (페이지 새로고침 시 refresh로 재발급)
- refreshToken: httpOnly Secure SameSite=Strict 쿠키
[구현 단순]
- 모두 localStorage (XSS 위험)
[모바일 앱]
- Secure Storage (Keychain·Keystore)OAuth2 + JWT (조합)
// Google OAuth → 우리 JWT 발급
app.get('/auth/google/callback', async (req, res) => {
// 1. Google에서 access token 받음
const googleToken = await exchangeCodeForToken(req.query.code);
// 2. Google 사용자 정보 조회
const profile = await fetchGoogleProfile(googleToken);
// 3. 우리 DB에서 사용자 찾거나 생성
let user = await db.user.findUnique({ where: { googleId: profile.id } });
if (!user) {
user = await db.user.create({
data: {
email: profile.email,
name: profile.name,
googleId: profile.id,
},
});
}
// 4. 우리 JWT 발급
const tokens = await issueTokens(user.id);
res.cookie('refreshToken', tokens.refreshToken, {
httpOnly: true, secure: true, sameSite: 'lax',
});
res.redirect(`/?token=${tokens.accessToken}`);
});SSO (Single Sign-On)
[OIDC (OpenID Connect)]
- OAuth2 위에 ID 레이어
- Google·Microsoft·Okta·Auth0
[SAML]
- 기업용 (legacy)
- Okta·OneLogin
[구현 — Clerk·Auth0·Workos]
- 자체 구현 대신 SaaS
- 시간 90% 절감다음 챕터
CH.103 "OWASP Top 10 실전 방어 코드".
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 코드의 인증 아키텍처 부분을 분석해서 실전 분석 + 개선 우선순위를 알려줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
인증 아키텍처 관련 베스트 프랙티스 5가지를 비교 분석해서 패턴 추출를 알려줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 프로젝트 전체에서 인증 아키텍처 최적화 가능 위치를 보고해줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 한국 시장의 인증 아키텍처 트렌드를 솔직히 알려줘.
⭐ 이것만 기억하세요
인증 아키텍처: Session vs JWT vs OAuth2는 이 3가지만 확실히 잡으세요
1.Session(Redis) = 단순·안전, JWT(stateless) = 분산·MSA
2.Refresh token rotation으로 도난 탐지
3.OAuth2 + 자체 JWT 조합 = 외부 ID + 내부 권한
공유하기
진행도 102 / 120