옵션
집 Skill 보안 auth-implementation-patterns

auth-implementation-patterns

amurata/cc-tools amurata/cc-tools

JWT, OAuth2, 세션 관리, RBAC을 포함한 인증 및 권한 부여 패턴을 숙지하여 안전하고 확장 가능한 액세스 제어 시스템을 구축합니다. 인증 시스템 구현, API 보호, 보안 문제 디버깅 시에 사용됩니다.

...모든 것을 확장하십시오
53
업데이트 된 시간 2026년 6월 29일

소개 auth-implementation-patterns

auth-implementation-patterns 스킬은 안전하고 확장 가능한 인증 및 권한 부여 시스템을 구축하기 위한 포괄적인 프레임워크를 제공합니다. 이 스킬은 업계 표준 패턴과 모범 사례에 중점을 두어, 개발자가 웹 애플리케이션 및 API를 위한 견고한 접근 제어 메커니즘을 구현할 수 있도록 지원합니다. 인증(사용자 신원 확인)과 권한 부여(권한 관리)를 모두 다루는 이 스킬은 무단 접근을 방지하고, 사용자가 리소스와 안전하게 상호작용할 수 있도록 보장합니다. 특히 높은 보안 수준이 요구되거나, 멀티테넌트 환경이 필요한 프로젝트, 또는 소셜 로그인 제공업체 및 기업용 SSO 시스템과의 통합이 필요한 프로젝트에 유용합니다.

주요 기능으로는 세션 기반 인증, JWT 토큰 기반 인증, OAuth2/OpenID Connect 위임 지원 등이 있습니다. 이 스킬에서는 JWT 생성 및 검증, 리프레시 토큰 흐름 구현, 사용자 세션 관리, 역할 기반 접근 제어(RBAC) 정책 적용 방법을 다룹니다. 또한 REST 및 GraphQL API 보안 강화, 소셜 로그인 통합, 인증 문제 디버깅에 대한 지침도 제공합니다. 개발자는 이러한 패턴을 활용하여 유연하고 유지 관리가 용이하며 수평적으로 확장 가능한 인증 시스템을 구축할 수 있습니다.

이 스킬은 웹 애플리케이션, API 개발 또는 엔터프라이즈 플랫폼을 다루는 백엔드 개발자, 보안 엔지니어, 소프트웨어 아키텍트에게 이상적입니다. 일반적인 사용 사례로는 사용자 로그인 시스템 구현, API 보호, OAuth2/소셜 로그인 추가, 세션 관리 설계, 인증 시스템 마이그레이션, 인증 관련 문제 해결, SSO 또는 멀티테넌트 액세스 제어 배포 등이 있습니다. 이 과정은 최신 소프트웨어 애플리케이션에서 안전한 사용자 인증과 세분화된 권한 부여를 보장해야 하는 모든 분을 대상으로 합니다.

자주 묻는 질문

이 스킬은 언제 사용해야 하나요?

이 스킬은 사용자 인증 시스템 구현, API 보안 강화, 소셜 로그인 통합, 세션 관리 또는 역할 기반 접근 제어(RBAC) 설정 시에 가장 효과적으로 활용할 수 있습니다.

어떤 인증 방법이 지원되나요?

세션 기반 인증, JWT 토큰 기반 인증, 위임 인증 및 소셜 로그인을 위한 OAuth2/OpenID Connect 흐름을 다룹니다.

이 스킬을 REST 및 GraphQL API와 함께 사용할 수 있나요?

네, 제공된 패턴과 예제는 REST 및 GraphQL API 모두를 보호하는 데 적용할 수 있습니다.

선행 조건이나 제한 사항이 있나요?

개발자는 Express.js와 같은 웹 프레임워크에 대한 기본 지식을 갖추고 있으며, TypeScript 또는 JavaScript에 익숙해야 합니다. 이 스킬은 리프레시 토큰을 저장할 데이터베이스와 같이 토큰을 저장하고 검증할 수 있는 환경을 전제로 합니다.

이 스킬은 멀티테넌트 또는 엔터프라이즈 SSO 설정을 지원하나요?

네, 업계 표준 패턴을 사용하여 SSO 및 멀티 테넌트 인증 시나리오를 구현하는 방법에 대한 지침을 제공합니다.

GitHub에서 보기

English | 日本語

認証・認可実装パターン

