-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathauth.ts
More file actions
194 lines (155 loc) · 6.09 KB
/
auth.ts
File metadata and controls
194 lines (155 loc) · 6.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import { Router } from 'express';
import { Issuer, generators, Client } from 'openid-client';
// Debug mode for verbose auth logging - OFF by default for security
// Set AUTH_DEBUG=true to enable detailed logging (includes sensitive data)
const AUTH_DEBUG = process.env.AUTH_DEBUG === 'true';
export interface AuthConfig {
oktaIssuer: string;
oktaClientId: string;
oktaClientSecret: string;
oktaRedirectUri: string;
}
// Helper function to get the OpenID Client configuration
async function getClientConfig(config: AuthConfig): Promise<Client> {
const { oktaIssuer, oktaClientId, oktaClientSecret, oktaRedirectUri } = config;
const issuer = await Issuer.discover(oktaIssuer);
const client = new issuer.Client({
client_id: oktaClientId,
client_secret: oktaClientSecret,
redirect_uris: [oktaRedirectUri],
response_types: ['code'],
});
return client;
}
export function createAuthRouter(config: AuthConfig): Router {
const { oktaIssuer, oktaClientId, oktaRedirectUri } = config;
console.log('🔐 Okta Auth Configuration:');
console.log(` Issuer: ${oktaIssuer}`);
console.log(` Client ID: ${oktaClientId}`);
console.log(` Redirect URI: ${oktaRedirectUri}`);
const router = Router();
router.get('/login', async (req, res) => {
console.log('[AUTH] Login endpoint hit');
console.log('[AUTH] Request URL:', req.url);
console.log('[AUTH] Request path:', req.path);
try {
// Get OpenID Client configuration
const client = await getClientConfig(config);
// Generate PKCE parameters using openid-client generators
const code_verifier = generators.codeVerifier();
const code_challenge = generators.codeChallenge(code_verifier);
const state = generators.state();
// Store PKCE parameters in session
req.session.pkce = { code_verifier, state };
// Build authorization URL with PKCE using openid-client
const authorizationUrl = client.authorizationUrl({
scope: 'openid profile email',
code_challenge,
code_challenge_method: 'S256',
state,
redirect_uri: oktaRedirectUri,
});
console.log('[AUTH] Generated PKCE parameters');
console.log('[AUTH] Redirecting to:', authorizationUrl);
res.redirect(authorizationUrl);
} catch (err: any) {
console.error(err.stack);
console.error('[AUTH] Login error:', err.message);
res.status(500).send('Login failed: ' + err.message);
}
});
router.get('/callback', async (req, res) => {
console.log('[AUTH] /callback route hit');
// Only log query params (contains authorization code) in debug mode
if (AUTH_DEBUG) {
console.log('[AUTH] Query params:', req.query);
}
const { code, error, error_description } = req.query;
if (error) {
console.error('[AUTH] Callback error:', error, error_description);
return res.status(400).send(`Authentication failed: ${error} - ${error_description}`);
}
if (!code) {
console.error('[AUTH] Missing authorization code');
return res.status(400).send('Missing authorization code');
}
try {
// Get OpenID Client configuration
const client = await getClientConfig(config);
console.log('[AUTH] Exchanging code for tokens...');
// Get PKCE parameters from session
const { pkce } = req.session as any;
if (!pkce || !pkce.code_verifier || !pkce.state) {
console.error('[AUTH] Missing PKCE parameters in session');
return res.status(400).send('Login session expired or invalid. Please try logging in again.');
}
console.log('[AUTH] Retrieved PKCE parameters from session');
// Build callback parameters from the request
const params = client.callbackParams(req);
// Exchange authorization code for tokens using openid-client
const tokenSet = await client.callback(
oktaRedirectUri,
params,
{
code_verifier: pkce.code_verifier,
state: pkce.state,
}
);
console.log('[AUTH] Token exchange successful');
console.log('[AUTH] Token response:', {
hasAccessToken: !!tokenSet.access_token,
hasIdToken: !!tokenSet.id_token,
});
// Get user claims before regenerating session
const claims = tokenSet.claims();
// Regenerate session to prevent session fixation attacks
req.session.regenerate((err) => {
if (err) {
console.error('[AUTH] Session regeneration failed:', err);
return res.status(500).send('Session error during authentication');
}
// Store tokens in the NEW session
(req.session as any).access_token = tokenSet.access_token;
(req.session as any).id_token = tokenSet.id_token;
// Store user claims for user-scoped operations
(req.session as any).userId = claims.sub;
(req.session as any).userEmail = claims.email;
console.log('[AUTH] Session regenerated, tokens stored for user:', claims.sub);
res.redirect('/');
});
} catch (err: any) {
console.error('[AUTH] Token exchange failed:', err.message);
console.error('[AUTH] Error details:', err);
res.status(500).send(`Authentication failed: ${err.message}`);
}
});
router.post('/logout', async (req, res) => {
console.log('[AUTH] Logout endpoint hit');
try {
// Get OpenID Client configuration
const client = await getClientConfig(config);
const idToken = (req.session as any).id_token;
// Build end session URL using openid-client with client_id parameter
const logoutUrl = client.endSessionUrl({
client_id: oktaClientId,
id_token_hint: idToken,
post_logout_redirect_uri: 'http://localhost:5001/',
});
req.session.destroy((err) => {
if (err) {
console.error('[AUTH] Session destroy error:', err);
}
console.log('[AUTH] Session destroyed');
// Only log logout URL (contains id_token_hint) in debug mode
if (AUTH_DEBUG) {
console.log('[AUTH] Redirecting to Okta logout:', logoutUrl);
}
res.redirect(logoutUrl);
});
} catch (err: any) {
console.error('[AUTH] Logout error:', err.message);
res.status(500).send('Something went wrong during logout.');
}
});
return router;
}