or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-authentication.mdindex.mdsession-management.mdstrategy-development.md

strategy-development.mddocs/

0

# Strategy Development

1

2

Framework for creating custom authentication strategies and understanding the strategy interface for advanced authentication scenarios. Strategies define how authentication is performed and can be customized for any authentication mechanism.

3

4

## Capabilities

5

6

### Base Strategy Class

7

8

All Passport strategies must inherit from the base Strategy class provided by the `passport-strategy` package.

9

10

```javascript { .api }

11

/**

12

* Base strategy class from passport-strategy package

13

* All custom strategies must inherit from this class

14

*/

15

const Strategy = require("passport-strategy");

16

17

class CustomStrategy extends Strategy {

18

/**

19

* Create a new strategy instance

20

* @param {Object} [options] - Strategy configuration options

21

* @param {Function} verify - Verification callback function

22

*/

23

constructor(options, verify) {

24

super();

25

this.name = "strategy-name"; // Required: unique strategy name

26

// Additional initialization

27

}

28

29

/**

30

* Main authentication method - must be implemented

31

* @param {http.IncomingMessage} req - HTTP request object

32

* @param {Object} [options] - Authentication options

33

*/

34

authenticate(req, options) {

35

// Authentication logic implementation

36

}

37

}

38

```

39

40

### Strategy Interface

41

42

Strategies have access to several action methods for communicating authentication results:

43

44

```javascript { .api }

45

/**

46

* Signal successful authentication

47

* @param {Object} user - Authenticated user object

48

* @param {Object} [info] - Additional authentication info

49

*/

50

this.success(user, info);

51

52

/**

53

* Signal authentication failure

54

* @param {string} [challenge] - Authentication challenge message

55

* @param {number} [status=401] - HTTP status code

56

*/

57

this.fail(challenge, status);

58

59

/**

60

* Redirect to external authentication provider

61

* @param {string} url - Redirect URL

62

* @param {number} [status=302] - HTTP status code

63

*/

64

this.redirect(url, status);

65

66

/**

67

* Pass without making success/failure decision

68

* Used for session restoration and optional authentication

69

*/

70

this.pass();

71

72

/**

73

* Signal internal authentication error

74

* @param {Error} err - Error object

75

*/

76

this.error(err);

77

```

78

79

**Strategy Action Method Details:**

80

81

The strategy action methods are dynamically attached during authentication and handle complex logic including flash messages, redirects, session management, and error handling:

82

83

```javascript { .api }

84

/**

85

* Success method handles authentication success with extensive option processing

86

* - Processes successFlash, successMessage, and successRedirect options

87

* - Handles user property assignment via assignProperty option

88

* - Manages session login via req.logIn() when session enabled

89

* - Transforms auth info via passport.transformAuthInfo() when authInfo enabled

90

*/

91

strategy.success = function(user, info) {

92

// Internal implementation handles:

93

// - Flash message processing

94

// - Session messages

95

// - Property assignment

96

// - Session login

97

// - Auth info transformation

98

// - Redirect handling

99

};

100

101

/**

102

* Fail method handles authentication failure with challenge accumulation

103

* - Accumulates challenges from multiple strategies

104

* - Processes failureFlash and failureMessage options

105

* - Sets WWW-Authenticate header with challenges

106

* - Handles failureRedirect or failWithError options

107

*/

108

strategy.fail = function(challenge, status) {

109

// Internal implementation handles:

110

// - Challenge accumulation across strategies

111

// - Flash message processing

112

// - HTTP header setting

113

// - Error vs redirect handling

114

};

115

116

/**

117

* Redirect method handles external redirects (OAuth flows)

118

* - Sets appropriate HTTP status code (default 302)

119

* - Performs redirect to external authentication provider

120

*/

121

strategy.redirect = function(url, status) {

122

// Internal implementation performs HTTP redirect

123

};

124

125

/**

126

* Pass method allows strategy to defer decision

127

* - Used for optional authentication scenarios

128

* - Continues middleware chain without success/failure

129

* - Common in session strategies when no session data exists

130

*/

131

strategy.pass = function() {

132

// Internal implementation continues to next middleware

133

};

134

135

/**

136

* Error method handles internal authentication errors

137

* - Bypasses normal authentication flow

138

* - Passes error to Express error handling middleware

139

*/

140

strategy.error = function(err) {

141

// Internal implementation calls next(err)

142

};

143

```

144

145

### Custom Strategy Example

146

147

Here's a complete example of a custom API key authentication strategy:

148

149

