Agent skill
authentication-authorization-nestjs
Comprehensive authentication and authorization patterns for Eridu Services. This skill should be used when implementing login flows, protecting endpoints, enforcing permissions, or designing API security for both backend and frontend.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/authentication-authorization-nestjs-allenlin90-eridu-services-2
Metadata
Additional technical details for this skill
- priority
- 2
- applies to
-
[ "backend", "frontend", "authentication", "authorization" ] - supersedes
-
[ "eridu-authentication-authorization", "authentication-authorization-backend", "authentication-authorization-frontend" ]
SKILL.md
Authentication & Authorization (Comprehensive)
Complete guide for authentication and authorization across Eridu Services monorepo (backend and frontend).
Table of Contents
- General Principles
- Authorization Levels
- Backend Implementation
- Frontend Implementation
- Common Mistakes
General Principles
Always Protect Sensitive Operations
🔴 Critical: Require authentication for any operation that:
- Modifies user data
- Accesses user-specific resources
- Changes permissions or roles
- Accesses client/studio-specific data
Examples:
- ✅ Public:
/health,/api/reference, login page - ✅ Authenticated:
/me/*, dashboard, user profile updates - ✅ Authorized:
/admin/*, moderation tools, financial reports
Validate on Every Access
🔴 Critical: Don't trust client-sent identifiers
- Backend validates credentials/tokens on every request
- User ID comes from validated credentials, never from URL/body
- Permissions are fetched from authoritative source (database)
- Frontend checks auth status before rendering sensitive content
Clear Error Messages (For Users)
Authentication Failures (401):
- User message: "Invalid credentials"
- Don't reveal: Whether username exists, password requirements
Authorization Failures (403):
- User message: "Access denied"
- Don't reveal: What resources exist, which permissions are missing
Token Security Standards
🔴 Critical Best Practices:
- ✅ Use industry-standard formats (JWT with RS256/EdDSA)
- ✅ Include token expiration times
- ✅ Implement refresh mechanisms
- ✅ Validate signatures using public keys
- ✅ Use HTTPS/TLS for transmission
- ✅ Store securely (HTTP-only cookies preferred)
- ❌ Never log tokens
- ❌ Never expose in URLs
- ❌ Never hardcode secrets
Authorization Levels
Public Access (No Authentication)
Use when: Information is freely available to everyone
- Health checks
- API documentation
- Login/registration pages
- Public blog posts
User Authentication (Logged In)
Use when: User must be logged in, but any logged-in user can access
- User profile (
/me) - Personal dashboard
- User-specific settings
- History/preferences
Role-Based Authorization (Admin/Special Role)
Use when: Only specific roles can access
- Admin panels (
/admin/*) - Moderation tools
- Financial reports
- User management
Resource-Level Authorization
Use when: User owns the resource or has explicit permission
- Edit own comments (not others')
- View client-specific data
- Manage team members
- Update project settings
Backend Implementation
Token Validation
🔴 Critical: Use @eridu/auth-sdk for JWT validation.
import { JwtAuthGuard } from '@/lib/auth/jwt-auth.guard';
import { CurrentUser } from '@eridu/auth-sdk/adapters/nestjs/current-user.decorator';
@Controller('me/profile')
@UseGuards(JwtAuthGuard)
export class ProfileController {
@Get()
async getProfile(@CurrentUser() user: AuthenticatedUser) {
// user.id is validated from JWT
return this.userService.getUserById(user.id);
}
}
Role-Based Authorization
🟡 Recommended: Use guards for role enforcement.
import { AdminGuard } from '@/lib/auth/admin.guard';
@Controller('admin/users')
@UseGuards(JwtAuthGuard, AdminGuard)
export class AdminUserController {
// Only system admins can access
}
Studio-Scoped Authorization
🔴 Critical: Use @StudioProtected() for studio-scoped resources.
import { StudioProtected } from '@/lib/decorators/studio-protected.decorator';
import { STUDIO_ROLE } from '@eridu/api-types/memberships';
@Controller('studios/:studioId/tasks')
@StudioProtected([STUDIO_ROLE.ADMIN, STUDIO_ROLE.MEMBER])
export class StudioTaskController {
// Only studio members can access
}
Service-to-Service Authentication
🔴 Critical: Use API keys for service-to-service calls.
import { ApiKeyGuard } from '@/lib/auth/api-key.guard';
@Controller('backdoor/users')
@UseGuards(ApiKeyGuard)
export class BackdoorUserController {
// Only services with valid API key can access
}
Frontend Implementation
Token Storage
🔴 Critical: Store tokens securely.
Preferred: HTTP-only cookies (set by backend)
// Backend sets cookie
res.cookie('access_token', token, {
httpOnly: true,
secure: true,
sameSite: 'strict'
});
Alternative: localStorage (if cookies not feasible)
// Only if HTTP-only cookies can't be used
localStorage.setItem('access_token', token);
Protected Routes
🟡 Recommended: Protect routes requiring authentication.
import { Navigate } from 'react-router-dom';
import { useAuth } from '@/hooks/useAuth';
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isLoading } = useAuth();
if (isLoading) return <LoadingSpinner />;
if (!isAuthenticated) return <Navigate to="/login" />;
return <>{children}</>;
}
User Context
🟡 Recommended: Provide user context globally.
import { createContext, useContext, useState, useEffect } from 'react';
interface AuthContext {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
login: (credentials: Credentials) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContext | undefined>(undefined);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Fetch current user on mount
fetchCurrentUser().then(setUser).finally(() => setIsLoading(false));
}, []);
const login = async (credentials: Credentials) => {
const user = await apiClient.login(credentials);
setUser(user);
};
const logout = () => {
apiClient.logout();
setUser(null);
};
return (
<AuthContext.Provider value={{ user, isAuthenticated: !!user, isLoading, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) throw new Error('useAuth must be used within AuthProvider');
return context;
}
Token Refresh
🟡 Recommended: Implement automatic token refresh.
import axios from 'axios';
const apiClient = axios.create({ baseURL: '/api' });
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
try {
// Attempt to refresh token
await apiClient.post('/auth/refresh');
// Retry original request
return apiClient(error.config);
} catch {
// Refresh failed, redirect to login
window.location.href = '/login';
}
}
return Promise.reject(error);
}
);
API Interceptors
🟡 Recommended: Attach tokens to requests automatically.
apiClient.interceptors.request.use((config) => {
const token = localStorage.getItem('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
Common Mistakes
❌ Mistake 1: Trusting user ID from request body
Problem:
// ❌ Wrong: User ID from body
@Post('update-profile')
async updateProfile(@Body() body: { userId: string; name: string }) {
return this.userService.updateUser(body.userId, { name: body.name });
}
Why it's wrong: Users can modify other users' profiles.
✅ Correct Approach:
// ✅ Right: User ID from validated token
@Post('me/profile')
async updateProfile(
@CurrentUser() user: AuthenticatedUser,
@Body() body: { name: string }
) {
return this.userService.updateUser(user.id, { name: body.name });
}
❌ Mistake 2: Storing tokens in localStorage without consideration
Problem:
// ❌ Potentially risky: Vulnerable to XSS
localStorage.setItem('access_token', token);
Why it's wrong: XSS attacks can steal tokens from localStorage.
✅ Correct Approach:
// ✅ Preferred: HTTP-only cookies (set by backend)
// Backend:
res.cookie('access_token', token, {
httpOnly: true,
secure: true,
sameSite: 'strict'
});
// Frontend: Cookie sent automatically, no JS access
❌ Mistake 3: Not checking authorization on backend
Problem:
// ❌ Wrong: Frontend-only protection
// Frontend protects route, but backend doesn't check
@Get('admin/users')
async listUsers() {
return this.userService.listUsers();
}
Why it's wrong: Attackers can bypass frontend and call API directly.
✅ Correct Approach:
// ✅ Right: Backend enforces authorization
@Get('admin/users')
@UseGuards(JwtAuthGuard, AdminGuard)
async listUsers() {
return this.userService.listUsers();
}
❌ Mistake 4: Not handling token expiration
Problem:
// ❌ Wrong: No token refresh logic
// User gets logged out abruptly when token expires
Why it's wrong: Poor user experience.
✅ Correct Approach:
// ✅ Right: Implement token refresh
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
await refreshToken();
return apiClient(error.config);
}
return Promise.reject(error);
}
);
❌ Mistake 5: Exposing sensitive information in error messages
Problem:
// ❌ Wrong: Revealing user existence
throw new UnauthorizedException('User [email protected] not found');
Why it's wrong: Helps attackers enumerate valid users.
✅ Correct Approach:
// ✅ Right: Generic error message
throw new UnauthorizedException('Invalid credentials');
Best Practices Checklist
Backend
- 🔴 Critical: Use
@eridu/auth-sdkfor JWT validation - 🔴 Critical: Validate tokens on every protected endpoint
- 🔴 Critical: User ID from token, never from request body/params
- 🔴 Critical: Use guards for role-based authorization
- 🟡 Recommended: Use
@StudioProtected()for studio-scoped resources - 🟡 Recommended: Use API keys for service-to-service auth
- 🟡 Recommended: Log authentication failures for security monitoring
- Generic error messages (don't reveal user existence)
Frontend
- 🔴 Critical: Store tokens securely (prefer HTTP-only cookies)
- 🔴 Critical: Protect sensitive routes with auth checks
- 🟡 Recommended: Provide global user context via React Context
- 🟡 Recommended: Implement automatic token refresh
- 🟡 Recommended: Use API interceptors to attach tokens
- 🟡 Recommended: Handle token expiration gracefully
- Redirect to login on 401 errors
- Clear user state on logout
Related Skills
- Backend Controller Pattern NestJS - Controller patterns for protected endpoints
- Service Pattern NestJS - Services receive authenticated user context
- Data Validation - Input validation (complement to auth)
- Erify Authorization - Role-based authorization in erify_api
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?