Extract token: AuthContextUtils.getTokenFromHeader(request) from Authorization: Bearer <token>. If missing, throw UnauthorizedException.
Validate: authService.isValidToken(token). If false, throw Unauthorized.
Session check is optional for access tokens if stateless-only, recommended if session-aware access revocation is required:
If session-aware: include a sid claim in access token and verify active session exists in DB/cache.
If missing or revoked, throw Unauthorized (for example, "Session not found or revoked").
Attach token to request (for example, (request as AuthenticatedRequest).authToken = token).
Permission check: Read permissions metadata from handler and class: reflector.get<string[]>('permissions', context.getHandler()) then class. If requiredPermissions.length > 0:
Decode JWT to get sub (userId).
Run Prisma: count permissions where name is in requiredPermissions and the permission is reachable via: User (userId) -> UserGroup -> Group -> GroupRole -> Role -> RolePermission -> Permission. Use a single prisma.permission.count({ where: { name: { in: requiredPermissions }, rolePermissions: { some: { role: { groupRoles: { some: { group: { users: { some: { userId } } } } } } } } } }).
If count < requiredPermissions.length, throw Unauthorized ("Insufficient permissions").
Return true.
Inject: AuthService, SessionService (or TokenService), Reflector, JwtService, PrismaService. Use Nest LoggerService or project logger for warnings.
findActiveByRefreshTokenHash(hash): Return matching active session.
rotateSession(sessionId, newHash, newExpiry): Revoke old + create successor session atomically.
revokeSession(sessionId): Mark revoked.
revokeAllForUser(userId): Revoke all active sessions for user.
cleanupExpired(): Optional scheduled cleanup.
If using Redis instead of Prisma, keep the same service contract and rotation semantics.
Step 7: Add auth context helpers and interfaces (required)
AuthContextUtils (for example, src/libs/rbac/utils/auth-context.utils.ts): Parse Authorization header; static getTokenFromHeader(request) returns token when scheme is Bearer, else undefined.
SessionInterface (for example, src/libs/rbac/interfaces/session.interface.ts): include id, userId, refreshTokenHash, expiresAt, revokedAt?, replacedById?.
Step 8: Protect routes with permissions metadata (required)
Guard + permissions:@UseGuards(AuthGuard) and @SetMetadata('permissions', ['resource.action']) (for example, ['users.read'], ['users.create']). Guard requires user to have all listed permissions.
Composite (optional): When Swagger is used, define Authentication() = applyDecorators(UseGuards(AuthGuard), ApiBearerAuth('JWT-auth'), HttpCode(HttpStatus.OK)) and use @Authentication() on protected routes.
Step 9: Align DTOs and controllers (required)
DTOs: Create/patch/get/delete per entity. Examples:
GetUserDto: id, username, createdAt (no password).
PatchUserDto: optional username, password.
Replace-user-groups: body as array of group IDs; same idea for group-roles and role-permissions.
Use class-validator where applicable (IsString, IsOptional, MinLength, etc.). If Swagger is present, add @ApiProperty to DTOs.
Controllers:
Auth:POST login (body: username, password; return token pair), POST refresh (read refresh token from cookie/header/body, rotate session), POST logout (revoke current refresh session), optional POST logout-all.
Users: CRUD (list, create, get by id, patch, delete), change-password, list/replace/assign/remove user groups. Protect with AuthGuard and @SetMetadata('permissions', ['users.read']) etc.
Groups, Roles, Permissions: CRUD plus assignment endpoints (for example, replace group roles, replace role permissions). Same guard + permissions pattern.
Use a consistent permission naming convention (for example, users.read, users.create, users.update, users.delete, groups.assign).
Step 10: Enforce hashing and secret-safe logging (required)
Do not store plain passwords. Use bcrypt/bcryptjs (for example, per setup-bcryptjs-nestjs skill) or the project's existing hashing. UserService (or a dedicated service) should hash on create/patch and verify on login.
Adaptations
Logger: Reference project may use a custom logger; use Nest LoggerService or the project's logger in AuthGuard and AuthService.
Swagger: If present, add @ApiBearerAuth('JWT-auth'), @ApiOperation, @ApiResponse on auth and protected endpoints; optional Responses(kind) decorator for common status codes. Document refresh cookie/header contract explicitly. If Swagger is not used, omit these.
Encryption package: If the reference uses a specific encryption package, prefer bcrypt/bcryptjs for new backends unless the project already has a standard.
Refresh token hashing: Prefer SHA-256/SHA-512 with per-token random value, or HMAC keyed by server secret; never store plaintext refresh tokens.
Step 11: Verification gates (required)
Build/test passes for the backend after RBAC changes.
Prisma migration and client generation succeed.
Login returns access+refresh token pair.
Refresh rotates sessions and invalidates/revokes replaced refresh sessions.
Protected routes enforce required permissions and deny insufficient scopes.
No plaintext passwords or refresh tokens are persisted or logged.
1---2name: prisma-rbac3description: Add, repair, and verify Prisma-backed RBAC (User, Group, Role, Permission) in existing NestJS backends with JWT access tokens, refresh-token session rotation, permission guards, and CRUD/assignment APIs. Use when users ask to implement or fix role-based access control, auth session rotation, permission-protected endpoints, or Prisma auth/authorization wiring.4---56# RBAC with Prisma
78Use this skill to implement or remediate Role-Based Access Control in a NestJS backend that already uses Prisma. The target model is:
910`User -> UserGroup -> Group -> GroupRole -> Role -> RolePermission -> Permission`
1112This skill assumes a synchronous API workflow and session-backed refresh token rotation.
1314## Workflow
1516### Step 1: Preflight checks (required)
1718- Prisma and PrismaService/PrismaModule are present.
19- ConfigModule is available (for `JWT_SECRET`).
20- Optional: `@nestjs/jwt`, `@nestjs/config`, bcrypt/bcryptjs (or project password hashing), Swagger, `crypto` (Node built-in) for secure random refresh tokens.
2122If any optional dependency is missing, add it or adapt (for example, use Nest `LoggerService` instead of a custom logger).
2324Stop and request the correct backend path if this is not an existing NestJS + Prisma project.
2526### Step 2: Enforce Prisma schema baseline (required)
2728Add RBAC models to `prisma/schema.prisma`. Full schema block is in [reference.md](reference.md).
2930**Models:**
3132- **User:** `id`, `username` (unique), `password`, `createdAt`, `updatedAt`; relation `userGroups UserGroup[]`.
33- **Group:** `id`, `name` (unique), `description?`, timestamps; relations `users UserGroup[]`, `groupRoles GroupRole[]`.
34- **UserGroup:** composite `@@id([userId, groupId])`; relations to User and Group with `onDelete: Cascade`; `@@map("user_groups")`.
35- **Permission:** `id`, `name` (unique), `description?`, timestamps; relation `rolePermissions RolePermission[]`.
36- **Role:** `id`, `name` (unique), `description?`, timestamps; relations `groupRoles GroupRole[]`, `rolePermissions RolePermission[]`.
37- **RolePermission:** composite `@@id([roleId, permissionId])`; relations to Role and Permission with `onDelete: Cascade`; `@@map("role_permissions")`.
38- **GroupRole:** composite `@@id([groupId, roleId])`; relations to Group and Role with `onDelete: Cascade`; `@@map("group_roles")`.
3940For refresh tokens, add a session model in Prisma (recommended for production):
4142- **AuthSession:** `id` (UUID/cuid), `userId`, `refreshTokenHash`, `expiresAt`, `revokedAt?`, `replacedById?`, `userAgent?`, `ip?`, `createdAt`, `updatedAt`.
43- Indexes: `@@index([userId])`, `@@index([expiresAt])`, `@@index([revokedAt])`, and unique `refreshTokenHash` when feasible.
4445Do not store raw refresh tokens in Prisma; store only a one-way hash.
4647After schema changes, run `npx prisma migrate dev` and `npx prisma generate`.
4849### Step 3: Enforce token/session model (required)
5051Use a two-token model:
5253- **Access token (JWT):** short TTL (5-15 minutes), includes `sub` and optional `username`; used for API authorization.
54- **Refresh token:** long TTL (7-30 days), opaque random string (recommended) or JWT with `jti`; used only at refresh endpoint.
5556Standardized refresh behavior:
57581. `POST /auth/login` returns `accessToken` and `refreshToken` and creates a DB session with hashed refresh token.
592. `POST /auth/refresh` validates refresh token, session status (`revokedAt` is null, `expiresAt` is in the future), and user existence.
603. On success, rotate refresh token:
61 - Mark old session as revoked and optionally set `replacedById`.
62 - Create new session row with new `refreshTokenHash` and new expiry.
63 - Return new `accessToken` and new `refreshToken`.
644. If a revoked or unknown refresh token is presented, treat as token reuse and revoke all active sessions for that user or device scope.
655. `POST /auth/logout` revokes current session.
666. Optional `POST /auth/logout-all` revokes all user sessions.
6768Transport standard:
6970- Prefer HttpOnly + Secure + SameSite cookie for refresh tokens.
71- Accept header/body refresh token only for non-browser clients; do not log tokens.
72- Keep access token in `Authorization: Bearer <token>`.
7374### Step 4: Wire module boundaries and DI (required)
7576Single **RbacModule** (for example, `src/libs/rbac/rbac.module.ts`):
7778- `@Global()` so services are available app-wide.
79- **Imports:** `ConfigModule`, `JwtModule.registerAsync({ useFactory: (config: ConfigService) => ({ secret: config.get('JWT_SECRET') }), inject: [ConfigService] })`. Throw if `JWT_SECRET` is missing.
80- **Providers:** AuthService, SessionService (or TokenService backed by Prisma), UserService, GroupService, RoleService, PermissionService, UserGroupsService (and optionally GroupRolesService, RolePermissionsService if mirroring full reference).
81- **Exports:** same services.
82- **Controllers:** AuthController, UserController, GroupController, RoleController, PermissionController.
8384Register RbacModule in `AppModule` imports.
8586### Step 5: Implement guard behavior (required)
8788File: `src/libs/rbac/guards/auth.guard.ts`.
89901. Extract token: `AuthContextUtils.getTokenFromHeader(request)` from `Authorization: Bearer <token>`. If missing, throw `UnauthorizedException`.
912. Validate: `authService.isValidToken(token)`. If false, throw Unauthorized.
923. Session check is optional for access tokens if stateless-only, recommended if session-aware access revocation is required:
93 - If session-aware: include a `sid` claim in access token and verify active session exists in DB/cache.
94 - If missing or revoked, throw Unauthorized (for example, "Session not found or revoked").
954. Attach token to request (for example, `(request as AuthenticatedRequest).authToken = token`).
965. **Permission check:** Read `permissions` metadata from handler and class: `reflector.get<string[]>('permissions', context.getHandler())` then class. If `requiredPermissions.length > 0`:
97 - Decode JWT to get `sub` (userId).
98 - Run Prisma: count permissions where `name` is in `requiredPermissions` and the permission is reachable via: User (userId) -> UserGroup -> Group -> GroupRole -> Role -> RolePermission -> Permission. Use a single `prisma.permission.count({ where: { name: { in: requiredPermissions }, rolePermissions: { some: { role: { groupRoles: { some: { group: { users: { some: { userId } } } } } } } } } })`.
99 - If count < requiredPermissions.length, throw Unauthorized ("Insufficient permissions").
1006. Return true.
101102Inject: AuthService, SessionService (or TokenService), Reflector, JwtService, PrismaService. Use Nest LoggerService or project logger for warnings.
103104### Step 6: Implement auth/session services (required)
105106- **login(username, password):** Validate with UserService (for example, `userService.isValidUser`), fetch user, generate short-lived access JWT (payload: `{ sub: user.id, username, sid }`), generate refresh token, hash refresh token, persist session, return `{ accessToken, refreshToken }`. Throw Unauthorized on invalid credentials.
107- **refresh(refreshToken):** Hash and lookup session; verify not revoked and not expired; rotate session and refresh token; return new token pair.
108- **logout(refreshToken | sid):** Revoke current session.
109- **logoutAll(userId):** Revoke all active sessions for user (optional but recommended).
110- **generateToken(payload):** Use JwtService with secret from ConfigService.
111- **isValidToken(token):** JwtService.verifyAsync with secret; ensure payload has `sub` and (if used) `username`. Return boolean.
112113## SessionService (or TokenService)
114115Prefer Prisma-backed session store over in-memory for production.
116117- **createSession(input):** Persist hashed refresh token, expiry, metadata.
118- **findActiveByRefreshTokenHash(hash):** Return matching active session.
119- **rotateSession(sessionId, newHash, newExpiry):** Revoke old + create successor session atomically.
120- **revokeSession(sessionId):** Mark revoked.
121- **revokeAllForUser(userId):** Revoke all active sessions for user.
122- **cleanupExpired():** Optional scheduled cleanup.
123124If using Redis instead of Prisma, keep the same service contract and rotation semantics.
125126### Step 7: Add auth context helpers and interfaces (required)
127128- **AuthContextUtils** (for example, `src/libs/rbac/utils/auth-context.utils.ts`): Parse `Authorization` header; static `getTokenFromHeader(request)` returns token when scheme is `Bearer`, else undefined.
129- **SessionInterface** (for example, `src/libs/rbac/interfaces/session.interface.ts`): include `id`, `userId`, `refreshTokenHash`, `expiresAt`, `revokedAt?`, `replacedById?`.
130131### Step 8: Protect routes with permissions metadata (required)
132133- **Guard + permissions:** `@UseGuards(AuthGuard)` and `@SetMetadata('permissions', ['resource.action'])` (for example, `['users.read']`, `['users.create']`). Guard requires user to have **all** listed permissions.
134- **Composite (optional):** When Swagger is used, define `Authentication()` = `applyDecorators(UseGuards(AuthGuard), ApiBearerAuth('JWT-auth'), HttpCode(HttpStatus.OK))` and use `@Authentication()` on protected routes.
135136### Step 9: Align DTOs and controllers (required)
137138**DTOs:** Create/patch/get/delete per entity. Examples:
139140- CreateUserDto: username, password (and validation).
141- GetUserDto: id, username, createdAt (no password).
142- PatchUserDto: optional username, password.
143- Replace-user-groups: body as array of group IDs; same idea for group-roles and role-permissions.
144145Use class-validator where applicable (`IsString`, `IsOptional`, `MinLength`, etc.). If Swagger is present, add `@ApiProperty` to DTOs.
146147**Controllers:**
148149- **Auth:** `POST login` (body: username, password; return token pair), `POST refresh` (read refresh token from cookie/header/body, rotate session), `POST logout` (revoke current refresh session), optional `POST logout-all`.
150- **Users:** CRUD (list, create, get by id, patch, delete), change-password, list/replace/assign/remove user groups. Protect with AuthGuard and `@SetMetadata('permissions', ['users.read'])` etc.
151- **Groups, Roles, Permissions:** CRUD plus assignment endpoints (for example, replace group roles, replace role permissions). Same guard + permissions pattern.
152153Use a consistent permission naming convention (for example, `users.read`, `users.create`, `users.update`, `users.delete`, `groups.assign`).
154155### Step 10: Enforce hashing and secret-safe logging (required)
156157Do not store plain passwords. Use bcrypt/bcryptjs (for example, per setup-bcryptjs-nestjs skill) or the project's existing hashing. UserService (or a dedicated service) should hash on create/patch and verify on login.
158159## Adaptations
160161- **Logger:** Reference project may use a custom logger; use Nest `LoggerService` or the project's logger in AuthGuard and AuthService.
162- **Swagger:** If present, add `@ApiBearerAuth('JWT-auth')`, `@ApiOperation`, `@ApiResponse` on auth and protected endpoints; optional `Responses(kind)` decorator for common status codes. Document refresh cookie/header contract explicitly. If Swagger is not used, omit these.
163- **Encryption package:** If the reference uses a specific encryption package, prefer bcrypt/bcryptjs for new backends unless the project already has a standard.
164- **Refresh token hashing:** Prefer SHA-256/SHA-512 with per-token random value, or HMAC keyed by server secret; never store plaintext refresh tokens.
165166### Step 11: Verification gates (required)
167168- Build/test passes for the backend after RBAC changes.
169- Prisma migration and client generation succeed.
170- Login returns access+refresh token pair.
171- Refresh rotates sessions and invalidates/revokes replaced refresh sessions.
172- Protected routes enforce required permissions and deny insufficient scopes.
173- No plaintext passwords or refresh tokens are persisted or logged.
174175## Checklist
176177- [ ] Prisma schema has User, Group, Role, Permission, UserGroup, GroupRole, RolePermission; migrations run.
178- [ ] AuthSession (or equivalent session store) exists for hashed refresh tokens and rotation.
179- [ ] JWT_SECRET in env; JwtModule registered in RbacModule.
180- [ ] Access token TTL and refresh token TTL configured (env-driven).
181- [ ] AuthService, SessionService (or TokenService), UserService, GroupService, RoleService, PermissionService, UserGroupsService (and optional GroupRolesService, RolePermissionsService) implemented.
182- [ ] AuthGuard: token extraction, validation, permission count via Prisma, and optional session-aware access-token revocation.
183- [ ] Refresh endpoint rotates refresh token on every use and handles reuse detection.
184- [ ] AuthContextUtils.getTokenFromHeader and SessionInterface in place.
185- [ ] AuthController (login, refresh, logout, optional logout-all); User, Group, Role, Permission controllers with CRUD and assignments; routes protected with AuthGuard and SetMetadata('permissions', [...]).
186- [ ] Passwords hashed; no plain-text storage.
187- [ ] Refresh tokens are hashed, never logged, and delivered via secure transport.
188189For full schema details, see [reference.md](reference.md).
190191## Official References
1921931. NestJS Authentication: https://docs.nestjs.com/security/authentication
1942. NestJS Authorization: https://docs.nestjs.com/security/authorization
1953. NestJS Guards: https://docs.nestjs.com/guards
1964. Prisma Data Model: https://www.prisma.io/docs/orm/prisma-schema/data-model/models
1975. Prisma Migrate: https://www.prisma.io/docs/orm/prisma-migrate
198
Run npx skillmds@latest add majiayu000/prisma-rbac in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Add, repair, and verify Prisma-backed RBAC (User, Group, Role, Permission) in existing NestJS backends with JWT access tokens, refresh-token session rotation, permission guards, and CRUD/assignment APIs. Use when users ask to implement or fix role-based access control, auth session rotation, permission-protected endpoints, or Prisma auth/authorization wiring. It is listed under Security on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Independent scanners report: SkillSpector: CAUTION, Skill Scanner: PASS. Capability flags: makes network calls, reads secrets. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
majiayu000 (@majiayu000) published this skill. Their other Agent Skills are listed on their SkillMD profile.