stack-analysis
CHAPTER 32 / 90
읽기 약 2분
FUNCTION
인증 시스템: Passport + JWT + OAuth2
핵심 개념
JWT 발급·refresh token·OAuth2(Google/GitHub) — 프로덕션 인증 플로우.
본문
JWT 인증 — 액세스/리프레시 토큰
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';
const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET!;
const REFRESH_SECRET = process.env.JWT_REFRESH_SECRET!;
const ACCESS_TTL = '15m';
const REFRESH_TTL = '7d';
export class AuthService {
async login(email: string, password: string) {
const user = await this.userRepo.findByEmail(email);
if (!user) throw new UnauthorizedError('Invalid credentials');
const ok = await bcrypt.compare(password, user.passwordHash);
if (!ok) throw new UnauthorizedError('Invalid credentials');
return this.issueTokens(user.id);
}
async signup(email: string, password: string, name: string) {
const existing = await this.userRepo.findByEmail(email);
if (existing) throw new ConflictError('Email exists');
const passwordHash = await bcrypt.hash(password, 12);
const user = await this.userRepo.create({ email, passwordHash, name });
return this.issueTokens(user.id);
}
private issueTokens(userId: string) {
const accessToken = jwt.sign({ sub: userId, type: 'access' }, ACCESS_SECRET, {
expiresIn: ACCESS_TTL,
});
const refreshToken = jwt.sign({ sub: userId, type: 'refresh' }, REFRESH_SECRET, {
expiresIn: REFRESH_TTL,
});
// refresh는 DB에 저장 (revoke 가능)
this.refreshRepo.save(userId, refreshToken);
return { accessToken, refreshToken };
}
async refresh(refreshToken: string) {
const payload = jwt.verify(refreshToken, REFRESH_SECRET) as { sub: string };
const exists = await this.refreshRepo.exists(payload.sub, refreshToken);
if (!exists) throw new UnauthorizedError('Refresh token revoked');
return this.issueTokens(payload.sub);
}
}인증 미들웨어
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
declare global {
namespace Express {
interface Request {
user?: { id: string; role: string };
}
}
}
export function authenticate(req: Request, res: Response, next: NextFunction) {
const auth = req.headers.authorization;
if (!auth?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing token' });
}
const token = auth.slice(7);
try {
const payload = jwt.verify(token, process.env.JWT_ACCESS_SECRET!) as any;
req.user = { id: payload.sub, role: payload.role };
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
}
export function requireRole(...roles: string[]) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user || !roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}OAuth2 (Passport - Google)
import passport from 'passport';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
callbackURL: '/auth/google/callback',
}, async (accessToken, refreshToken, profile, done) => {
try {
let user = await userRepo.findByGoogleId(profile.id);
if (!user) {
user = await userRepo.create({
email: profile.emails![0].value,
name: profile.displayName,
googleId: profile.id,
});
}
done(null, user);
} catch (err) {
done(err as Error);
}
}));
// 라우트
app.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] })
);
app.get('/auth/google/callback',
passport.authenticate('google', { session: false }),
(req, res) => {
const { accessToken, refreshToken } = authService.issueTokens(req.user!.id);
// 쿠키에 설정 또는 프론트로 리다이렉트
res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: true });
res.redirect(`/?token=${accessToken}`);
}
);httpOnly 쿠키 vs localStorage
| 저장소 | XSS 위험 | CSRF 위험 | 모바일 |
|---|---|---|---|
| localStorage | 높음 (JS 접근) | 없음 | 가능 |
| httpOnly Cookie | 낮음 | 있음 | 제한 |
권장:
- accessToken: 메모리 (페이지 새로고침 시 refresh로 재발급)
- refreshToken: httpOnly Secure SameSite=Strict 쿠키다음 챕터
CH.33 "파일 업로드: Multer + S3 + presigned URL".
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년 한국 백엔드 시장의 인증 시스템 트렌드를 솔직히 알려줘.
⭐ 이것만 기억하세요
인증 시스템: Passport + JWT + OAuth2는 이 3가지만 확실히 잡으세요
1.JWT는 액세스(15분) + 리프레시(7일) 분리 — 보안과 UX 균형
2.리프레시 토큰은 DB 저장 → 로그아웃 시 revoke 가능
3.OAuth2는 Passport 전략으로 — Google/GitHub/Kakao 모두 동일 패턴
공유하기
진행도 32 / 90