원클릭으로
authentication-authorization
JWT auth, OAuth, OTP verification, session management, RBAC for SE104_VLEAGUE
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
JWT auth, OAuth, OTP verification, session management, RBAC for SE104_VLEAGUE
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Swagger/OpenAPI setup and documentation patterns for SE104_VLEAGUE
Complete guide for the SE104_VLEAGUE React frontend — pages, services, components, auth flow, i18n, and patterns
Complete guide for testing patterns, test structure, CI pipeline, and development workflow for SE104_VLEAGUE
Complete guide for the SE104_VLEAGUE NestJS backend — modules, endpoints, Prisma schema, guards, interceptors, and patterns
All business rules, state machines, scoring, regulations, and domain constraints for SE104_VLEAGUE
Prisma schema, migrations, seeding, and database operations for SE104_VLEAGUE
| name | Authentication & Authorization |
| description | JWT auth, OAuth, OTP verification, session management, RBAC for SE104_VLEAGUE |
| Token | Storage | Lifetime | Purpose |
|---|---|---|---|
| Access Token (JWT) | In-memory (frontend) | 15min | API authorization |
| Refresh Token | localStorage (frontend), sha256-hashed in DB | 7d (30d with "remember me") | Token renewal |
{
"sub": "user-uuid",
"email": "user@email.com",
"role": "ADMIN",
"name": "User Name",
"iat": 1234567890,
"exp": 1234568790
}
| Role | Description |
|---|---|
ADMIN | Full system access, user management |
TEAM_MANAGER | Manage own team roster, players |
REFEREE | Add/remove match events |
SUPERVISOR | Read-only oversight |
PUBLIC | Basic authenticated access |
Roles are stored in two places (for flexibility):
role enum field on User model — used by guardsroleId FK to roles table — future extensibility| Method | Endpoint | Rate Limit | Description |
|---|---|---|---|
| POST | /auth/register | 5/min | Register + send verification OTP |
| POST | /auth/verify-email | 5/10s | Verify email with 6-digit OTP |
| POST | /auth/resend-otp | 3/min | Resend verification OTP (60s cooldown) |
| POST | /auth/forgot-password | 3/min | Request password reset OTP |
| POST | /auth/reset-password | 5/10s | Reset password with OTP |
| POST | /auth/login | 5/min | Login → access + refresh tokens |
| POST | /auth/refresh | SkipThrottle | Refresh access token |
| POST | /auth/logout | SkipThrottle | Revoke refresh token |
| Method | Endpoint | Description |
|---|---|---|
| GET | /auth/me | Current user profile |
| POST | /auth/change-password | Change password (validates current) |
| POST | /auth/logout-all | Revoke all sessions |
| PATCH | /auth/profile | Update name/avatarUrl |
| GET | /auth/sessions | List active sessions |
| DELETE | /auth/sessions/:sessionId | Revoke specific session |
| POST | /auth/set-password | Set password for OAuth-only users |
| Method | Endpoint | Description |
|---|---|---|
| GET | /auth/google | Start Google OAuth flow |
| GET | /auth/google/callback | Google callback → redirect frontend |
| GET | /auth/facebook | Start Facebook OAuth flow |
| GET | /auth/facebook/callback | Facebook callback → redirect frontend |
AUTH_EMAIL_EXISTS checkotp_codes tableMailService.sendEmailVerificationOtp()emailVerified = trueMAIL_SKIP_SEND=true logs OTP to consoleGET /api/auth/google → Google consent screen
→ GET /api/auth/google/callback → generate tokens
→ Redirect: {FRONTEND_URL}/auth/oauth-callback?accessToken=...&refreshToken=...
Same flow as Google, different strategy.
googleId/facebookId)model RefreshToken {
id String @id @default(uuid()) @db.Uuid
tokenHash String @map("token_hash") // sha256 hash
userId String @map("user_id") @db.Uuid
userAgent String? @map("user_agent")
ipAddress String? @map("ip_address")
deviceName String? @map("device_name") // Parsed from UA
lastUsedAt DateTime @default(now()) @map("last_used_at")
revokedAt DateTime? @map("revoked_at")
expiresAt DateTime @map("expires_at")
}
GET /auth/sessions shows all active sessionsDELETE /auth/sessions/:idPOST /auth/logout-allexpiresAt| Guard | Purpose |
|---|---|
JwtAuthGuard (global) | Validates JWT; skips @Public() routes |
RolesGuard (global) | Checks @Roles() metadata vs req.user.role |
GoogleAuthGuard | Passport Google OAuth strategy |
FacebookAuthGuard | Passport Facebook OAuth strategy |
@Public() // Skip JWT auth
@Roles(UserRole.ADMIN) // Require ADMIN role
@Roles(UserRole.ADMIN, UserRole.TEAM_MANAGER) // Multiple roles (OR)
@CurrentUser() // Extract user from request
| Strategy | Config Key |
|---|---|
JwtStrategy | JWT_SECRET |
GoogleStrategy | GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_CALLBACK_URL |
FacebookStrategy | FACEBOOK_APP_ID, FACEBOOK_APP_SECRET, FACEBOOK_CALLBACK_URL |
src/auth/AuthContext.tsx)App mount → attempt silent refresh (stored RT)
├── Success → decode JWT → set user, isAuthed=true
└── Fail → isAuthed=false, show login
Login → POST /auth/login → store AT (memory) + RT (localStorage)
→ decode JWT → set user
OAuth → /auth/oauth-callback → applyOAuthTokens(at, rt) → decode JWT
Logout → POST /auth/logout → clear tokens → redirect /login
lib/api.ts)auth:expired custom event → AuthContext clears stateapi:rate-limited custom event<RequireAuth> // Redirects to /login if !isAuthed, preserves location
<RequireRole allow={['ADMIN']}> // Redirects to /403 if wrong role
<ProtectedPage />
</RequireRole>
</RequireAuth>
# Backend
JWT_SECRET=your-jwt-secret
JWT_REFRESH_SECRET=your-refresh-secret
JWT_EXPIRATION=15m
JWT_REFRESH_EXPIRATION=7d
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GOOGLE_CALLBACK_URL=http://localhost:8080/api/auth/google/callback
FACEBOOK_APP_ID=...
FACEBOOK_APP_SECRET=...
FACEBOOK_CALLBACK_URL=http://localhost:8080/api/auth/facebook/callback
FRONTEND_URL=http://localhost:5173
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USER=...
MAIL_PASS=...
MAIL_FROM=noreply@vleague.local
MAIL_SKIP_SEND=true