```javascript { .api }

150

const Strategy = require("passport-strategy");

151

const util = require("util");

152

153

/**

154

* Custom API Key authentication strategy

155

* @param {Object} options - Strategy options

156

* @param {string} [options.apiKeyHeader='x-api-key'] - Header name for API key

157

* @param {Function} verify - Verification function (apiKey, done) => void

158

*/

159

function ApiKeyStrategy(options, verify) {

160

if (typeof options === "function") {

161

verify = options;

162

options = {};

163

}

164

if (!verify) {

165

throw new TypeError("ApiKeyStrategy requires a verify callback");

166

}

167

168

Strategy.call(this);

169

170

this.name = "apikey";

171

this._verify = verify;

172

this._apiKeyHeader = options.apiKeyHeader || "x-api-key";

173

}

174

175

// Inherit from Strategy

176

util.inherits(ApiKeyStrategy, Strategy);

177

178

/**

179

* Authenticate request using API key

180

* @param {http.IncomingMessage} req - HTTP request

181

* @param {Object} [options] - Authentication options

182

*/

183

ApiKeyStrategy.prototype.authenticate = function(req, options) {

184

const apiKey = req.headers[this._apiKeyHeader];

185

186

if (!apiKey) {

187

return this.fail("Missing API key", 401);

188

}

189

190

const self = this;

191

192

function verified(err, user, info) {

193

if (err) return self.error(err);

194

if (!user) return self.fail(info || "Invalid API key", 401);

195

return self.success(user, info);

196

}

197

198

try {

199

this._verify(apiKey, verified);

200

} catch (ex) {

201

return this.error(ex);

202

}

203

};

204

205

module.exports = ApiKeyStrategy;

206

```

207

208

**Usage of Custom Strategy:**

209

210

```javascript

211

const ApiKeyStrategy = require("./apikey-strategy");

212

213

// Register the custom strategy

214

passport.use(new ApiKeyStrategy(

215

{ apiKeyHeader: "authorization" },

216

async (apiKey, done) => {

217

try {

218

const user = await User.findOne({ apiKey: apiKey });

219

if (!user) {

220

return done(null, false, { message: "Invalid API key" });

221

}

222

return done(null, user);

223

} catch (err) {

224

return done(err);

225

}

226

}

227

));

228

229

// Use the strategy

230

app.get("/api/data",

231

passport.authenticate("apikey", { session: false }),

232

(req, res) => {

233

res.json({ data: "protected data", user: req.user.id });

234

}

235

);

236

```

237

238

### OAuth Strategy Pattern

239

240

OAuth strategies typically involve redirection to external providers:

241

242

```javascript { .api }

243

const Strategy = require("passport-strategy");

244

const OAuth2 = require("oauth").OAuth2;

245

246

/**

247

* Custom OAuth2 strategy example

248

* @param {Object} options - OAuth2 configuration

249

* @param {Function} verify - Verification callback

250

*/

251

function CustomOAuth2Strategy(options, verify) {

252

Strategy.call(this);

253

254

this.name = "custom-oauth2";

255

this._verify = verify;

256

this._oauth2 = new OAuth2(

257

options.clientID,

258

options.clientSecret,

259

options.authorizationURL,

260

options.tokenURL

261

);

262

this._callbackURL = options.callbackURL;

263

}

264

265

util.inherits(CustomOAuth2Strategy, Strategy);

266

267

CustomOAuth2Strategy.prototype.authenticate = function(req, options) {

268

if (req.query && req.query.code) {

269

// Handle callback from OAuth provider

270

this._oauth2.getOAuthAccessToken(

271

req.query.code,

272

{ grant_type: "authorization_code" },

273

(err, accessToken, refreshToken, results) => {

274

if (err) return this.error(err);

275

276

// Get user profile with access token

277

this._oauth2.get(

278

"https://api.provider.com/user",

279

accessToken,

280

(err, body) => {

281

if (err) return this.error(err);

282

283

const profile = JSON.parse(body);

284

this._verify(accessToken, refreshToken, profile, (err, user) => {

285

if (err) return this.error(err);

286

if (!user) return this.fail();

287

return this.success(user);

288

});

289

}

290

);

291

}

292

);

293

} else {

294

// Redirect to OAuth provider

295

const authURL = this._oauth2.getAuthorizeUrl({

296

response_type: "code",

297

client_id: this._oauth2._clientId,

298

redirect_uri: this._callbackURL,

299

scope: options.scope || "read"

300

});

301

this.redirect(authURL);

302

}

303

};

304

```

305

306

### Built-in Session Strategy

307

308

Understanding the built-in SessionStrategy implementation:

309

310

