@orchestrator-claude/definitions 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/api-extractor.md +687 -0
- package/agents/business-rule-miner.md +754 -0
- package/agents/code-archaeologist.md +720 -0
- package/agents/docs-guardian.md +524 -0
- package/agents/implementer.md +512 -0
- package/agents/legacy-discoverer.md +583 -0
- package/agents/legacy-synthesizer.md +1101 -0
- package/agents/orchestrator.md +165 -0
- package/agents/planner.md +365 -0
- package/agents/researcher.md +447 -0
- package/agents/reviewer.md +514 -0
- package/agents/schema-extractor.md +781 -0
- package/agents/specifier.md +360 -0
- package/agents/task-generator.md +390 -0
- package/bin/orch-defs.js +2 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +172 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/DiffCommand.d.ts +13 -0
- package/dist/commands/DiffCommand.d.ts.map +1 -0
- package/dist/commands/DiffCommand.js +74 -0
- package/dist/commands/DiffCommand.js.map +1 -0
- package/dist/commands/SeedCommand.d.ts +19 -0
- package/dist/commands/SeedCommand.d.ts.map +1 -0
- package/dist/commands/SeedCommand.js +56 -0
- package/dist/commands/SeedCommand.js.map +1 -0
- package/dist/http/ApiClient.d.ts +50 -0
- package/dist/http/ApiClient.d.ts.map +1 -0
- package/dist/http/ApiClient.js +58 -0
- package/dist/http/ApiClient.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest/ManifestLoader.d.ts +34 -0
- package/dist/manifest/ManifestLoader.d.ts.map +1 -0
- package/dist/manifest/ManifestLoader.js +110 -0
- package/dist/manifest/ManifestLoader.js.map +1 -0
- package/dist/manifest/types.d.ts +59 -0
- package/dist/manifest/types.d.ts.map +1 -0
- package/dist/manifest/types.js +5 -0
- package/dist/manifest/types.js.map +1 -0
- package/dist/scripts/generate-manifest.d.ts +10 -0
- package/dist/scripts/generate-manifest.d.ts.map +1 -0
- package/dist/scripts/generate-manifest.js +114 -0
- package/dist/scripts/generate-manifest.js.map +1 -0
- package/hooks/post-agent-artifact-relay.sh +157 -0
- package/hooks/post-artifact-generate.sh +39 -0
- package/hooks/post-implement-validate.sh +139 -0
- package/hooks/post-phase-checkpoint.sh +322 -0
- package/hooks/pre-agent-invoke.sh +34 -0
- package/hooks/pre-phase-advance.sh +40 -0
- package/hooks/track-agent-invocation.sh +241 -0
- package/kb/auth-strategies.md +742 -0
- package/kb/docs-constitution.md +310 -0
- package/kb/error-handling.md +555 -0
- package/kb/rest-conventions.md +458 -0
- package/kb/validation-patterns.md +589 -0
- package/manifest.json +314 -0
- package/package.json +65 -0
- package/skills/artifact-validator/SKILL.md +226 -0
- package/skills/docs-guardian/SKILL.md +230 -0
- package/skills/kb-lookup/SKILL.md +257 -0
- package/skills/phase-gate-evaluator/SKILL.md +274 -0
- package/skills/release/SKILL.md +239 -0
- package/skills/release/release.sh +491 -0
- package/skills/smoke-test/SKILL.md +195 -0
- package/skills/workflow-status/SKILL.md +322 -0
- package/workflows/bug-fix.json +74 -0
- package/workflows/feature-development.json +88 -0
- package/workflows/legacy-analysis.json +304 -0
- package/workflows/refactoring.json +74 -0
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Authentication Strategies"
|
|
3
|
+
category: "security"
|
|
4
|
+
tier: "foundational"
|
|
5
|
+
tags: ["api", "auth", "jwt", "oauth", "security"]
|
|
6
|
+
template: "api"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Authentication Strategies
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
This document defines authentication strategies and best practices for securing REST APIs, covering JWT, OAuth 2.0, API keys, and session-based authentication.
|
|
14
|
+
|
|
15
|
+
**Project**: my-project
|
|
16
|
+
**Template**: api
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. Authentication vs Authorization
|
|
21
|
+
|
|
22
|
+
### Authentication
|
|
23
|
+
|
|
24
|
+
**Who are you?** - Verifying user identity
|
|
25
|
+
|
|
26
|
+
- Login with credentials
|
|
27
|
+
- Token validation
|
|
28
|
+
- Session management
|
|
29
|
+
|
|
30
|
+
### Authorization
|
|
31
|
+
|
|
32
|
+
**What can you do?** - Verifying permissions
|
|
33
|
+
|
|
34
|
+
- Role-based access control (RBAC)
|
|
35
|
+
- Permission checks
|
|
36
|
+
- Resource ownership validation
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 2. JWT (JSON Web Token) Authentication
|
|
41
|
+
|
|
42
|
+
### Overview
|
|
43
|
+
|
|
44
|
+
JWT is a stateless authentication mechanism where tokens contain user identity and claims.
|
|
45
|
+
|
|
46
|
+
**Pros:**
|
|
47
|
+
- Stateless (no server-side session storage)
|
|
48
|
+
- Scalable across multiple servers
|
|
49
|
+
- Can include custom claims
|
|
50
|
+
|
|
51
|
+
**Cons:**
|
|
52
|
+
- Cannot revoke tokens before expiration
|
|
53
|
+
- Larger payload than session IDs
|
|
54
|
+
- Vulnerable if secret key compromised
|
|
55
|
+
|
|
56
|
+
### JWT Structure
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
header.payload.signature
|
|
60
|
+
|
|
61
|
+
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
|
|
62
|
+
eyJ1c2VySWQiOiIxMjMiLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE2MTYyMzkwMjJ9.
|
|
63
|
+
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Implementation
|
|
67
|
+
|
|
68
|
+
#### Install Dependencies
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm install jsonwebtoken bcrypt
|
|
72
|
+
npm install --save-dev @types/jsonwebtoken @types/bcrypt
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
#### Generate Token
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// src/infrastructure/auth/JwtService.ts
|
|
79
|
+
import jwt from 'jsonwebtoken';
|
|
80
|
+
|
|
81
|
+
export interface TokenPayload {
|
|
82
|
+
userId: string;
|
|
83
|
+
email: string;
|
|
84
|
+
role: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export class JwtService {
|
|
88
|
+
private readonly secret: string;
|
|
89
|
+
private readonly expiresIn: string;
|
|
90
|
+
|
|
91
|
+
constructor() {
|
|
92
|
+
this.secret = process.env.JWT_SECRET!;
|
|
93
|
+
this.expiresIn = process.env.JWT_EXPIRES_IN || '24h';
|
|
94
|
+
|
|
95
|
+
if (!this.secret) {
|
|
96
|
+
throw new Error('JWT_SECRET is not defined');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
generateToken(payload: TokenPayload): string {
|
|
101
|
+
return jwt.sign(payload, this.secret, {
|
|
102
|
+
expiresIn: this.expiresIn,
|
|
103
|
+
issuer: 'api-service',
|
|
104
|
+
audience: 'api-client',
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
verifyToken(token: string): TokenPayload {
|
|
109
|
+
try {
|
|
110
|
+
return jwt.verify(token, this.secret, {
|
|
111
|
+
issuer: 'api-service',
|
|
112
|
+
audience: 'api-client',
|
|
113
|
+
}) as TokenPayload;
|
|
114
|
+
} catch (err) {
|
|
115
|
+
throw new Error('Invalid or expired token');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### Authentication Middleware
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// src/presentation/middleware/authenticate.ts
|
|
125
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
126
|
+
import { JwtService } from '../../infrastructure/auth/JwtService';
|
|
127
|
+
|
|
128
|
+
const jwtService = new JwtService();
|
|
129
|
+
|
|
130
|
+
export function authenticate(req: Request, res: Response, next: NextFunction) {
|
|
131
|
+
const authHeader = req.headers.authorization;
|
|
132
|
+
|
|
133
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
134
|
+
return res.status(401).json({
|
|
135
|
+
error: 'Unauthorized',
|
|
136
|
+
message: 'Missing or invalid authorization header',
|
|
137
|
+
timestamp: new Date().toISOString(),
|
|
138
|
+
path: req.path,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const token = authHeader.substring(7); // Remove 'Bearer ' prefix
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const payload = jwtService.verifyToken(token);
|
|
146
|
+
|
|
147
|
+
// Attach user info to request
|
|
148
|
+
req.user = {
|
|
149
|
+
id: payload.userId,
|
|
150
|
+
email: payload.email,
|
|
151
|
+
role: payload.role,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
next();
|
|
155
|
+
} catch (err) {
|
|
156
|
+
return res.status(401).json({
|
|
157
|
+
error: 'Unauthorized',
|
|
158
|
+
message: 'Invalid or expired token',
|
|
159
|
+
timestamp: new Date().toISOString(),
|
|
160
|
+
path: req.path,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### Login Endpoint
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// src/presentation/routes/auth.ts
|
|
170
|
+
import bcrypt from 'bcrypt';
|
|
171
|
+
import { JwtService } from '../../infrastructure/auth/JwtService';
|
|
172
|
+
|
|
173
|
+
const jwtService = new JwtService();
|
|
174
|
+
|
|
175
|
+
router.post('/api/v1/auth/login', async (req, res, next) => {
|
|
176
|
+
const { email, password } = req.body;
|
|
177
|
+
|
|
178
|
+
// Find user
|
|
179
|
+
const user = await userRepository.findByEmail(email);
|
|
180
|
+
if (!user) {
|
|
181
|
+
return res.status(401).json({
|
|
182
|
+
error: 'Unauthorized',
|
|
183
|
+
message: 'Invalid credentials',
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Verify password
|
|
188
|
+
const isValidPassword = await bcrypt.compare(password, user.passwordHash);
|
|
189
|
+
if (!isValidPassword) {
|
|
190
|
+
return res.status(401).json({
|
|
191
|
+
error: 'Unauthorized',
|
|
192
|
+
message: 'Invalid credentials',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Generate token
|
|
197
|
+
const token = jwtService.generateToken({
|
|
198
|
+
userId: user.id,
|
|
199
|
+
email: user.email,
|
|
200
|
+
role: user.role,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
res.json({
|
|
204
|
+
token,
|
|
205
|
+
user: {
|
|
206
|
+
id: user.id,
|
|
207
|
+
email: user.email,
|
|
208
|
+
role: user.role,
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
#### Protected Route
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import { authenticate } from '../middleware/authenticate';
|
|
218
|
+
|
|
219
|
+
router.get('/api/v1/profile', authenticate, async (req, res) => {
|
|
220
|
+
// req.user is populated by authenticate middleware
|
|
221
|
+
const user = await userRepository.findById(req.user.id);
|
|
222
|
+
res.json(user);
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 3. Refresh Tokens
|
|
229
|
+
|
|
230
|
+
### Why Refresh Tokens?
|
|
231
|
+
|
|
232
|
+
- Short-lived access tokens (15-30 min)
|
|
233
|
+
- Long-lived refresh tokens (7-30 days)
|
|
234
|
+
- Refresh tokens can be revoked
|
|
235
|
+
|
|
236
|
+
### Implementation
|
|
237
|
+
|
|
238
|
+
#### Database Schema
|
|
239
|
+
|
|
240
|
+
```sql
|
|
241
|
+
CREATE TABLE refresh_tokens (
|
|
242
|
+
id UUID PRIMARY KEY,
|
|
243
|
+
user_id UUID NOT NULL,
|
|
244
|
+
token_hash VARCHAR(255) NOT NULL,
|
|
245
|
+
expires_at TIMESTAMP NOT NULL,
|
|
246
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
247
|
+
revoked_at TIMESTAMP
|
|
248
|
+
);
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### Generate Refresh Token
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
import crypto from 'crypto';
|
|
255
|
+
|
|
256
|
+
export class RefreshTokenService {
|
|
257
|
+
async generateRefreshToken(userId: string): Promise<string> {
|
|
258
|
+
const token = crypto.randomBytes(32).toString('hex');
|
|
259
|
+
const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
|
|
260
|
+
|
|
261
|
+
// Store in database
|
|
262
|
+
await refreshTokenRepository.create({
|
|
263
|
+
userId,
|
|
264
|
+
tokenHash,
|
|
265
|
+
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
return token;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async verifyRefreshToken(token: string): Promise<string | null> {
|
|
272
|
+
const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
|
|
273
|
+
|
|
274
|
+
const refreshToken = await refreshTokenRepository.findByTokenHash(tokenHash);
|
|
275
|
+
|
|
276
|
+
if (!refreshToken || refreshToken.revokedAt || refreshToken.expiresAt < new Date()) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return refreshToken.userId;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async revokeRefreshToken(token: string): Promise<void> {
|
|
284
|
+
const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
|
|
285
|
+
await refreshTokenRepository.revoke(tokenHash);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
#### Refresh Endpoint
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
router.post('/api/v1/auth/refresh', async (req, res) => {
|
|
294
|
+
const { refreshToken } = req.body;
|
|
295
|
+
|
|
296
|
+
const userId = await refreshTokenService.verifyRefreshToken(refreshToken);
|
|
297
|
+
|
|
298
|
+
if (!userId) {
|
|
299
|
+
return res.status(401).json({
|
|
300
|
+
error: 'Unauthorized',
|
|
301
|
+
message: 'Invalid or expired refresh token',
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const user = await userRepository.findById(userId);
|
|
306
|
+
|
|
307
|
+
// Generate new access token
|
|
308
|
+
const accessToken = jwtService.generateToken({
|
|
309
|
+
userId: user.id,
|
|
310
|
+
email: user.email,
|
|
311
|
+
role: user.role,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
res.json({ accessToken });
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## 4. Role-Based Access Control (RBAC)
|
|
321
|
+
|
|
322
|
+
### Authorization Middleware
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
// src/presentation/middleware/authorize.ts
|
|
326
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
327
|
+
|
|
328
|
+
export function authorize(...allowedRoles: string[]) {
|
|
329
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
330
|
+
if (!req.user) {
|
|
331
|
+
return res.status(401).json({
|
|
332
|
+
error: 'Unauthorized',
|
|
333
|
+
message: 'Authentication required',
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (!allowedRoles.includes(req.user.role)) {
|
|
338
|
+
return res.status(403).json({
|
|
339
|
+
error: 'Forbidden',
|
|
340
|
+
message: 'Insufficient permissions',
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
next();
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Usage
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
import { authenticate } from '../middleware/authenticate';
|
|
353
|
+
import { authorize } from '../middleware/authorize';
|
|
354
|
+
|
|
355
|
+
// Only admins can delete users
|
|
356
|
+
router.delete(
|
|
357
|
+
'/api/v1/users/:id',
|
|
358
|
+
authenticate,
|
|
359
|
+
authorize('admin'),
|
|
360
|
+
async (req, res) => {
|
|
361
|
+
// Delete user logic
|
|
362
|
+
}
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
// Admins and moderators can update users
|
|
366
|
+
router.patch(
|
|
367
|
+
'/api/v1/users/:id',
|
|
368
|
+
authenticate,
|
|
369
|
+
authorize('admin', 'moderator'),
|
|
370
|
+
async (req, res) => {
|
|
371
|
+
// Update user logic
|
|
372
|
+
}
|
|
373
|
+
);
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## 5. OAuth 2.0 (Third-Party Auth)
|
|
379
|
+
|
|
380
|
+
### Overview
|
|
381
|
+
|
|
382
|
+
OAuth 2.0 allows users to authenticate using third-party providers (Google, GitHub, etc.).
|
|
383
|
+
|
|
384
|
+
### Install Passport.js
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
npm install passport passport-google-oauth20
|
|
388
|
+
npm install --save-dev @types/passport @types/passport-google-oauth20
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Configure Google OAuth
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
// src/infrastructure/auth/passport.ts
|
|
395
|
+
import passport from 'passport';
|
|
396
|
+
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
|
|
397
|
+
|
|
398
|
+
passport.use(
|
|
399
|
+
new GoogleStrategy(
|
|
400
|
+
{
|
|
401
|
+
clientID: process.env.GOOGLE_CLIENT_ID!,
|
|
402
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
403
|
+
callbackURL: '/api/v1/auth/google/callback',
|
|
404
|
+
},
|
|
405
|
+
async (accessToken, refreshToken, profile, done) => {
|
|
406
|
+
try {
|
|
407
|
+
// Find or create user
|
|
408
|
+
let user = await userRepository.findByEmail(profile.emails![0].value);
|
|
409
|
+
|
|
410
|
+
if (!user) {
|
|
411
|
+
user = await userRepository.create({
|
|
412
|
+
email: profile.emails![0].value,
|
|
413
|
+
name: profile.displayName,
|
|
414
|
+
googleId: profile.id,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
done(null, user);
|
|
419
|
+
} catch (err) {
|
|
420
|
+
done(err, undefined);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
)
|
|
424
|
+
);
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### OAuth Routes
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
import passport from 'passport';
|
|
431
|
+
|
|
432
|
+
// Redirect to Google login
|
|
433
|
+
router.get(
|
|
434
|
+
'/api/v1/auth/google',
|
|
435
|
+
passport.authenticate('google', { scope: ['profile', 'email'], session: false })
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
// Google callback
|
|
439
|
+
router.get(
|
|
440
|
+
'/api/v1/auth/google/callback',
|
|
441
|
+
passport.authenticate('google', { session: false }),
|
|
442
|
+
(req, res) => {
|
|
443
|
+
const user = req.user as User;
|
|
444
|
+
|
|
445
|
+
// Generate JWT
|
|
446
|
+
const token = jwtService.generateToken({
|
|
447
|
+
userId: user.id,
|
|
448
|
+
email: user.email,
|
|
449
|
+
role: user.role,
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// Redirect to frontend with token
|
|
453
|
+
res.redirect(`${process.env.FRONTEND_URL}/auth/callback?token=${token}`);
|
|
454
|
+
}
|
|
455
|
+
);
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
## 6. API Key Authentication
|
|
461
|
+
|
|
462
|
+
### Overview
|
|
463
|
+
|
|
464
|
+
API keys are used for server-to-server authentication or public API access.
|
|
465
|
+
|
|
466
|
+
**Use cases:**
|
|
467
|
+
- Third-party integrations
|
|
468
|
+
- Webhooks
|
|
469
|
+
- Public API access
|
|
470
|
+
|
|
471
|
+
### Generate API Key
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
import crypto from 'crypto';
|
|
475
|
+
|
|
476
|
+
export class ApiKeyService {
|
|
477
|
+
async generateApiKey(userId: string): Promise<string> {
|
|
478
|
+
const apiKey = `sk_${crypto.randomBytes(32).toString('hex')}`;
|
|
479
|
+
const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
|
|
480
|
+
|
|
481
|
+
// Store in database
|
|
482
|
+
await apiKeyRepository.create({
|
|
483
|
+
userId,
|
|
484
|
+
keyHash,
|
|
485
|
+
createdAt: new Date(),
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
return apiKey;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async verifyApiKey(apiKey: string): Promise<string | null> {
|
|
492
|
+
const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
|
|
493
|
+
const record = await apiKeyRepository.findByKeyHash(keyHash);
|
|
494
|
+
|
|
495
|
+
if (!record || record.revokedAt) {
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return record.userId;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### API Key Middleware
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
// src/presentation/middleware/authenticateApiKey.ts
|
|
508
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
509
|
+
|
|
510
|
+
export async function authenticateApiKey(
|
|
511
|
+
req: Request,
|
|
512
|
+
res: Response,
|
|
513
|
+
next: NextFunction
|
|
514
|
+
) {
|
|
515
|
+
const apiKey = req.headers['x-api-key'] as string;
|
|
516
|
+
|
|
517
|
+
if (!apiKey) {
|
|
518
|
+
return res.status(401).json({
|
|
519
|
+
error: 'Unauthorized',
|
|
520
|
+
message: 'Missing API key',
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const userId = await apiKeyService.verifyApiKey(apiKey);
|
|
525
|
+
|
|
526
|
+
if (!userId) {
|
|
527
|
+
return res.status(401).json({
|
|
528
|
+
error: 'Unauthorized',
|
|
529
|
+
message: 'Invalid API key',
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const user = await userRepository.findById(userId);
|
|
534
|
+
req.user = user;
|
|
535
|
+
|
|
536
|
+
next();
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## 7. Password Hashing
|
|
543
|
+
|
|
544
|
+
### Use bcrypt
|
|
545
|
+
|
|
546
|
+
```bash
|
|
547
|
+
npm install bcrypt
|
|
548
|
+
npm install --save-dev @types/bcrypt
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Hash Password
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
import bcrypt from 'bcrypt';
|
|
555
|
+
|
|
556
|
+
const SALT_ROUNDS = 10;
|
|
557
|
+
|
|
558
|
+
export async function hashPassword(password: string): Promise<string> {
|
|
559
|
+
return bcrypt.hash(password, SALT_ROUNDS);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
export async function verifyPassword(
|
|
563
|
+
password: string,
|
|
564
|
+
hash: string
|
|
565
|
+
): Promise<boolean> {
|
|
566
|
+
return bcrypt.compare(password, hash);
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### Usage
|
|
571
|
+
|
|
572
|
+
```typescript
|
|
573
|
+
router.post('/api/v1/auth/register', async (req, res) => {
|
|
574
|
+
const { email, password } = req.body;
|
|
575
|
+
|
|
576
|
+
// Hash password
|
|
577
|
+
const passwordHash = await hashPassword(password);
|
|
578
|
+
|
|
579
|
+
// Create user
|
|
580
|
+
const user = await userRepository.create({
|
|
581
|
+
email,
|
|
582
|
+
passwordHash,
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
res.status(201).json({ user });
|
|
586
|
+
});
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
## 8. Security Best Practices
|
|
592
|
+
|
|
593
|
+
### Environment Variables
|
|
594
|
+
|
|
595
|
+
```env
|
|
596
|
+
# JWT
|
|
597
|
+
JWT_SECRET=your-secret-key-min-32-chars
|
|
598
|
+
JWT_EXPIRES_IN=15m
|
|
599
|
+
|
|
600
|
+
# Refresh Token
|
|
601
|
+
REFRESH_TOKEN_EXPIRES_IN=30d
|
|
602
|
+
|
|
603
|
+
# OAuth
|
|
604
|
+
GOOGLE_CLIENT_ID=your-google-client-id
|
|
605
|
+
GOOGLE_CLIENT_SECRET=your-google-client-secret
|
|
606
|
+
|
|
607
|
+
# API Keys
|
|
608
|
+
API_KEY_PREFIX=sk_
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### Security Headers (Helmet)
|
|
612
|
+
|
|
613
|
+
```bash
|
|
614
|
+
npm install helmet
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
```typescript
|
|
618
|
+
import helmet from 'helmet';
|
|
619
|
+
|
|
620
|
+
app.use(helmet());
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### Rate Limiting
|
|
624
|
+
|
|
625
|
+
```typescript
|
|
626
|
+
import rateLimit from 'express-rate-limit';
|
|
627
|
+
|
|
628
|
+
const authLimiter = rateLimit({
|
|
629
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
630
|
+
max: 5, // 5 attempts
|
|
631
|
+
message: 'Too many login attempts, please try again later',
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
router.post('/api/v1/auth/login', authLimiter, async (req, res) => {
|
|
635
|
+
// Login logic
|
|
636
|
+
});
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### HTTPS Only
|
|
640
|
+
|
|
641
|
+
```typescript
|
|
642
|
+
app.use((req, res, next) => {
|
|
643
|
+
if (process.env.NODE_ENV === 'production' && !req.secure) {
|
|
644
|
+
return res.redirect(`https://${req.headers.host}${req.url}`);
|
|
645
|
+
}
|
|
646
|
+
next();
|
|
647
|
+
});
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
## 9. Testing Authentication
|
|
653
|
+
|
|
654
|
+
### Test Login
|
|
655
|
+
|
|
656
|
+
```typescript
|
|
657
|
+
import request from 'supertest';
|
|
658
|
+
import { app } from '../src/server';
|
|
659
|
+
|
|
660
|
+
describe('POST /api/v1/auth/login', () => {
|
|
661
|
+
it('should return token for valid credentials', async () => {
|
|
662
|
+
const response = await request(app)
|
|
663
|
+
.post('/api/v1/auth/login')
|
|
664
|
+
.send({
|
|
665
|
+
email: 'user@example.com',
|
|
666
|
+
password: 'SecureP@ss123',
|
|
667
|
+
})
|
|
668
|
+
.expect(200);
|
|
669
|
+
|
|
670
|
+
expect(response.body).toHaveProperty('token');
|
|
671
|
+
expect(response.body.token).toMatch(/^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/); // JWT format
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
it('should return 401 for invalid credentials', async () => {
|
|
675
|
+
await request(app)
|
|
676
|
+
.post('/api/v1/auth/login')
|
|
677
|
+
.send({
|
|
678
|
+
email: 'user@example.com',
|
|
679
|
+
password: 'wrong-password',
|
|
680
|
+
})
|
|
681
|
+
.expect(401);
|
|
682
|
+
});
|
|
683
|
+
});
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
### Test Protected Route
|
|
687
|
+
|
|
688
|
+
```typescript
|
|
689
|
+
describe('GET /api/v1/profile', () => {
|
|
690
|
+
it('should return user profile with valid token', async () => {
|
|
691
|
+
const token = generateValidToken();
|
|
692
|
+
|
|
693
|
+
const response = await request(app)
|
|
694
|
+
.get('/api/v1/profile')
|
|
695
|
+
.set('Authorization', `Bearer ${token}`)
|
|
696
|
+
.expect(200);
|
|
697
|
+
|
|
698
|
+
expect(response.body).toHaveProperty('id');
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
it('should return 401 without token', async () => {
|
|
702
|
+
await request(app).get('/api/v1/profile').expect(401);
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
it('should return 401 with invalid token', async () => {
|
|
706
|
+
await request(app)
|
|
707
|
+
.get('/api/v1/profile')
|
|
708
|
+
.set('Authorization', 'Bearer invalid-token')
|
|
709
|
+
.expect(401);
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
---
|
|
715
|
+
|
|
716
|
+
## 10. Common Pitfalls
|
|
717
|
+
|
|
718
|
+
### DON'T
|
|
719
|
+
|
|
720
|
+
1. **Store passwords in plain text** - Always hash with bcrypt
|
|
721
|
+
2. **Use weak JWT secrets** - Min 32 characters, random
|
|
722
|
+
3. **Include sensitive data in JWT** - No passwords, credit cards
|
|
723
|
+
4. **Expose JWT secret in client** - Server-side only
|
|
724
|
+
5. **Use long-lived access tokens** - Max 30 minutes
|
|
725
|
+
6. **Forget to validate token expiration** - Always check `exp` claim
|
|
726
|
+
7. **Allow password reuse** - Check against previous passwords
|
|
727
|
+
8. **Skip HTTPS in production** - Always use HTTPS
|
|
728
|
+
|
|
729
|
+
---
|
|
730
|
+
|
|
731
|
+
## References
|
|
732
|
+
|
|
733
|
+
- [JWT.io](https://jwt.io/)
|
|
734
|
+
- [OAuth 2.0 RFC](https://datatracker.ietf.org/doc/html/rfc6749)
|
|
735
|
+
- [OWASP Authentication Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html)
|
|
736
|
+
- [Passport.js Documentation](http://www.passportjs.org/)
|
|
737
|
+
|
|
738
|
+
---
|
|
739
|
+
|
|
740
|
+
**Generated for**: my-project
|
|
741
|
+
**Template**: api
|
|
742
|
+
**Constitution Preset**: orchestrator
|