0
# Authentication & Authorization
1
2
Authentication and authorization system supporting custom auth providers, user principals, and role-based access control. The system integrates seamlessly with dependency injection and provides flexible authentication patterns for Express applications.
3
4
## Capabilities
5
6
### Principal Interface
7
8
Interface representing an authenticated user with support for authentication, role-based, and resource-based authorization.
9
10
```typescript { .api }
11
/**
12
* Represents an authenticated user principal with authorization capabilities
13
*/
14
interface Principal<T = unknown> {
15
/** User details/data of generic type T */
16
details: T;
17
18
/**
19
* Checks if the user is authenticated
20
* @returns Promise resolving to authentication status
21
*/
22
isAuthenticated(): Promise<boolean>;
23
24
/**
25
* Checks if the user has a specific role (role-based authorization)
26
* @param role - Role name to check
27
* @returns Promise resolving to authorization status
28
*/
29
isInRole(role: string): Promise<boolean>;
30
31
/**
32
* Checks if the user owns a specific resource (content-based authorization)
33
* @param resourceId - Resource identifier to check ownership
34
* @returns Promise resolving to ownership status
35
*/
36
isResourceOwner(resourceId: unknown): Promise<boolean>;
37
}
38
```
39
40
### Auth Provider Interface
41
42
Interface for implementing custom authentication providers that integrate with the Inversify container.
43
44
```typescript { .api }
45
/**
46
* Interface for authentication providers that resolve user principals from requests
47
*/
48
interface AuthProvider {
49
/**
50
* Extracts and authenticates user from HTTP request
51
* @param req - Express request object
52
* @param res - Express response object
53
* @param next - Express next function
54
* @returns Promise resolving to authenticated user principal
55
*/
56
getUser(req: Request, res: Response, next: NextFunction): Promise<Principal>;
57
}
58
```
59
60
### HTTP Context
61
62
HTTP context provides access to the authenticated user and request-scoped container for each request.
63
64
```typescript { .api }
65
/**
66
* HTTP context providing request-scoped access to container and user information
67
*/
68
interface HttpContext<T = unknown> {
69
/** Child container created for this specific request */
70
container: interfaces.Container;
71
/** Express request object */
72
request: Request;
73
/** Express response object */
74
response: Response;
75
/** Authenticated user principal */
76
user: Principal<T>;
77
}
78
```
79
80
**Usage Examples:**
81
82
```typescript
83
import { injectable } from "inversify";
84
import {
85
AuthProvider,
86
Principal,
87
controller,
88
httpGet,
89
principal,
90
BaseHttpController
91
} from "inversify-express-utils";
92
93
// Custom Principal Implementation
94
class UserPrincipal implements Principal<{ id: number; name: string; roles: string[] }> {
95
constructor(public details: { id: number; name: string; roles: string[] }) {}
96
97
async isAuthenticated(): Promise<boolean> {
98
return this.details.id > 0;
99
}
100
101
async isInRole(role: string): Promise<boolean> {
102
return this.details.roles.includes(role);
103
}
104
105
async isResourceOwner(resourceId: unknown): Promise<boolean> {
106
// Example: check if user owns the resource
107
if (typeof resourceId === "object" && resourceId && "userId" in resourceId) {
108
return (resourceId as any).userId === this.details.id;
109
}
110
return false;
111
}
112
}
113
114
// Custom Auth Provider Implementation
115
@injectable()
116
class JwtAuthProvider implements AuthProvider {
117
async getUser(req: Request, res: Response, next: NextFunction): Promise<Principal> {
118
const token = req.headers.authorization?.replace("Bearer ", "");
119
120
if (!token) {
121
// Return unauthenticated principal
122
return new UserPrincipal({ id: 0, name: "anonymous", roles: [] });
123
}
124
125
try {
126
// Validate JWT token (pseudo-code)
127
const decoded = jwt.verify(token, process.env.JWT_SECRET);
128
const user = await this.userService.findById(decoded.sub);
129
130
return new UserPrincipal({
131
id: user.id,
132
name: user.name,
133
roles: user.roles
134
});
135
} catch (error) {
136
// Return unauthenticated principal on invalid token
137
return new UserPrincipal({ id: 0, name: "anonymous", roles: [] });
138
}
139
}
140
}
141
142
// Using Authentication in Controllers
143
@controller("/protected")
144
class ProtectedController extends BaseHttpController {
145
@httpGet("/profile")
146
async getProfile(@principal() user: Principal) {
147
if (!(await user.isAuthenticated())) {
148
return this.json({ error: "Authentication required" }, 401);
149
}
150
151
return this.ok({
152
user: user.details,
153
authenticated: true
154
});
155
}
156
157
@httpGet("/admin")
158
async adminOnly(@principal() user: Principal) {
159
if (!(await user.isAuthenticated())) {
160
return this.json({ error: "Authentication required" }, 401);
161
}
162
163
if (!(await user.isInRole("admin"))) {
164
return this.json({ error: "Admin access required" }, 403);
165
}
166
167
return this.ok({ message: "Welcome admin!" });
168
}
169
170
@httpGet("/documents/:id")
171
async getDocument(
172
@requestParam("id") docId: string,
173
@principal() user: Principal
174
) {
175
const document = await this.documentService.findById(docId);
176
177
if (!document) {
178
return this.notFound();
179
}
180
181
// Check if user owns the document
182
if (!(await user.isResourceOwner({ userId: document.ownerId }))) {
183
return this.json({ error: "Access denied" }, 403);
184
}
185
186
return this.ok(document);
187
}
188
}
189
```
190
191
### Server Integration
192
193
Integrating authentication with the InversifyExpressServer.
194
195
```typescript { .api }
196
// Server constructor with auth provider
197
class InversifyExpressServer {
198
constructor(
199
container: interfaces.Container,
200
customRouter?: Router | null,
201
routingConfig?: RoutingConfig | null,
202
customApp?: Application | null,
203
authProvider?: (new () => AuthProvider) | null,
204
forceControllers?: boolean
205
);
206
}
207
```
208
209
**Usage Examples:**
210
211
```typescript
212
import { Container } from "inversify";
213
import { InversifyExpressServer } from "inversify-express-utils";
214
215
// Setup container and register auth provider
216
const container = new Container();
217
container.bind<AuthProvider>("AuthProvider").to(JwtAuthProvider);
218
219
// Create server with auth provider
220
const server = new InversifyExpressServer(
221
container,
222
null, // customRouter
223
null, // routingConfig
224
null, // customApp
225
JwtAuthProvider // authProvider constructor
226
);
227
228
// Configure middleware
229
server.setConfig((app) => {
230
app.use(express.json());
231
app.use(cors());
232
});
233
234
// Build and start server
235
const app = server.build();
236
app.listen(3000, () => {
237
console.log("Server started with authentication");
238
});
239
```
240
241
### Context-based Authentication
242
243
Access authentication information through HTTP context in controllers.
244
245
**Usage Examples:**
246
247
```typescript
248
@controller("/context")
249
class ContextController extends BaseHttpController {
250
@httpGet("/user-info")
251
async getUserInfo() {
252
const { user, request } = this.httpContext;
253
254
return this.ok({
255
isAuthenticated: await user.isAuthenticated(),
256
userDetails: user.details,
257
requestPath: request.path,
258
timestamp: Date.now()
259
});
260
}
261
262
@httpPost("/check-permissions")
263
async checkPermissions(@requestBody() { resource, action }: any) {
264
const { user } = this.httpContext;
265
266
if (!(await user.isAuthenticated())) {
267
return this.json({ error: "Authentication required" }, 401);
268
}
269
270
const hasPermission = await this.permissionService.checkPermission(
271
user.details,
272
resource,
273
action
274
);
275
276
if (!hasPermission) {
277
return this.json({ error: "Insufficient permissions" }, 403);
278
}
279
280
return this.ok({
281
message: "Permission granted",
282
resource,
283
action
284
});
285
}
286
}
287
```
288
289
### Anonymous Principal
290
291
Default principal implementation for unauthenticated requests when no auth provider is configured.
292
293
```typescript { .api }
294
// Default anonymous principal returned when no auth provider is set
295
const anonymousPrincipal: Principal = {
296
details: null,
297
isAuthenticated: async () => false,
298
isInRole: async (_role: string) => false,
299
isResourceOwner: async (_resourceId: unknown) => false
300
};
301
```
302
303
### Type Constants
304
305
Authentication-related type constants for dependency injection.
306
307
```typescript { .api }
308
const TYPE = {
309
/** Symbol for AuthProvider binding */
310
AuthProvider: Symbol.for('AuthProvider'),
311
/** Symbol for Controller binding */
312
Controller: Symbol.for('Controller'),
313
/** Symbol for HttpContext binding */
314
HttpContext: Symbol.for('HttpContext')
315
};
316
```