業界標準のパターンと最新のベストプラクティスを使用して、安全でスケーラブルな認証・認可システムを構築します。

このスキルを使用するタイミング

  • ユーザー認証システムの実装
  • REST または GraphQL API の保護
  • OAuth2/ソーシャルログインの追加
  • ロールベースアクセス制御(RBAC)の実装
  • セッション管理の設計
  • 認証システムの移行
  • 認証問題のデバッグ
  • SSO またはマルチテナンシーの実装

コア概念

1. 認証 vs 認可

認証(Authentication, AuthN):あなたは誰ですか?

  • 身元の検証(ユーザー名/パスワード、OAuth、生体認証)
  • 資格情報の発行(セッション、トークン)
  • ログイン/ログアウトの管理

認可(Authorization, AuthZ):あなたは何ができますか?

  • 権限チェック
  • ロールベースアクセス制御(RBAC)
  • リソース所有権の検証
  • ポリシー適用

2. 認証戦略

セッションベース:

  • サーバーがセッション状態を保存
  • Cookie内のセッションID
  • 従来型、シンプル、ステートフル

トークンベース(JWT):

  • ステートレス、自己完結型
  • 水平スケーリング可能
  • クレームを保存可能

OAuth2/OpenID Connect:

  • 認証の委譲
  • ソーシャルログイン(Google、GitHub)
  • エンタープライズSSO

JWT認証

パターン1:JWT実装

// JWT構造:header.payload.signatureimport jwt from 'jsonwebtoken';import { Request, Response, NextFunction } from 'express';interface JWTPayload {    userId: string;    email: string;    role: string;    iat: number;    exp: number;}// JWTを生成function generateTokens(userId: string, email: string, role: string) {    const accessToken = jwt.sign(        { userId, email, role },        process.env.JWT_SECRET!,        { expiresIn: '15m' }  // 短命    );    const refreshToken = jwt.sign(        { userId },        process.env.JWT_REFRESH_SECRET!,        { expiresIn: '7d' }  // 長命    );    return { accessToken, refreshToken };}// JWTを検証function verifyToken(token: string): JWTPayload {    try {        return jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;    } catch (error) {        if (error instanceof jwt.TokenExpiredError) {            throw new Error('トークンが期限切れです');        }        if (error instanceof jwt.JsonWebTokenError) {            throw new Error('無効なトークンです');        }        throw error;    }}// ミドルウェアfunction authenticate(req: Request, res: Response, next: NextFunction) {    const authHeader = req.headers.authorization;    if (!authHeader?.startsWith('Bearer ')) {        return res.status(401).json({ error: 'トークンが提供されていません' });    }    const token = authHeader.substring(7);    try {        const payload = verifyToken(token);        req.user = payload;  // リクエストにユーザーを付加        next();    } catch (error) {        return res.status(401).json({ error: '無効なトークンです' });    }}// 使用例app.get('/api/profile', authenticate, (req, res) => {    res.json({ user: req.user });});

パターン2:リフレッシュトークンフロー

