or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

auth.mdbase-controller.mddecorators.mdindex.mdmiddleware.mdresults.mdserver.md
tile.json

auth.mddocs/

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

```