0
# Passport.js Integration
1
2
Complete Passport.js strategy for OpenID Connect authentication with Express.js applications, providing seamless integration with the Passport authentication ecosystem.
3
4
## Capabilities
5
6
### Strategy Class
7
8
Passport.js strategy implementation for OpenID Connect authentication.
9
10
```typescript { .api }
11
/**
12
* OpenID Connect Passport strategy
13
*/
14
class Strategy implements passport.Strategy {
15
/** Strategy name */
16
readonly name: string;
17
18
constructor(options: StrategyOptions, verify: VerifyFunction);
19
constructor(options: StrategyOptionsWithRequest, verify: VerifyFunctionWithRequest);
20
21
authenticate<TOptions extends AuthenticateOptions>(
22
req: express.Request,
23
options: TOptions
24
): void;
25
}
26
```
27
28
**Usage Examples:**
29
30
```typescript
31
import * as client from "openid-client";
32
import { Strategy } from "openid-client/passport";
33
import passport from "passport";
34
import express from "express";
35
36
const app = express();
37
38
// Configure OpenID Connect client
39
const config = await client.discovery(
40
new URL("https://accounts.google.com"),
41
process.env.CLIENT_ID!,
42
process.env.CLIENT_SECRET!
43
);
44
45
// Configure Passport strategy
46
passport.use(
47
"oidc",
48
new Strategy(
49
{
50
config: config,
51
callbackURL: "https://example.com/auth/callback",
52
scope: "openid email profile"
53
},
54
(tokens, done) => {
55
// Verify callback - process tokens
56
const claims = tokens.claims();
57
const user = {
58
id: claims?.sub,
59
email: claims?.email,
60
name: claims?.name,
61
accessToken: tokens.access_token,
62
refreshToken: tokens.refresh_token
63
};
64
65
return done(null, user);
66
}
67
)
68
);
69
70
// Routes
71
app.get("/auth/login", passport.authenticate("oidc"));
72
73
app.get("/auth/callback",
74
passport.authenticate("oidc", { failureRedirect: "/login" }),
75
(req, res) => {
76
res.redirect("/dashboard");
77
}
78
);
79
```
80
81
### Strategy Options
82
83
Configuration options for the Passport strategy.
84
85
```typescript { .api }
86
interface StrategyOptions extends StrategyOptionsBase {
87
/** Do not pass request to verify callback */
88
passReqToCallback?: false;
89
}
90
91
interface StrategyOptionsWithRequest extends StrategyOptionsBase {
92
/** Pass request as first argument to verify callback */
93
passReqToCallback: true;
94
}
95
96
interface StrategyOptionsBase {
97
/** OpenID Client configuration instance */
98
config: Configuration;
99
/** Strategy name (defaults to issuer hostname) */
100
name?: string;
101
/** Session key for storing state (defaults to issuer hostname) */
102
sessionKey?: string;
103
/** DPoP handle retrieval function */
104
DPoP?: getDPoPHandle;
105
/** Callback URL */
106
callbackURL?: URL | string;
107
/** OAuth 2.0 scope */
108
scope?: string;
109
/** Rich Authorization Requests */
110
authorizationDetails?: AuthorizationDetails | AuthorizationDetails[];
111
/** Resource indicators */
112
resource?: string | string[];
113
/** Use PAR (Pushed Authorization Requests) */
114
usePAR?: boolean;
115
/** Use JAR (JWT Authorization Requests) */
116
useJAR?: false | CryptoKey | PrivateKey | [CryptoKey | PrivateKey, ModifyAssertionFunction];
117
}
118
```
119
120
**Advanced Configuration Examples:**
121
122
```typescript
123
import * as client from "openid-client";
124
import { Strategy } from "openid-client/passport";
125
126
// Strategy with PAR (Pushed Authorization Requests)
127
passport.use(new Strategy(
128
{
129
config: config,
130
callbackURL: "https://example.com/auth/callback",
131
scope: "openid email profile",
132
usePAR: true // Use PAR for enhanced security
133
},
134
(tokens, done) => {
135
// Process tokens
136
return done(null, { user: tokens.claims()?.sub });
137
}
138
));
139
140
// Strategy with JAR (JWT Authorization Requests)
141
const jarKeyPair = await crypto.subtle.generateKey(
142
{ name: "ECDSA", namedCurve: "P-256" },
143
true,
144
["sign"]
145
);
146
147
passport.use(new Strategy(
148
{
149
config: config,
150
callbackURL: "https://example.com/auth/callback",
151
scope: "openid email profile",
152
useJAR: jarKeyPair.privateKey // Sign authorization requests
153
},
154
(tokens, done) => {
155
return done(null, { user: tokens.claims()?.sub });
156
}
157
));
158
159
// Strategy with DPoP support
160
passport.use(new Strategy(
161
{
162
config: config,
163
callbackURL: "https://example.com/auth/callback",
164
scope: "openid email profile",
165
DPoP: async (req) => {
166
// Return DPoP handle based on request/session
167
const dpopKeyPair = await getDPoPKeyPairFromSession(req);
168
if (dpopKeyPair) {
169
return client.getDPoPHandle(config, dpopKeyPair);
170
}
171
return undefined;
172
}
173
},
174
(tokens, done) => {
175
return done(null, { user: tokens.claims()?.sub });
176
}
177
));
178
```
179
180
### Verify Functions
181
182
Callback functions for processing authentication results.
183
184
```typescript { .api }
185
/**
186
* Standard verify function
187
* @param tokens - Token response from authorization server with helpers
188
* @param verified - Passport verification callback
189
*/
190
type VerifyFunction = (
191
tokens: TokenEndpointResponse & TokenEndpointResponseHelpers,
192
verified: passport.AuthenticateCallback
193
) => void;
194
195
/**
196
* Verify function with request object
197
* @param req - Express request object
198
* @param tokens - Token response from authorization server with helpers
199
* @param verified - Passport verification callback
200
*/
201
type VerifyFunctionWithRequest = (
202
req: express.Request,
203
tokens: TokenEndpointResponse & TokenEndpointResponseHelpers,
204
verified: passport.AuthenticateCallback
205
) => void;
206
```
207
208
**Verify Function Examples:**
209
210
```typescript
211
import * as client from "openid-client";
212
import { Strategy } from "openid-client/passport";
213
214
// Standard verify function
215
const verifyUser: VerifyFunction = (tokens, done) => {
216
const claims = tokens.claims();
217
218
if (!claims) {
219
return done(new Error("No ID token claims"));
220
}
221
222
// Find or create user in database
223
User.findOrCreate({
224
where: { sub: claims.sub },
225
defaults: {
226
email: claims.email,
227
name: claims.name,
228
picture: claims.picture
229
}
230
}).then(([user, created]) => {
231
// Store tokens for API access
232
user.accessToken = tokens.access_token;
233
user.refreshToken = tokens.refresh_token;
234
235
return done(null, user);
236
}).catch(err => {
237
return done(err);
238
});
239
};
240
241
// Verify function with request access
242
const verifyUserWithRequest: VerifyFunctionWithRequest = (req, tokens, done) => {
243
const claims = tokens.claims();
244
245
// Access request information
246
const userAgent = req.get("User-Agent");
247
const ipAddress = req.ip;
248
249
// Log authentication
250
console.log(`User ${claims?.sub} authenticated from ${ipAddress}`);
251
252
// Store additional session information
253
req.session.tokenExpiresAt = Date.now() + (tokens.expires_in || 3600) * 1000;
254
255
return done(null, {
256
id: claims?.sub,
257
email: claims?.email,
258
name: claims?.name,
259
lastLogin: new Date(),
260
userAgent,
261
ipAddress
262
});
263
};
264
265
// Use with strategy
266
passport.use(new Strategy(
267
{
268
config: config,
269
callbackURL: "https://example.com/auth/callback",
270
passReqToCallback: true
271
},
272
verifyUserWithRequest
273
));
274
```
275
276
### Authentication Options
277
278
Options for authentication requests.
279
280
```typescript { .api }
281
interface AuthenticateOptions extends passport.AuthenticateOptions {
282
/** Resource indicators (request-specific) */
283
resource?: string | string[];
284
/** Login hint for authorization request */
285
loginHint?: string;
286
/** ID Token hint for authorization request */
287
idTokenHint?: string;
288
/** Rich Authorization Requests (request-specific) */
289
authorizationDetails?: AuthorizationDetails | AuthorizationDetails[];
290
/** OpenID Connect prompt parameter */
291
prompt?: string;
292
/** OAuth 2.0 scope (request-specific) */
293
scope?: string | string[];
294
/** Callback URL (request-specific) */
295
callbackURL?: URL | string;
296
}
297
```
298
299
**Authentication Examples:**
300
301
```typescript
302
import express from "express";
303
import passport from "passport";
304
305
const app = express();
306
307
// Basic authentication
308
app.get("/auth/login", passport.authenticate("oidc"));
309
310
// Authentication with specific scope
311
app.get("/auth/admin", passport.authenticate("oidc", {
312
scope: "openid email profile admin:read admin:write"
313
}));
314
315
// Authentication with resource indicator
316
app.get("/auth/api", passport.authenticate("oidc", {
317
resource: "https://api.example.com"
318
}));
319
320
// Authentication with login hint
321
app.get("/auth/user/:email", (req, res, next) => {
322
passport.authenticate("oidc", {
323
loginHint: req.params.email
324
})(req, res, next);
325
});
326
327
// Force re-authentication
328
app.get("/auth/reauth", passport.authenticate("oidc", {
329
prompt: "login"
330
}));
331
332
// Request consent
333
app.get("/auth/consent", passport.authenticate("oidc", {
334
prompt: "consent"
335
}));
336
```
337
338
### Strategy Method Overrides
339
340
Extensible methods for customizing strategy behavior.
341
342
```typescript { .api }
343
/**
344
* Return additional authorization request parameters
345
* @param req - Express request object
346
* @param options - Authentication options
347
* @returns Additional parameters or undefined
348
*/
349
authorizationRequestParams<TOptions extends AuthenticateOptions>(
350
req: express.Request,
351
options: TOptions
352
): URLSearchParams | Record<string, string> | undefined;
353
354
/**
355
* Return additional token endpoint parameters
356
* @param req - Express request object
357
* @param options - Authentication options
358
* @returns Additional parameters or undefined
359
*/
360
authorizationCodeGrantParameters<TOptions extends AuthenticateOptions>(
361
req: express.Request,
362
options: TOptions
363
): URLSearchParams | Record<string, string> | undefined;
364
365
/**
366
* Determine current request URL
367
* @param req - Express request object
368
* @returns Current URL for callback processing
369
*/
370
currentUrl(req: express.Request): URL;
371
372
/**
373
* Determine whether to initiate authorization request
374
* @param req - Express request object
375
* @param currentUrl - Current request URL
376
* @param options - Authentication options
377
* @returns True to initiate authorization, false to process callback
378
*/
379
shouldInitiateAuthRequest<TOptions extends AuthenticateOptions>(
380
req: express.Request,
381
currentUrl: URL,
382
options: TOptions
383
): boolean;
384
```
385
386
**Custom Strategy Example:**
387
388
```typescript
389
import * as client from "openid-client";
390
import { Strategy } from "openid-client/passport";
391
392
class CustomOIDCStrategy extends Strategy {
393
// Add custom authorization parameters
394
authorizationRequestParams(req: express.Request, options: any) {
395
const params: Record<string, string> = {};
396
397
// Add custom parameters based on request
398
if (req.query.locale) {
399
params.ui_locales = req.query.locale as string;
400
}
401
402
if (req.headers["x-tenant-id"]) {
403
params.tenant_hint = req.headers["x-tenant-id"] as string;
404
}
405
406
return params;
407
}
408
409
// Add custom token endpoint parameters
410
authorizationCodeGrantParameters(req: express.Request, options: any) {
411
const params: Record<string, string> = {};
412
413
// Add tenant information to token request
414
if (req.headers["x-tenant-id"]) {
415
params.tenant_id = req.headers["x-tenant-id"] as string;
416
}
417
418
return params;
419
}
420
421
// Custom URL handling for proxies
422
currentUrl(req: express.Request): URL {
423
// Handle proxy headers
424
const protocol = req.headers["x-forwarded-proto"] || req.protocol;
425
const host = req.headers["x-forwarded-host"] || req.get("host");
426
const url = req.originalUrl || req.url;
427
428
return new URL(`${protocol}://${host}${url}`);
429
}
430
431
// Custom logic for determining auth vs callback
432
shouldInitiateAuthRequest(req: express.Request, currentUrl: URL, options: any): boolean {
433
// Custom logic - check for specific callback indicator
434
return !currentUrl.searchParams.has("code") &&
435
!currentUrl.searchParams.has("error") &&
436
req.method === "GET";
437
}
438
}
439
440
// Use custom strategy
441
passport.use("custom-oidc", new CustomOIDCStrategy(
442
{
443
config: config,
444
callbackURL: "https://example.com/auth/callback"
445
},
446
(tokens, done) => {
447
return done(null, { user: tokens.claims()?.sub });
448
}
449
));
450
```
451
452
## Complete Express.js Application Example
453
454
```typescript
455
import express from "express";
456
import session from "express-session";
457
import passport from "passport";
458
import * as client from "openid-client";
459
import { Strategy } from "openid-client/passport";
460
461
const app = express();
462
463
// Session configuration
464
app.use(session({
465
secret: process.env.SESSION_SECRET!,
466
resave: false,
467
saveUninitialized: false,
468
cookie: { secure: process.env.NODE_ENV === "production" }
469
}));
470
471
// Passport middleware
472
app.use(passport.initialize());
473
app.use(passport.session());
474
475
// Serialize/deserialize user
476
passport.serializeUser((user: any, done) => {
477
done(null, user.id);
478
});
479
480
passport.deserializeUser(async (id: string, done) => {
481
try {
482
const user = await User.findById(id);
483
done(null, user);
484
} catch (error) {
485
done(error);
486
}
487
});
488
489
// Configure OpenID Connect
490
async function setupAuth() {
491
const config = await client.discovery(
492
new URL(process.env.OIDC_ISSUER!),
493
process.env.CLIENT_ID!,
494
process.env.CLIENT_SECRET!
495
);
496
497
passport.use("oidc", new Strategy(
498
{
499
config: config,
500
callbackURL: process.env.CALLBACK_URL!,
501
scope: "openid email profile"
502
},
503
async (tokens, done) => {
504
try {
505
const claims = tokens.claims();
506
507
const [user] = await User.findOrCreate({
508
where: { sub: claims?.sub },
509
defaults: {
510
email: claims?.email,
511
name: claims?.name,
512
picture: claims?.picture
513
}
514
});
515
516
// Store tokens for API access
517
await user.update({
518
accessToken: tokens.access_token,
519
refreshToken: tokens.refresh_token,
520
tokenExpiresAt: new Date(Date.now() + (tokens.expires_in || 3600) * 1000)
521
});
522
523
return done(null, user);
524
} catch (error) {
525
return done(error);
526
}
527
}
528
));
529
}
530
531
// Authentication routes
532
app.get("/login", passport.authenticate("oidc"));
533
app.get("/auth/callback",
534
passport.authenticate("oidc", { failureRedirect: "/login?error=1" }),
535
(req, res) => {
536
res.redirect("/dashboard");
537
}
538
);
539
540
app.get("/logout", (req, res) => {
541
req.logout(() => {
542
res.redirect("/");
543
});
544
});
545
546
// Protected routes
547
function requireAuth(req: express.Request, res: express.Response, next: express.NextFunction) {
548
if (req.isAuthenticated()) {
549
return next();
550
}
551
res.redirect("/login");
552
}
553
554
app.get("/dashboard", requireAuth, (req, res) => {
555
res.json({ user: req.user });
556
});
557
558
// Start server
559
setupAuth().then(() => {
560
app.listen(3000, () => {
561
console.log("Server running on http://localhost:3000");
562
});
563
});
564
```
565
566
## DPoP Integration Helper
567
568
```typescript { .api }
569
/**
570
* Function to retrieve DPoP handle for requests
571
* @param req - Express request object
572
* @returns DPoP handle or undefined
573
*/
574
type getDPoPHandle = (
575
req: express.Request
576
) => Promise<DPoPHandle | undefined> | DPoPHandle | undefined;
577
```
578
579
**DPoP Session Management:**
580
581
```typescript
582
import * as client from "openid-client";
583
584
// Store DPoP key pair in session
585
const dpopHelper: getDPoPHandle = async (req) => {
586
if (!req.session.dpopKeyPair) {
587
// Generate new key pair for session
588
req.session.dpopKeyPair = await client.randomDPoPKeyPair();
589
}
590
591
return client.getDPoPHandle(config, req.session.dpopKeyPair);
592
};
593
594
passport.use(new Strategy(
595
{
596
config: config,
597
callbackURL: "https://example.com/auth/callback",
598
DPoP: dpopHelper
599
},
600
(tokens, done) => {
601
// Tokens will be sender-constrained if server supports DPoP
602
return done(null, { user: tokens.claims()?.sub });
603
}
604
));
605
```