```javascript { .api }

311

/**

312

* Built-in session strategy for restoring authentication from session

313

* @param {Object|Function} [options] - Strategy options or deserializeUser function if only one parameter

314

* @param {string} [options.key='passport'] - Session key

315

* @param {Function} deserializeUser - User deserialization function

316

*/

317

class SessionStrategy extends Strategy {

318

constructor(options, deserializeUser);

319

constructor(deserializeUser);

320

name: "session";

321

322

/**

323

* Authenticate based on session data

324

* @param {http.IncomingMessage} req - HTTP request

325

* @param {Object} [options] - Authentication options

326

* @param {boolean} [options.pauseStream=false] - Pause request stream

327

*/

328

authenticate(req, options);

329

}

330

```

331

332

### Strategy Testing

333

334

Example of how to test custom strategies:

335

336

```javascript

337

const chai = require("chai");

338

const expect = chai.expect;

339

const ApiKeyStrategy = require("../lib/apikey-strategy");

340

341

describe("ApiKeyStrategy", () => {

342

let strategy;

343

344

beforeEach(() => {

345

strategy = new ApiKeyStrategy((apiKey, done) => {

346

if (apiKey === "valid-key") {

347

return done(null, { id: 1, name: "Test User" });

348

}

349

return done(null, false);

350

});

351

});

352

353

it("should authenticate with valid API key", (done) => {

354

const req = {

355

headers: { "x-api-key": "valid-key" }

356

};

357

358

strategy.success = (user) => {

359

expect(user).to.deep.equal({ id: 1, name: "Test User" });

360

done();

361

};

362

363

strategy.authenticate(req);

364

});

365

366

it("should fail with invalid API key", (done) => {

367

const req = {

368

headers: { "x-api-key": "invalid-key" }

369

};

370

371

strategy.fail = (challenge, status) => {

372

expect(challenge).to.equal("Invalid API key");

373

expect(status).to.equal(401);

374

done();

375

};

376

377

strategy.authenticate(req);

378

});

379

380

it("should fail with missing API key", (done) => {

381

const req = { headers: {} };

382

383

strategy.fail = (challenge, status) => {

384

expect(challenge).to.equal("Missing API key");

385

expect(status).to.equal(401);

386

done();

387

};

388

389

strategy.authenticate(req);

390

});

391

});

392

```

393

394

## Advanced Strategy Patterns

395

396

### Multi-step Authentication

397

398

Strategies can implement multi-step authentication flows:

399

400

```javascript

401

class TwoFactorStrategy extends Strategy {

402

constructor(options, verify) {

403

super();

404

this.name = "twofactor";

405

this._verify = verify;

406

}

407

408

authenticate(req, options) {

409

const { username, password, token } = req.body;

410

411

if (!username || !password) {

412

return this.fail("Missing credentials", 400);

413

}

414

415

// First step: verify username/password

416

this._verify(username, password, (err, user) => {

417

if (err) return this.error(err);

418

if (!user) return this.fail("Invalid credentials", 401);

419

420

if (!token) {

421

// Request second factor

422

return this.fail("Two-factor token required", 200);

423

}

424

425

// Second step: verify 2FA token

426

if (this.verifyToken(user, token)) {

427

return this.success(user);

428

} else {

429

return this.fail("Invalid token", 401);

430

}

431

});

432

}

433

434

verifyToken(user, token) {

435

// Implement 2FA token verification

436

return user.twoFactorSecret &&

437

require("speakeasy").totp.verify({

438

secret: user.twoFactorSecret,

439

encoding: "base32",

440

token: token

441

});

442

}

443

}

444

```

445

446

### Conditional Authentication

447

448

Strategies can implement conditional logic:

449

450

```javascript

451

class SmartStrategy extends Strategy {

452

constructor(options, verify) {

453

super();

454

this.name = "smart";

455

this._verify = verify;

456

}

457

458

authenticate(req, options) {

459

const ipAddress = req.ip;

460

const userAgent = req.headers["user-agent"];

461

462

// Check if this is a trusted environment

463

if (this.isTrustedEnvironment(ipAddress, userAgent)) {

464

// Skip authentication for trusted environments

465

return this.pass();

466

}

467

468

// Require authentication for untrusted environments

469

const token = this.extractToken(req);

470

if (!token) {

471

return this.fail("Authentication required", 401);

472

}

473

474

this._verify(token, (err, user) => {

475

if (err) return this.error(err);

476

if (!user) return this.fail("Invalid token", 401);

477

return this.success(user);

478

});

479

}

480

481

isTrustedEnvironment(ip, userAgent) {

482

// Implement trust logic

483

return ip.startsWith("192.168.") ||

484

userAgent.includes("TrustedApp");

485

}

486

487

extractToken(req) {

488

const authHeader = req.headers.authorization;

489

if (authHeader && authHeader.startsWith("Bearer ")) {

490

return authHeader.substring(7);

491

}

492

return null;

493

}

494

}

495

```