interface StoredRefreshToken {    token: string;    userId: string;    expiresAt: Date;    createdAt: Date;}class RefreshTokenService {    // リフレッシュトークンをデータベースに保存    async storeRefreshToken(userId: string, refreshToken: string) {        const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);        await db.refreshTokens.create({            token: await hash(refreshToken),  // 保存前にハッシュ化            userId,            expiresAt,        });    }    // アクセストークンをリフレッシュ    async refreshAccessToken(refreshToken: string) {        // リフレッシュトークンを検証        let payload;        try {            payload = jwt.verify(                refreshToken,                process.env.JWT_REFRESH_SECRET!            ) as { userId: string };        } catch {            throw new Error('無効なリフレッシュトークンです');        }        // データベースにトークンが存在するか確認        const storedToken = await db.refreshTokens.findOne({            where: {                token: await hash(refreshToken),                userId: payload.userId,                expiresAt: { $gt: new Date() },            },        });        if (!storedToken) {            throw new Error('リフレッシュトークンが見つからないか期限切れです');        }        // ユーザーを取得        const user = await db.users.findById(payload.userId);        if (!user) {            throw new Error('ユーザーが見つかりません');        }        // 新しいアクセストークンを生成        const accessToken = jwt.sign(            { userId: user.id, email: user.email, role: user.role },            process.env.JWT_SECRET!,            { expiresIn: '15m' }        );        return { accessToken };    }    // リフレッシュトークンを無効化(ログアウト)    async revokeRefreshToken(refreshToken: string) {        await db.refreshTokens.deleteOne({            token: await hash(refreshToken),        });    }    // すべてのユーザートークンを無効化(全デバイスからログアウト)    async revokeAllUserTokens(userId: string) {        await db.refreshTokens.deleteMany({ userId });    }}// APIエンドポイントapp.post('/api/auth/refresh', async (req, res) => {    const { refreshToken } = req.body;    try {        const { accessToken } = await refreshTokenService            .refreshAccessToken(refreshToken);        res.json({ accessToken });    } catch (error) {        res.status(401).json({ error: '無効なリフレッシュトークンです' });    }});app.post('/api/auth/logout', authenticate, async (req, res) => {    const { refreshToken } = req.body;    await refreshTokenService.revokeRefreshToken(refreshToken);    res.json({ message: 'ログアウトしました' });});

セッションベース認証

パターン1:Express Session

import session from 'express-session';import RedisStore from 'connect-redis';import { createClient } from 'redis';// セッションストレージ用にRedisをセットアップconst redisClient = createClient({    url: process.env.REDIS_URL,});await redisClient.connect();app.use(    session({        store: new RedisStore({ client: redisClient }),        secret: process.env.SESSION_SECRET!,        resave: false,        saveUninitialized: false,        cookie: {            secure: process.env.NODE_ENV === 'production',  // HTTPSのみ            httpOnly: true,  // JavaScriptからのアクセス不可            maxAge: 24 * 60 * 60 * 1000,  // 24時間            sameSite: 'strict',  // CSRF保護        },    }));// ログインapp.post('/api/auth/login', async (req, res) => {    const { email, password } = req.body;    const user = await db.users.findOne({ email });    if (!user || !(await verifyPassword(password, user.passwordHash))) {        return res.status(401).json({ error: '無効な認証情報です' });    }    // セッションにユーザーを保存    req.session.userId = user.id;    req.session.role = user.role;    res.json({ user: { id: user.id, email: user.email, role: user.role } });});// セッションミドルウェアfunction requireAuth(req: Request, res: Response, next: NextFunction) {    if (!req.session.userId) {        return res.status(401).json({ error: '認証されていません' });    }    next();}// 保護されたルートapp.get('/api/profile', requireAuth, async (req, res) => {    const user = await db.users.findById(req.session.userId);    res.json({ user });});// ログアウトapp.post('/api/auth/logout', (req, res) => {    req.session.destroy((err) => {        if (err) {            return res.status(500).json({ error: 'ログアウトに失敗しました' });        }        res.clearCookie('connect.sid');        res.json({ message: 'ログアウトしました' });    });});

OAuth2 / ソーシャルログイン

パターン1:Passport.jsを使用したOAuth2

import passport from 'passport';import { Strategy as GoogleStrategy } from 'passport-google-oauth20';import { Strategy as GitHubStrategy } from 'passport-github2';// Google OAuthpassport.use(    new GoogleStrategy(        {            clientID: process.env.GOOGLE_CLIENT_ID!,            clientSecret: process.env.GOOGLE_CLIENT_SECRET!,            callbackURL: '/api/auth/google/callback',        },        async (accessToken, refreshToken, profile, done) => {            try {                // ユーザーを検索または作成                let user = await db.users.findOne({                    googleId: profile.id,                });                if (!user) {                    user = await db.users.create({                        googleId: profile.id,                        email: profile.emails?.[0]?.value,                        name: profile.displayName,                        avatar: profile.photos?.[0]?.value,                    });                }                return done(null, user);            } catch (error) {                return done(error, undefined);            }        }    ));// ルートapp.get('/api/auth/google', passport.authenticate('google', {    scope: ['profile', 'email'],}));app.get(    '/api/auth/google/callback',    passport.authenticate('google', { session: false }),    (req, res) => {        // JWTを生成        const tokens = generateTokens(req.user.id, req.user.email, req.user.role);        // トークンと共にフロントエンドにリダイレクト        res.redirect(`${process.env.FRONTEND_URL}/auth/callback?token=${tokens.accessToken}`);    });

認可パターン

パターン1:ロールベースアクセス制御(RBAC)

enum Role {    USER = 'user',    MODERATOR = 'moderator',    ADMIN = 'admin',}const roleHierarchy: Record<Role, Role[]> = {    [Role.ADMIN]: [Role.ADMIN, Role.MODERATOR, Role.USER],    [Role.MODERATOR]: [Role.MODERATOR, Role.USER],    [Role.USER]: [Role.USER],};function hasRole(userRole: Role, requiredRole: Role): boolean {    return roleHierarchy[userRole].includes(requiredRole);}// ミドルウェアfunction requireRole(...roles: Role[]) {    return (req: Request, res: Response, next: NextFunction) => {        if (!req.user) {            return res.status(401).json({ error: '認証されていません' });        }        if (!roles.some(role => hasRole(req.user.role, role))) {            return res.status(403).json({ error: '権限が不十分です' });        }        next();    };}// 使用例app.delete('/api/users/:id',    authenticate,    requireRole(Role.ADMIN),    async (req, res) => {        // 管理者のみがユーザーを削除可能        await db.users.delete(req.params.id);        res.json({ message: 'ユーザーを削除しました' });    });

パターン2:権限ベースアクセス制御

enum Permission {    READ_USERS = 'read:users',    WRITE_USERS = 'write:users',    DELETE_USERS = 'delete:users',    READ_POSTS = 'read:posts',    WRITE_POSTS = 'write:posts',}const rolePermissions: Record<Role, Permission[]> = {    [Role.USER]: [Permission.READ_POSTS, Permission.WRITE_POSTS],    [Role.MODERATOR]: [        Permission.READ_POSTS,        Permission.WRITE_POSTS,        Permission.READ_USERS,    ],    [Role.ADMIN]: Object.values(Permission),};function hasPermission(userRole: Role, permission: Permission): boolean {    return rolePermissions[userRole]?.includes(permission) ?? false;}function requirePermission(...permissions: Permission[]) {    return (req: Request, res: Response, next: NextFunction) => {        if (!req.user) {            return res.status(401).json({ error: '認証されていません' });        }        const hasAllPermissions = permissions.every(permission =>            hasPermission(req.user.role, permission)        );        if (!hasAllPermissions) {            return res.status(403).json({ error: '権限が不十分です' });        }        next();    };}// 使用例app.get('/api/users',    authenticate,    requirePermission(Permission.READ_USERS),    async (req, res) => {        const users = await db.users.findAll();        res.json({ users });    });

パターン3:リソース所有権

// ユーザーがリソースを所有しているか確認async function requireOwnership(    resourceType: 'post' | 'comment',    resourceIdParam: string = 'id') {    return async (req: Request, res: Response, next: NextFunction) => {        if (!req.user) {            return res.status(401).json({ error: '認証されていません' });        }        const resourceId = req.params[resourceIdParam];        // 管理者はすべてにアクセス可能        if (req.user.role === Role.ADMIN) {            return next();        }        // 所有権を確認        let resource;        if (resourceType === 'post') {            resource = await db.posts.findById(resourceId);        } else if (resourceType === 'comment') {            resource = await db.comments.findById(resourceId);        }        if (!resource) {            return res.status(404).json({ error: 'リソースが見つかりません' });        }        if (resource.userId !== req.user.userId) {            return res.status(403).json({ error: '権限がありません' });        }        next();    };}// 使用例app.put('/api/posts/:id',    authenticate,    requireOwnership('post'),    async (req, res) => {        // ユーザーは自分の投稿のみ更新可能        const post = await db.posts.update(req.params.id, req.body);        res.json({ post });    });

セキュリティベストプラクティス

パターン1:パスワードセキュリティ

import bcrypt from 'bcrypt';import { z } from 'zod';// パスワード検証スキーマconst passwordSchema = z.string()    .min(12, 'パスワードは12文字以上である必要があります')    .regex(/[A-Z]/, 'パスワードには大文字を含める必要があります')    .regex(/[a-z]/, 'パスワードには小文字を含める必要があります')    .regex(/[0-9]/, 'パスワードには数字を含める必要があります')    .regex(/[^A-Za-z0-9]/, 'パスワードには特殊文字を含める必要があります');// パスワードをハッシュ化async function hashPassword(password: string): Promise<string> {    const saltRounds = 12;  // 2^12回の反復    return bcrypt.hash(password, saltRounds);}// パスワードを検証async function verifyPassword(    password: string,    hash: string): Promise<boolean> {    return bcrypt.compare(password, hash);}// パスワード検証付き登録app.post('/api/auth/register', async (req, res) => {    try {        const { email, password } = req.body;        // パスワードを検証        passwordSchema.parse(password);        // ユーザーが存在するか確認        const existingUser = await db.users.findOne({ email });        if (existingUser) {            return res.status(400).json({ error: 'メールアドレスは既に登録されています' });        }        // パスワードをハッシュ化        const passwordHash = await hashPassword(password);        // ユーザーを作成        const user = await db.users.create({            email,            passwordHash,        });        // トークンを生成        const tokens = generateTokens(user.id, user.email, user.role);        res.status(201).json({            user: { id: user.id, email: user.email },            ...tokens,        });    } catch (error) {        if (error instanceof z.ZodError) {            return res.status(400).json({ error: error.errors[0].message });        }        res.status(500).json({ error: '登録に失敗しました' });    }});

パターン2:レート制限

import rateLimit from 'express-rate-limit';import RedisStore from 'rate-limit-redis';// ログインレート制限const loginLimiter = rateLimit({    store: new RedisStore({ client: redisClient }),    windowMs: 15 * 60 * 1000,  // 15分    max: 5,  // 5回の試行    message: 'ログイン試行回数が多すぎます。後でもう一度お試しください',    standardHeaders: true,    legacyHeaders: false,});// APIレート制限const apiLimiter = rateLimit({    windowMs: 60 * 1000,  // 1分    max: 100,  // 1分あたり100リクエスト    standardHeaders: true,});// ルートに適用app.post('/api/auth/login', loginLimiter, async (req, res) => {    // ログインロジック});app.use('/api/', apiLimiter);

ベストプラクティス

