在 Manus 中运行任何 Skill
一键导入
一键导入
一键在 Manus 中运行任何 Skill
开始使用nestjs-auth
星标2
分支0
更新时间2026年4月17日 01:09
NestJS 인증 패턴. Passport JWT, OAuth, Guard, 토큰 관리.
安装
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
SKILL.md
readonly菜单
NestJS 인증 패턴. Passport JWT, OAuth, Guard, 토큰 관리.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Figma MCP 로 디자인을 픽셀 퍼펙트로 구현. 토큰 추출 → 테마 매핑 → 정확한 레이아웃 → 스크린샷 diff 검증 루프. Flutter/React 공용.
Flutter 외부/BaaS API 연동 풀 파이프라인. 계약 → retrofit 코드젠 → dio 인터셉터(auth/retry) → Result 매핑 → repository → 오프라인 캐시. dio-retrofit 스킬 심화.
TanStack Query v5 패턴 (React). 서버 상태 패칭/캐시/뮤테이션/낙관적 업데이트.
Supabase BaaS 연동 패턴 (Flutter). 인증/DB/리얼타임/스토리지/RLS.
Auth.js v5 (NextAuth) 패턴. 세션/미들웨어/Server Action 가드.
Cross-stack API 계약 동기화. NestJS DTO ↔ Flutter DTO 일치성 관리.
| name | nestjs-auth |
| description | NestJS 인증 패턴. Passport JWT, OAuth, Guard, 토큰 관리. |
| globs | server/src/auth/**, server/src/**/guard/**, server/src/**/decorator/** |
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
export interface JwtPayload {
sub: string; // userId
email: string;
role: string;
iat: number;
exp: number;
}
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: config.getOrThrow<string>('JWT_SECRET'),
});
}
validate(payload: JwtPayload): JwtPayload {
if (!payload.sub) throw new UnauthorizedException();
return payload;
}
}
import { Injectable, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
return super.canActivate(context);
}
}
// @Public() - 인증 불필요 엔드포인트
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
// @CurrentUser() - 현재 유저 추출
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CurrentUser = createParamDecorator(
(data: keyof JwtPayload | undefined, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user as JwtPayload;
return data ? user[data] : user;
},
);
@Injectable()
export class AuthService {
constructor(
private readonly prisma: PrismaService,
private readonly jwtService: JwtService,
private readonly config: ConfigService,
) {}
async login(provider: string, accessToken: string) {
// 1. OAuth 토큰 검증 (카카오/네이버/애플)
const profile = await this.verifyOAuthToken(provider, accessToken);
// 2. 유저 조회/생성
const user = await this.prisma.user.upsert({
where: { providerId_provider: { providerId: profile.id, provider } },
update: { lastLoginAt: new Date() },
create: { provider, providerId: profile.id, nickname: profile.nickname },
});
// 3. JWT 발급
const tokens = await this.generateTokens(user);
// 4. Refresh Token 해시 저장
await this.prisma.user.update({
where: { id: user.id },
data: { refreshTokenHash: await hash(tokens.refreshToken) },
});
return tokens;
}
async refresh(refreshToken: string) {
const payload = this.jwtService.verify(refreshToken, {
secret: this.config.getOrThrow('JWT_REFRESH_SECRET'),
});
const user = await this.prisma.user.findUniqueOrThrow({
where: { id: payload.sub },
});
// Refresh Token 해시 대조
const isValid = await compare(refreshToken, user.refreshTokenHash);
if (!isValid) throw new UnauthorizedException('Invalid refresh token');
return this.generateTokens(user);
}
private async generateTokens(user: User) {
const payload: Omit<JwtPayload, 'iat' | 'exp'> = {
sub: user.id,
email: user.email,
role: user.role,
};
return {
accessToken: this.jwtService.sign(payload, {
secret: this.config.getOrThrow('JWT_SECRET'),
expiresIn: '15m',
}),
refreshToken: this.jwtService.sign(payload, {
secret: this.config.getOrThrow('JWT_REFRESH_SECRET'),
expiresIn: '30d',
}),
};
}
}
@Module({
imports: [
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.registerAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
secret: config.getOrThrow('JWT_SECRET'),
signOptions: { expiresIn: '15m' },
}),
}),
PrismaModule,
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy, JwtAuthGuard],
exports: [JwtAuthGuard, JwtStrategy],
})
export class AuthModule {}
@Public() 데코레이터로 공개 엔드포인트 명시