Migrate from AngularJS to Angular using hybrid mode, incremental component rewriting, and dependency injection updates. Use when upgrading AngularJS applications, planning framework migrations, or modernizing legacy Angular code.
中文简介正在准备中。当前显示原文说明。
JWT、OAuth2、セッション管理、RBACを含む認証・認可パターンをマスターし、安全でスケーラブルなアクセス制御システムを構築。認証システムの実装、APIの保護、セキュリティ問題のデバッグ時に使用。
中文简介正在准备中。当前显示原文说明。
查看安装方法安装前可以查看交给 AI 代理的指令内容。
English | 日本語
業界標準のパターンと最新のベストプラクティスを使用して、安全でスケーラブルな認証・認可システムを構築します。
認証(Authentication, AuthN):あなたは誰ですか?
認可(Authorization, AuthZ):あなたは何ができますか?
セッションベース:
トークンベース(JWT):
OAuth2/OpenID Connect:
// JWT構造:header.payload.signature
import 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 });
});
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: 'ログアウトしました' });
});
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: 'ログアウトしました' });
});
});
import passport from 'passport';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
import { Strategy as GitHubStrategy } from 'passport-github2';
// Google OAuth
passport.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}`);
}
);
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: 'ユーザーを削除しました' });
}
);
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 });
}
);
// ユーザーがリソースを所有しているか確認
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 });
}
);
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: '登録に失敗しました' });
}
});
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);
还没有评价。欢迎分享你的使用感受。
无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。
还没有评论。来写第一条吧。
Migrate from AngularJS to Angular using hybrid mode, incremental component rewriting, and dependency injection updates. Use when upgrading AngularJS applications, planning framework migrations, or modernizing legacy Angular code.
中文简介正在准备中。当前显示原文说明。
ハイブリッドモード、段階的コンポーネント書き換え、依存性注入更新を使用してAngularJSからAngularへ移行します。AngularJSアプリケーションのアップグレード、フレームワーク移行の計画、またはレガシーAngularコードのモダナイゼーション時に使用します。
中文简介正在准备中。当前显示原文说明。
開発者を喜ばせる直感的でスケーラブルで保守可能なAPIを構築するために、RESTとGraphQL APIの設計原則をマスターします。新しいAPIの設計、API仕様のレビュー、またはAPI設計標準の確立時に使用してください。
中文简介正在准备中。当前显示原文说明。
Master REST and GraphQL API design principles to build intuitive, scalable, and maintainable APIs that delight developers. Use when designing new APIs, reviewing API specifications, or establishing API design standards.
中文简介正在准备中。当前显示原文说明。
Implement proven backend architecture patterns including Clean Architecture, Hexagonal Architecture, and Domain-Driven Design. Use when architecting complex backend systems or refactoring existing applications for better maintainability.
中文简介正在准备中。当前显示原文说明。
クリーンアーキテクチャ、ヘキサゴナルアーキテクチャ、ドメイン駆動設計を含む実証済みのバックエンドアーキテクチャパターンを実装します。複雑なバックエンドシステムの設計や、保守性向上のための既存アプリケーションのリファクタリング時に使用します。
中文简介正在准备中。当前显示原文说明。