  1. 平文パスワードを保存しない:常にbcrypt/argon2でハッシュ化
  2. HTTPSを使用:転送中のデータを暗号化
  3. 短命のアクセストークン:最大15〜30分
  4. 安全なCookie:httpOnly、secure、sameSiteフラグ
  5. すべての入力を検証:メール形式、パスワード強度
  6. 認証エンドポイントにレート制限:ブルートフォース攻撃を防止
  7. CSRF保護を実装:セッションベース認証用
  8. シークレットを定期的にローテーション:JWTシークレット、セッションシークレット
  9. セキュリティイベントをログ記録:ログイン試行、認証失敗
  10. 可能な限りMFAを使用:追加のセキュリティ層

よくある落とし穴

  • 弱いパスワード:強力なパスワードポリシーを適用
  • localStorageにJWT:XSSに脆弱、httpOnly Cookieを使用
  • トークン有効期限なし:トークンは期限切れにすべき
  • クライアント側の認証チェックのみ:常にサーバー側で検証
  • 安全でないパスワードリセット:有効期限付きの安全なトークンを使用
  • レート制限なし:ブルートフォース攻撃に脆弱
  • クライアントデータを信頼:常にサーバーで検証

リソース

  • references/jwt-best-practices.md:JWT実装ガイド
  • references/oauth2-flows.md:OAuth2フロー図と例
  • references/session-security.md:安全なセッション管理
  • assets/auth-security-checklist.md:セキュリティレビューチェックリスト
  • assets/password-policy-template.md:パスワード要件テンプレート
  • scripts/token-validator.ts:JWT検証ユーティリティ

모든 파일

1개 파일

auth-implementation-patterns 설치

스킬 파일을 다운로드하여 .claude/skills/ 디렉터리에 압축을 풀어주세요.

ZIP 다운로드

저장소를 클론하고 스킬 파일을 프로젝트에 복사하세요.

git clone https://github.com/amurata/cc-tools/blob/main/i18n/ja/plugins/developer-essentials/skills/auth-implementation-patterns/SKILL.md # Copy SKILL.md to your .claude/skills/ directory

복사 복사
빠른 설정: skill 폴더를 .claude/skills/로 복사하면 Claude가 해당 스킬을 자동으로 감지하여 사용합니다.
저장소 amurata/cc-tools

관련 스킬

gmgn-portfolio
업데이트 된 시간 2026년 7월 1일
zeroize-audit
업데이트 된 시간 2026년 7월 1일
device-integrity
업데이트 된 시간 2026년 6월 29일
flutter-use-http-package
업데이트 된 시간 2026년 6월 30일
OR