How to Build an Authentication System with Claude Code (JWT and OAuth)
How to Build an Authentication System: Claude Code 활용 (JWT and OAuth). 코드 예시가 포함된 실전 가이드입니다.
인증システム구축にClaude Code를 사용하는メリット
인증は보안に直結する機能であり、구현ミスは重大な脆弱性につながります。Claude Code는 보안の모범 사례を踏まえた구현を생성でき、見落としがちな攻撃ベクトルへの対策も提案してくれます。
JWT인증の구현
> Express + TypeScriptでJWT인증를 구현해줘。
> アクセス토큰(15分)とリフレッシュ토큰(7日)の2토큰方式で。
> リフレッシュ토큰はhttpOnly쿠키で관리して。
인증サービス
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import { prisma } from './db';
const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET!;
const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET!;
interface TokenPayload {
userId: string;
email: string;
}
export class AuthService {
async register(email: string, password: string, name: string) {
const existing = await prisma.user.findUnique({ where: { email } });
if (existing) throw new Error('Email already registered');
const hashedPassword = await bcrypt.hash(password, 12);
const user = await prisma.user.create({
data: { email, password: hashedPassword, name },
});
return this.generateTokens({ userId: user.id, email: user.email });
}
async login(email: string, password: string) {
const user = await prisma.user.findUnique({ where: { email } });
if (!user) throw new Error('Invalid credentials');
const valid = await bcrypt.compare(password, user.password);
if (!valid) throw new Error('Invalid credentials');
return this.generateTokens({ userId: user.id, email: user.email });
}
async refreshToken(token: string) {
const payload = jwt.verify(token, REFRESH_TOKEN_SECRET) as TokenPayload;
// DBでリフレッシュ토큰の유효性を확인
const stored = await prisma.refreshToken.findFirst({
where: { token, userId: payload.userId, revoked: false },
});
if (!stored) throw new Error('Invalid refresh token');
// 古い토큰を무효化(토큰ローテーション)
await prisma.refreshToken.update({
where: { id: stored.id },
data: { revoked: true },
});
return this.generateTokens(payload);
}
private async generateTokens(payload: TokenPayload) {
const accessToken = jwt.sign(payload, ACCESS_TOKEN_SECRET, {
expiresIn: '15m',
});
const refreshToken = jwt.sign(payload, REFRESH_TOKEN_SECRET, {
expiresIn: '7d',
});
await prisma.refreshToken.create({
data: { token: refreshToken, userId: payload.userId },
});
return { accessToken, refreshToken };
}
}
인증미들웨어
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
export function authMiddleware(req: Request, res: Response, next: NextFunction) {
const header = req.headers.authorization;
if (!header?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = header.slice(7);
try {
const payload = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET!);
req.user = payload as TokenPayload;
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
}
ルーター설정
import { Router } from 'express';
import { AuthService } from './auth-service';
const router = Router();
const auth = new AuthService();
router.post('/register', async (req, res) => {
try {
const { accessToken, refreshToken } = await auth.register(
req.body.email, req.body.password, req.body.name
);
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000,
});
res.json({ accessToken });
} catch (err) {
res.status(400).json({ error: (err as Error).message });
}
});
router.post('/login', async (req, res) => {
try {
const { accessToken, refreshToken } = await auth.login(
req.body.email, req.body.password
);
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000,
});
res.json({ accessToken });
} catch (err) {
res.status(401).json({ error: 'Invalid credentials' });
}
});
router.post('/refresh', async (req, res) => {
try {
const token = req.cookies.refreshToken;
const { accessToken, refreshToken } = await auth.refreshToken(token);
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000,
});
res.json({ accessToken });
} catch (err) {
res.status(401).json({ error: 'Invalid refresh token' });
}
});
export default router;
OAuth연동(Google)
> PassportJSでGoogle로그イン를 추가해줘。既存のJWT인증と통합して。
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) => {
let user = await prisma.user.findFirst({
where: { providerId: profile.id, provider: 'google' },
});
if (!user) {
user = await prisma.user.create({
data: {
email: profile.emails![0].value,
name: profile.displayName,
provider: 'google',
providerId: profile.id,
},
});
}
done(null, user);
}));
보안チェック리스트
Claude Code에다음의プロンプトで보안監査を依頼할 수 있습니다。
> 인증まわりのコードを보안観点でレビューして。
> OWASP Top 10에 기반하여チェックして。
主な확인項目は다음의通りです。
- 비밀번호ハッシュにbcryptを使用しているか
- JWTのシークレットキーが十分に長いか
- リフレッシュ토큰のローテーションが구현されているか
- CSRF対策が施されているか
- レートリミットが설정されているか
보안を含むコード品質の維持には리팩터링の자동화も効果的です。또한、인증の설정方針をCLAUDE.mdに記述しておくと、Claude Code가 一貫したコードを생성します。
정리
Claude Code를 활용하면 JWT인증やOAuth연동を含む堅牢な인증システムを효율적으로구축할 수 있습니다。보안の모범 사례を踏まえたコードが생성されるため、見落としがちな脆弱性にも対処しやすくなります。必ず프로덕션 환경ではシークレットキーの安全な관리とHTTPS通信を徹底して주세요。
자세한 내용은Anthropic공식 문서를 확인하세요.
#Claude Code
#authentication
#JWT
#OAuth
#security
Related Posts
Use Cases
Use Cases
Claude Code로 리팩토링을 자동화하는 방법
Claude Code를 활용해 코드 리팩토링을 효율적으로 자동화하는 방법을 알아봅니다. 실전 프롬프트와 구체적인 리팩토링 패턴을 소개합니다.
Use Cases
Use Cases
Claude Code로 사이드 프로젝트 개발 속도를 극대화하는 방법 [예제 포함]
Claude Code를 활용해 개인 프로젝트 개발 속도를 획기적으로 높이는 방법을 알아봅니다. 실전 예제와 아이디어부터 배포까지의 워크플로를 포함합니다.
Use Cases
Use Cases
Complete CORS Configuration Guide: Claude Code 활용 가이드
complete cors configuration guide: Claude Code 활용. 실용적인 팁과 코드 예시를 포함합니다.