@oauth42/next 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __typeError = (msg) => {
9
+ throw TypeError(msg);
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -26,33 +29,121 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
29
  mod
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
33
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
34
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
35
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
29
36
 
30
37
  // src/index.ts
31
38
  var src_exports = {};
32
39
  __export(src_exports, {
40
+ OAuth42ClientAssertion: () => OAuth42ClientAssertion,
41
+ OAuth42DPoP: () => OAuth42DPoP,
33
42
  OAuth42Provider: () => OAuth42Provider,
34
43
  createAuth: () => createAuth,
35
44
  createMiddlewareConfig: () => createMiddlewareConfig,
36
45
  getOAuth42Session: () => getOAuth42Session,
37
46
  getServerSession: () => getServerSession,
47
+ pushAuthorizationURL: () => pushAuthorizationURL,
38
48
  refreshAccessToken: () => refreshAccessToken,
39
49
  withOAuth42Auth: () => withOAuth42Auth,
40
50
  withOAuth42ServerSideProps: () => withOAuth42ServerSideProps,
41
- withOAuth42Session: () => withOAuth42Session
51
+ withOAuth42Session: () => withOAuth42Session,
52
+ withPushedAuthorization: () => withPushedAuthorization
42
53
  });
43
54
  module.exports = __toCommonJS(src_exports);
44
55
 
56
+ // src/server/client-assertion.ts
57
+ var import_node_crypto = require("crypto");
58
+ var _key, _public;
59
+ var _OAuth42ClientAssertion = class _OAuth42ClientAssertion {
60
+ constructor(kid, key) {
61
+ __privateAdd(this, _key);
62
+ __privateAdd(this, _public);
63
+ if (typeof kid !== "string" || !/^[\x21-\x7e]{1,128}$/.test(kid)) throw new Error("Invalid client signing key ID");
64
+ if (key.type !== "private" || key.asymmetricKeyType !== "ec" || key.asymmetricKeyDetails?.namedCurve !== "prime256v1") {
65
+ throw new Error("Client assertions require a private P-256 key");
66
+ }
67
+ __privateSet(this, _key, key);
68
+ this.kid = kid;
69
+ const publicKey = (0, import_node_crypto.createPublicKey)(key).export({ format: "jwk" });
70
+ __privateSet(this, _public, { crv: "P-256", kty: "EC", x: publicKey.x, y: publicKey.y });
71
+ this.thumbprint = (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(__privateGet(this, _public))).digest("base64url");
72
+ }
73
+ static generate(kid) {
74
+ return new _OAuth42ClientAssertion(kid, (0, import_node_crypto.generateKeyPairSync)("ec", { namedCurve: "prime256v1" }).privateKey);
75
+ }
76
+ get publicJwk() {
77
+ return { ...__privateGet(this, _public), kid: this.kid };
78
+ }
79
+ /** Private keys for the server-only NextAuth/openid-client configuration, never registration. */
80
+ get oidcJwks() {
81
+ return { keys: [{
82
+ kty: "EC",
83
+ crv: "P-256",
84
+ x: __privateGet(this, _public).x,
85
+ y: __privateGet(this, _public).y,
86
+ d: __privateGet(this, _key).export({ format: "jwk" }).d,
87
+ kid: this.kid
88
+ }] };
89
+ }
90
+ static validateEndpoint(target) {
91
+ const url = new URL(target);
92
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash || url.protocol !== "https:" && !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)) {
93
+ throw new Error("Client authentication requires HTTPS outside loopback development");
94
+ }
95
+ }
96
+ assertion(clientId, audience) {
97
+ _OAuth42ClientAssertion.validateEndpoint(audience);
98
+ if (!clientId || Buffer.byteLength(clientId) > 256) throw new Error("Invalid client identity");
99
+ const now = Math.floor(Date.now() / 1e3);
100
+ const header = { alg: "ES256", typ: "JWT", kid: this.kid };
101
+ const payload = { iss: clientId, sub: clientId, aud: audience, iat: now, exp: now + 60, jti: (0, import_node_crypto.randomBytes)(32).toString("base64url") };
102
+ const data = `${Buffer.from(JSON.stringify(header)).toString("base64url")}.${Buffer.from(JSON.stringify(payload)).toString("base64url")}`;
103
+ return `${data}.${(0, import_node_crypto.sign)("sha256", Buffer.from(data), { key: __privateGet(this, _key), dsaEncoding: "ieee-p1363" }).toString("base64url")}`;
104
+ }
105
+ async fetch(clientId, target, init, dpop) {
106
+ if (init.method?.toUpperCase() !== "POST" || !(typeof init.body === "string" || init.body instanceof URLSearchParams)) {
107
+ throw new Error("Client assertions require a form POST");
108
+ }
109
+ const prepare = () => {
110
+ const body = new URLSearchParams(init.body);
111
+ body.delete("client_secret");
112
+ body.set("client_id", clientId);
113
+ body.set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
114
+ body.set("client_assertion", this.assertion(clientId, target));
115
+ const headers = new Headers(init.headers);
116
+ headers.delete("Authorization");
117
+ headers.delete("Content-Length");
118
+ headers.delete("Transfer-Encoding");
119
+ headers.set("Content-Type", "application/x-www-form-urlencoded");
120
+ return { ...init, body, headers, redirect: "error", cache: "no-store", signal: init.signal || AbortSignal.timeout(3e4) };
121
+ };
122
+ return dpop ? dpop.fetch(target, init, void 0, prepare) : fetch(target, prepare());
123
+ }
124
+ };
125
+ _key = new WeakMap();
126
+ _public = new WeakMap();
127
+ var OAuth42ClientAssertion = _OAuth42ClientAssertion;
128
+
45
129
  // src/provider.ts
46
130
  function OAuth42Provider(options) {
47
131
  const issuer = options.issuer || process.env.OAUTH42_ISSUER || "https://api.oauth42.com";
48
132
  const baseUrl = issuer.replace(/\/$/, "");
133
+ const { dpop, clientAssertionKey, ...providerOptions } = options;
134
+ if (clientAssertionKey) {
135
+ OAuth42ClientAssertion.validateEndpoint(`${baseUrl}/oauth2/token`);
136
+ providerOptions.clientSecret = void 0;
137
+ }
138
+ if (options.pkceEnabled === false) throw new Error("OAuth42 requires S256 PKCE");
49
139
  return {
50
140
  id: "oauth42",
51
141
  name: "OAuth42",
52
142
  type: "oauth",
53
143
  version: "2.0",
54
- // Use OIDC discovery to automatically find endpoints
55
- wellKnown: `${baseUrl}/.well-known/openid-configuration`,
144
+ // Pin O42 metadata: NextAuth v4 discovery does not compare the returned issuer.
145
+ // Constructing its OIDC client from these trusted endpoints preserves issuer binding.
146
+ jwks_endpoint: `${baseUrl}/.well-known/jwks.json`,
56
147
  // Use ID token for user profile (standard OIDC pattern)
57
148
  // This avoids an extra HTTP call to /userinfo on every auth flow
58
149
  // Apps can call /userinfo separately when they need extended claims (groups, company_id, etc.)
@@ -65,17 +156,26 @@ function OAuth42Provider(options) {
65
156
  response_type: "code"
66
157
  }
67
158
  },
68
- token: `${baseUrl}/oauth2/token`,
159
+ token: dpop ? {
160
+ url: `${baseUrl}/oauth2/token`,
161
+ async request({ client, provider, params, checks }) {
162
+ const tokens = await client.callback(provider.callbackUrl, params, checks, { DPoP: dpop.oidcKey });
163
+ dpop.assertTokenResponse(tokens);
164
+ return { tokens };
165
+ }
166
+ } : `${baseUrl}/oauth2/token`,
69
167
  userinfo: `${baseUrl}/oauth2/userinfo`,
168
+ ...clientAssertionKey ? { jwks: clientAssertionKey.oidcJwks } : {},
70
169
  client: {
71
170
  id: options.clientId,
72
- secret: options.clientSecret,
73
- token_endpoint_auth_method: "client_secret_post",
171
+ secret: clientAssertionKey ? void 0 : options.clientSecret,
172
+ token_endpoint_auth_method: clientAssertionKey ? "private_key_jwt" : "client_secret_post",
173
+ ...clientAssertionKey ? { token_endpoint_auth_signing_alg: "ES256" } : {},
74
174
  id_token_signed_response_alg: "RS256"
75
175
  // OAuth42 uses RS256 for ID tokens
76
176
  },
77
- issuer: baseUrl,
78
- checks: options.pkceEnabled !== false ? ["pkce", "state"] : ["state"],
177
+ issuer,
178
+ checks: ["pkce", "state", "nonce"],
79
179
  profile(profile, tokens) {
80
180
  return {
81
181
  id: profile.sub || profile.id || profile.email,
@@ -91,45 +191,127 @@ function OAuth42Provider(options) {
91
191
  bg: "#1e40af",
92
192
  text: "#ffffff"
93
193
  },
94
- options
194
+ // NextAuth v4 types require a string even when private_key_jwt sends no secret.
195
+ options: { ...providerOptions, clientSecret: providerOptions.clientSecret || "" }
95
196
  };
96
197
  }
97
198
 
199
+ // src/server/par.ts
200
+ async function pushAuthorizationURL(value, config) {
201
+ const base = config.issuer.replace(/\/$/, "");
202
+ let url;
203
+ try {
204
+ url = new URL(value);
205
+ } catch {
206
+ return value;
207
+ }
208
+ if (`${url.origin}${url.pathname}` !== `${base}/oauth2/authorize`) return value;
209
+ if (url.username || url.password || url.hash) throw new Error("Invalid authorization endpoint");
210
+ const body = new URLSearchParams(url.search);
211
+ if (body.getAll("client_id").length !== 1 || body.get("client_id") !== config.clientId || body.get("response_type") !== "code" || !body.get("state") || !body.get("nonce") || body.get("code_challenge_method") !== "S256" || !/^[A-Za-z0-9_-]{43}$/.test(body.get("code_challenge") || "") || ["request_uri", "client_secret", "client_assertion", "client_assertion_type"].some((k) => body.has(k))) {
212
+ throw new Error("Invalid PAR authorization transaction");
213
+ }
214
+ const target = `${base}/oauth2/par`;
215
+ OAuth42ClientAssertion.validateEndpoint(target);
216
+ if (!config.clientAssertionKey) body.set("client_secret", config.clientSecret || "");
217
+ const init = {
218
+ method: "POST",
219
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
220
+ body,
221
+ redirect: "error",
222
+ cache: "no-store",
223
+ signal: AbortSignal.timeout(3e4)
224
+ };
225
+ const response = config.clientAssertionKey ? await config.clientAssertionKey.fetch(config.clientId, target, init, config.dpop) : config.dpop ? await config.dpop.fetch(target, init) : await fetch(target, init);
226
+ if (response.status !== 201) throw new Error("Pushed authorization failed");
227
+ const pushed = await response.json();
228
+ if (typeof pushed.request_uri !== "string" || !/^urn:ietf:params:oauth:request_uri:[A-Za-z0-9_-]{43}$/.test(pushed.request_uri) || !Number.isInteger(pushed.expires_in) || pushed.expires_in <= 0 || pushed.expires_in > 600) throw new Error("Invalid PAR response");
229
+ url.search = new URLSearchParams({ client_id: config.clientId, request_uri: pushed.request_uri }).toString();
230
+ return url.href;
231
+ }
232
+ function withPushedAuthorization(handler, config) {
233
+ return async (request, context) => {
234
+ if (!(request instanceof Request)) throw new Error("PAR handlers require Next.js App Router Request/Response");
235
+ const response = await handler(request, context);
236
+ const location = response.headers.get("Location");
237
+ if (location) {
238
+ const next = await pushAuthorizationURL(location, config);
239
+ if (next === location) return response;
240
+ const headers = new Headers(response.headers);
241
+ headers.set("Location", next);
242
+ headers.delete("content-length");
243
+ return new Response(response.body, { status: response.status, headers });
244
+ }
245
+ if (response.headers.get("content-type")?.includes("application/json")) {
246
+ const body = await response.clone().json();
247
+ if (typeof body?.url === "string") {
248
+ const next = await pushAuthorizationURL(body.url, config);
249
+ if (next !== body.url) {
250
+ const headers = new Headers(response.headers);
251
+ headers.delete("content-length");
252
+ return new Response(JSON.stringify({ ...body, url: next }), { status: response.status, headers });
253
+ }
254
+ }
255
+ }
256
+ return response;
257
+ };
258
+ }
259
+
260
+ // src/server/verified-userinfo.ts
261
+ async function verifiedUserInfo(issuer, accessToken, subject, dpop) {
262
+ if (typeof accessToken !== "string" || !accessToken || typeof subject !== "string" || !subject) return null;
263
+ try {
264
+ const target = `${issuer.replace(/\/$/, "")}/oauth2/userinfo`;
265
+ const fetcher = dpop ? (url, init) => dpop.fetch(url, init, accessToken) : fetch;
266
+ const response = await fetcher(target, {
267
+ headers: { Authorization: `Bearer ${accessToken}` },
268
+ redirect: "error",
269
+ cache: "no-store",
270
+ signal: AbortSignal.timeout(3e4)
271
+ });
272
+ if (!response.ok) return null;
273
+ const user = await response.json();
274
+ return parseVerifiedUserInfo(user, subject);
275
+ } catch {
276
+ return null;
277
+ }
278
+ }
279
+ function parseVerifiedUserInfo(value, subject) {
280
+ if (typeof subject !== "string" || !subject || !value || typeof value !== "object" || Array.isArray(value)) return null;
281
+ const user = value;
282
+ if (user.sub !== subject) return null;
283
+ if (user.groups !== void 0 && (!Array.isArray(user.groups) || user.groups.some((group) => typeof group !== "string"))) return null;
284
+ if (user.email !== void 0 && typeof user.email !== "string") return null;
285
+ if (user.email_verified !== void 0 && typeof user.email_verified !== "boolean") return null;
286
+ const profile = { sub: user.sub };
287
+ for (const field of ["email", "name", "preferred_username", "username", "given_name", "family_name", "company_id", "company_name"]) {
288
+ if (user[field] != null) {
289
+ if (typeof user[field] !== "string") return null;
290
+ profile[field] = user[field];
291
+ }
292
+ }
293
+ if (user.email_verified !== void 0) profile.email_verified = user.email_verified;
294
+ if (user.groups !== void 0) profile.groups = user.groups;
295
+ return profile;
296
+ }
297
+ async function verifiedBackendSessionUserInfo(issuer, session, subject, dpop) {
298
+ if (typeof session.access_token !== "string" || !session.access_token) return null;
299
+ if (Object.prototype.hasOwnProperty.call(session, "user_info")) {
300
+ return parseVerifiedUserInfo(session.user_info, subject);
301
+ }
302
+ return verifiedUserInfo(issuer, session.access_token, subject, dpop);
303
+ }
304
+
98
305
  // src/server/auth.ts
99
306
  var import_next_auth2 = __toESM(require("next-auth"));
100
307
 
101
308
  // src/server/session.ts
102
309
  var import_next_auth = require("next-auth");
103
- var import_headers = require("next/headers");
104
- var REFRESHED_TOKEN_HEADER = "x-oauth42-refreshed-token";
105
310
  async function getOAuth42Session(...args) {
106
311
  const session = await (0, import_next_auth.getServerSession)(...args);
107
- if (!session) {
312
+ if (!session || session.error) {
108
313
  return null;
109
314
  }
110
- let refreshedToken = null;
111
- try {
112
- if (args.length === 1) {
113
- const headersList = await (0, import_headers.headers)();
114
- refreshedToken = headersList.get(REFRESHED_TOKEN_HEADER);
115
- } else if (args.length === 3) {
116
- const req = args[0];
117
- refreshedToken = req.headers[REFRESHED_TOKEN_HEADER] || null;
118
- }
119
- } catch {
120
- }
121
- if (refreshedToken) {
122
- const sessionToken = session.accessToken;
123
- console.log("[OAuth42 Session] Using refreshed token from middleware:", {
124
- refreshedPrefix: refreshedToken.substring(0, 20),
125
- sessionPrefix: sessionToken?.substring(0, 20),
126
- tokensMatch: refreshedToken === sessionToken
127
- });
128
- return {
129
- ...session,
130
- accessToken: refreshedToken
131
- };
132
- }
133
315
  return session;
134
316
  }
135
317
  function withOAuth42Session(handler, authOptions) {
@@ -167,66 +349,59 @@ function isBackendSessionMode(mode) {
167
349
  function sessionEndpoint(issuer) {
168
350
  return `${issuer.replace(/\/$/, "")}/api/sdk/sessions`;
169
351
  }
170
- async function createBackendSession(params) {
171
- const response = await fetch(sessionEndpoint(params.issuer), {
172
- method: "POST",
352
+ async function sessionFetch(params, target, body, credential, method = "POST") {
353
+ OAuth42ClientAssertion.validateEndpoint(target);
354
+ const prepare = () => ({
355
+ method,
356
+ redirect: "error",
357
+ cache: "no-store",
358
+ signal: AbortSignal.timeout(3e4),
173
359
  headers: { "Content-Type": "application/json" },
174
360
  body: JSON.stringify({
175
- access_token: params.accessToken,
176
- refresh_token: params.refreshToken,
177
- expires_at: params.expiresAt ?? Math.floor(Date.now() / 1e3) + 3600,
178
- id_token: params.idToken,
361
+ ...body,
179
362
  client_id: params.clientId,
180
- client_secret: params.clientSecret,
181
- issuer: params.issuer,
182
- user: params.user
363
+ ...params.clientAssertionKey ? {
364
+ client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
365
+ client_assertion: params.clientAssertionKey.assertion(params.clientId, target)
366
+ } : { client_secret: params.clientSecret }
183
367
  })
184
368
  });
185
- if (!response.ok) {
186
- throw new Error(`OAuth42 backend session create failed: ${response.status}`);
187
- }
188
- return response.json();
369
+ return params.dpop ? params.dpop.fetch(target, {}, credential, prepare) : fetch(target, prepare());
370
+ }
371
+ async function sessionResponse(response, params) {
372
+ if (!response.ok) throw new Error(`OAuth42 backend session request failed: ${response.status}`);
373
+ const session = await response.json();
374
+ if (params.dpop) params.dpop.assertTokenResponse(session);
375
+ else if (session.token_type?.toLowerCase() === "dpop") throw new Error("Backend session requires its retained proof key");
376
+ return session;
377
+ }
378
+ async function createBackendSession(params) {
379
+ return sessionResponse(await sessionFetch(params, sessionEndpoint(params.issuer), {
380
+ access_token: params.accessToken,
381
+ refresh_token: params.refreshToken,
382
+ id_token: params.idToken,
383
+ user: params.user
384
+ }, params.accessToken), params);
189
385
  }
190
386
  async function resolveBackendSession(params) {
191
- const response = await fetch(`${sessionEndpoint(params.issuer)}/resolve`, {
192
- method: "POST",
193
- headers: { "Content-Type": "application/json" },
194
- body: JSON.stringify({
195
- session_id: params.sessionId,
196
- client_id: params.clientId,
197
- client_secret: params.clientSecret,
198
- issuer: params.issuer
199
- })
200
- });
201
- if (response.status === 401 || response.status === 403 || response.status === 404) {
202
- return null;
203
- }
204
- if (!response.ok) {
205
- throw new Error(`OAuth42 backend session resolve failed: ${response.status}`);
206
- }
207
- return response.json();
387
+ const response = await sessionFetch(params, `${sessionEndpoint(params.issuer)}/resolve`, { session_id: params.sessionId }, params.sessionId);
388
+ if ([401, 403, 404].includes(response.status)) return null;
389
+ return sessionResponse(response, params);
208
390
  }
209
391
  async function revokeBackendSession(params) {
210
- await fetch(sessionEndpoint(params.issuer), {
211
- method: "DELETE",
212
- headers: { "Content-Type": "application/json" },
213
- body: JSON.stringify({
214
- session_id: params.sessionId,
215
- client_id: params.clientId,
216
- client_secret: params.clientSecret
217
- })
218
- });
392
+ const response = await sessionFetch(params, sessionEndpoint(params.issuer), { session_id: params.sessionId }, params.sessionId, "DELETE");
393
+ if (!response.ok) throw new Error(`OAuth42 backend session revoke failed: ${response.status}`);
219
394
  }
220
395
 
221
396
  // src/server/auth.ts
222
397
  var NextAuth = import_next_auth2.default.default || import_next_auth2.default;
223
- var activeRefresh = null;
398
+ var activeRefreshes = /* @__PURE__ */ new Map();
224
399
  function createAuth(options = {}) {
225
400
  const clientId = options.clientId || process.env.OAUTH42_CLIENT_ID;
226
- const clientSecret = options.clientSecret || process.env.OAUTH42_CLIENT_SECRET;
401
+ const clientSecret = options.clientAssertionKey ? "" : options.clientSecret || process.env.OAUTH42_CLIENT_SECRET;
227
402
  const issuer = options.issuer || process.env.NEXT_PUBLIC_OAUTH_ISSUER || process.env.OAUTH42_ISSUER || "https://localhost:8443";
228
403
  const useBackendSessions = isBackendSessionMode(options.sessionStore);
229
- if (!clientId || !clientSecret) {
404
+ if (!clientId || !clientSecret && !options.clientAssertionKey) {
230
405
  throw new Error(
231
406
  "OAuth42 client credentials are required. Set OAUTH42_CLIENT_ID and OAUTH42_CLIENT_SECRET environment variables or pass them in the options."
232
407
  );
@@ -235,14 +410,17 @@ function createAuth(options = {}) {
235
410
  providers: [
236
411
  OAuth42Provider({
237
412
  clientId,
238
- clientSecret,
413
+ clientSecret: clientSecret || "",
239
414
  issuer,
240
415
  scopes: options.scopes,
241
- pkceEnabled: options.pkceEnabled
416
+ pkceEnabled: options.pkceEnabled,
417
+ dpop: options.dpop,
418
+ clientAssertionKey: options.clientAssertionKey
242
419
  })
243
420
  ],
244
421
  callbacks: {
245
422
  async jwt({ token, account, profile }) {
423
+ if (options.clientAssertionKey) delete token.clientSecret;
246
424
  console.log("[OAuth42 SDK] JWT callback called", { hasAccount: !!account, hasProfile: !!profile });
247
425
  if (account) {
248
426
  if (useBackendSessions) {
@@ -251,9 +429,11 @@ function createAuth(options = {}) {
251
429
  }
252
430
  const oauth42Profile = profile;
253
431
  const backendSession = await createBackendSession({
432
+ dpop: options.dpop,
433
+ clientAssertionKey: options.clientAssertionKey,
254
434
  issuer,
255
435
  clientId,
256
- clientSecret,
436
+ clientSecret: clientSecret || "",
257
437
  accessToken: account.access_token,
258
438
  refreshToken: account.refresh_token,
259
439
  expiresAt: account.expires_at,
@@ -277,7 +457,8 @@ function createAuth(options = {}) {
277
457
  token.expiresAt = account.expires_at;
278
458
  token.idToken = account.id_token;
279
459
  token.clientId = clientId;
280
- token.clientSecret = clientSecret;
460
+ if (options.clientAssertionKey) delete token.clientSecret;
461
+ else token.clientSecret = clientSecret;
281
462
  token.issuer = issuer;
282
463
  }
283
464
  }
@@ -296,25 +477,36 @@ function createAuth(options = {}) {
296
477
  },
297
478
  async session({ session, token }) {
298
479
  console.log("[OAuth42 SDK] Session callback called", { hasToken: !!token, hasSession: !!session });
480
+ const revokedSession = () => {
481
+ session.error = "SessionRevoked";
482
+ delete session.accessToken;
483
+ delete session.idToken;
484
+ delete session.userInfo;
485
+ delete session.user;
486
+ return session;
487
+ };
488
+ if (token.error) return revokedSession();
489
+ let verified;
299
490
  if (useBackendSessions) {
300
491
  const sessionId = token.oauth42SessionId;
301
492
  if (!sessionId) {
302
- session.error = "SessionRevoked";
303
- return session;
493
+ return revokedSession();
304
494
  }
305
495
  const backendSession = await resolveBackendSession({
496
+ dpop: options.dpop,
497
+ clientAssertionKey: options.clientAssertionKey,
306
498
  issuer,
307
499
  clientId,
308
- clientSecret,
500
+ clientSecret: clientSecret || "",
309
501
  sessionId
310
502
  });
311
503
  if (!backendSession) {
312
- session.error = "SessionRevoked";
313
- return session;
504
+ return revokedSession();
314
505
  }
315
506
  session.accessToken = backendSession.access_token;
316
507
  session.idToken = backendSession.id_token;
317
508
  token.expiresAt = backendSession.expires_at;
509
+ verified = await verifiedBackendSessionUserInfo(issuer, backendSession, token.sub, options.dpop);
318
510
  if (backendSession.user && session.user) {
319
511
  session.user.email = backendSession.user.email;
320
512
  session.user.name = backendSession.user.name;
@@ -325,6 +517,23 @@ function createAuth(options = {}) {
325
517
  } else {
326
518
  session.accessToken = token.accessToken;
327
519
  session.idToken = token.idToken;
520
+ verified = await verifiedUserInfo(issuer, session.accessToken, token.sub, options.dpop);
521
+ }
522
+ if (!verified) {
523
+ return revokedSession();
524
+ }
525
+ session.userInfo = verified;
526
+ token.email = verified.email;
527
+ token.name = verified.name;
528
+ token.username = verified.preferred_username ?? verified.username;
529
+ token.emailVerified = verified.email_verified;
530
+ token.groups = verified.groups ?? [];
531
+ if (session.user) {
532
+ session.user.email = verified.email;
533
+ session.user.name = verified.name;
534
+ session.user.username = verified.preferred_username ?? verified.username;
535
+ session.user.emailVerified = verified.email_verified;
536
+ session.user.groups = verified.groups ?? [];
328
537
  }
329
538
  if (token.error) {
330
539
  session.error = token.error;
@@ -359,9 +568,11 @@ function createAuth(options = {}) {
359
568
  const sessionId = message.token?.oauth42SessionId;
360
569
  if (sessionId) {
361
570
  await revokeBackendSession({
571
+ dpop: options.dpop,
572
+ clientAssertionKey: options.clientAssertionKey,
362
573
  issuer,
363
574
  clientId,
364
- clientSecret,
575
+ clientSecret: clientSecret || "",
365
576
  sessionId
366
577
  }).catch((error) => {
367
578
  console.warn("[OAuth42 SDK] Failed to revoke backend session on signOut:", error);
@@ -448,31 +659,48 @@ function createAuth(options = {}) {
448
659
  }
449
660
  }
450
661
  };
451
- const handler = NextAuth(authOptions);
662
+ const nextAuthHandler = NextAuth(authOptions);
663
+ const handler = options.pushedAuthorization ? withPushedAuthorization(nextAuthHandler, { issuer, clientId, clientSecret, dpop: options.dpop, clientAssertionKey: options.clientAssertionKey }) : nextAuthHandler;
452
664
  return {
453
665
  auth: authOptions,
454
666
  handlers: { GET: handler, POST: handler }
455
667
  };
456
668
  }
457
669
  var getServerSession = getOAuth42Session;
458
- async function refreshAccessToken(token, clientId, clientSecret, issuer) {
459
- if (activeRefresh) {
460
- console.log("[OAuth42] Refresh already in progress, waiting...");
461
- return await activeRefresh;
670
+ async function refreshAccessToken(token, clientId, clientSecret, issuer, dpop, clientAssertionKey) {
671
+ if (typeof token.refreshToken !== "string" || !token.refreshToken) {
672
+ return { ...token, error: "RefreshAccessTokenError" };
673
+ }
674
+ const key = JSON.stringify([issuer || process.env.OAUTH42_ISSUER || "https://api.oauth42.com", clientId, clientSecret, token.sub, token.refreshToken, dpop?.thumbprint, clientAssertionKey?.thumbprint, clientAssertionKey?.kid]);
675
+ let refresh = activeRefreshes.get(key);
676
+ if (!refresh) {
677
+ refresh = doRefresh(token, clientId, clientSecret, issuer, dpop, clientAssertionKey);
678
+ activeRefreshes.set(key, refresh);
462
679
  }
463
- activeRefresh = doRefresh(token, clientId, clientSecret, issuer);
464
680
  try {
465
- return await activeRefresh;
681
+ const result = await refresh;
682
+ const updated = {
683
+ ...token,
684
+ accessToken: result.accessToken,
685
+ refreshToken: result.refreshToken,
686
+ expiresAt: result.expiresAt,
687
+ error: result.error
688
+ };
689
+ if (clientAssertionKey) delete updated.clientSecret;
690
+ return updated;
466
691
  } finally {
467
- activeRefresh = null;
692
+ if (activeRefreshes.get(key) === refresh) activeRefreshes.delete(key);
468
693
  }
469
694
  }
470
- async function doRefresh(token, clientId, clientSecret, issuer) {
695
+ async function doRefresh(token, clientId, clientSecret, issuer, dpop, clientAssertionKey) {
471
696
  try {
472
697
  const baseUrl = issuer || process.env.OAUTH42_ISSUER || "https://api.oauth42.com";
473
698
  const tokenUrl = `${baseUrl}/oauth2/token`;
474
699
  const fetchOptions = {
475
700
  method: "POST",
701
+ redirect: "error",
702
+ cache: "no-store",
703
+ signal: AbortSignal.timeout(3e4),
476
704
  headers: {
477
705
  "Content-Type": "application/x-www-form-urlencoded"
478
706
  },
@@ -483,17 +711,12 @@ async function doRefresh(token, clientId, clientSecret, issuer) {
483
711
  client_secret: clientSecret
484
712
  })
485
713
  };
486
- if (process.env.NODE_ENV !== "production" && tokenUrl.startsWith("https://")) {
487
- const https = await import("https");
488
- fetchOptions.agent = new https.Agent({
489
- rejectUnauthorized: false
490
- });
491
- }
492
- const response = await fetch(tokenUrl, fetchOptions);
714
+ const response = clientAssertionKey ? await clientAssertionKey.fetch(clientId, tokenUrl, fetchOptions, dpop) : dpop ? await dpop.fetch(tokenUrl, fetchOptions) : await fetch(tokenUrl, fetchOptions);
493
715
  const refreshedTokens = await response.json();
494
716
  if (!response.ok) {
495
717
  throw refreshedTokens;
496
718
  }
719
+ dpop?.assertTokenResponse(refreshedTokens);
497
720
  console.log("[OAuth42] Token refreshed successfully");
498
721
  return {
499
722
  ...token,
@@ -505,7 +728,7 @@ async function doRefresh(token, clientId, clientSecret, issuer) {
505
728
  error: void 0
506
729
  };
507
730
  } catch (error) {
508
- console.error("[OAuth42] Failed to refresh access token:", error);
731
+ console.error("[OAuth42] Failed to refresh access token");
509
732
  return {
510
733
  ...token,
511
734
  error: "RefreshAccessTokenError"
@@ -519,14 +742,20 @@ var import_jwt = require("next-auth/jwt");
519
742
  var ALLOWED_COOKIE_SIZE = 4096;
520
743
  var ESTIMATED_EMPTY_COOKIE_SIZE = 163;
521
744
  var CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE;
522
- var REFRESH_RACE_GRACE_SECONDS = 60;
523
745
  var DEFAULT_SESSION_MAX_AGE_SECONDS = 365 * 24 * 60 * 60;
524
746
  var pendingRefreshes = /* @__PURE__ */ new Map();
525
- function shouldPreserveSessionOnRefreshFailure(params) {
526
- if (params.error !== "invalid_grant" || !params.expiresAt) {
527
- return false;
747
+ function forwardedHeaders(req, cookieName, jwt) {
748
+ const headers = new Headers(req.headers);
749
+ headers.delete("x-oauth42-refreshed-token");
750
+ if (cookieName && jwt) {
751
+ const cookies = req.cookies.getAll().filter((cookie) => cookie.name !== cookieName && !cookie.name.startsWith(`${cookieName}.`));
752
+ if (jwt.length <= CHUNK_SIZE) cookies.push({ name: cookieName, value: jwt });
753
+ else for (let i = 0; i * CHUNK_SIZE < jwt.length; i++) {
754
+ cookies.push({ name: `${cookieName}.${i}`, value: jwt.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE) });
755
+ }
756
+ headers.set("cookie", cookies.map((cookie) => `${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`).join("; "));
528
757
  }
529
- return params.now - params.expiresAt <= REFRESH_RACE_GRACE_SECONDS;
758
+ return headers;
530
759
  }
531
760
  function resolveRefreshConfig(options, token) {
532
761
  return {
@@ -535,11 +764,15 @@ function resolveRefreshConfig(options, token) {
535
764
  issuer: options.issuer || token.issuer || process.env.OAUTH42_ISSUER || "https://localhost:8443"
536
765
  };
537
766
  }
538
- async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
767
+ async function refreshTokens(refreshToken, clientId, clientSecret, issuer, dpop, clientAssertionKey) {
539
768
  try {
540
769
  const tokenUrl = `${issuer}/oauth2/token`;
541
- const response = await fetch(tokenUrl, {
770
+ const fetcher = clientAssertionKey ? (url, init) => clientAssertionKey.fetch(clientId, url, init, dpop) : dpop ? dpop.fetch.bind(dpop) : fetch;
771
+ const response = await fetcher(tokenUrl, {
542
772
  method: "POST",
773
+ redirect: "error",
774
+ cache: "no-store",
775
+ signal: AbortSignal.timeout(3e4),
543
776
  headers: {
544
777
  "Content-Type": "application/x-www-form-urlencoded"
545
778
  },
@@ -552,9 +785,10 @@ async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
552
785
  });
553
786
  const data = await response.json();
554
787
  if (!response.ok) {
555
- console.error("[OAuth42 Middleware] Token refresh failed:", data);
788
+ console.error("[OAuth42 Middleware] Token refresh failed:", response.status);
556
789
  return { success: false, error: data.error || "refresh_failed" };
557
790
  }
791
+ dpop?.assertTokenResponse(data);
558
792
  console.log("[OAuth42 Middleware] Token refreshed successfully");
559
793
  return {
560
794
  success: true,
@@ -563,14 +797,14 @@ async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
563
797
  expiresAt: Math.floor(Date.now() / 1e3) + (data.expires_in || 3600)
564
798
  };
565
799
  } catch (error) {
566
- console.error("[OAuth42 Middleware] Token refresh error:", error);
800
+ console.error("[OAuth42 Middleware] Token refresh request failed");
567
801
  return { success: false, error: "refresh_error" };
568
802
  }
569
803
  }
570
804
  function withOAuth42Auth(options = {}) {
571
805
  const secret = process.env.NEXTAUTH_SECRET;
572
806
  if (!secret) {
573
- console.warn("[OAuth42 Middleware] NEXTAUTH_SECRET not set");
807
+ throw new Error("NEXTAUTH_SECRET is required for OAuth42 middleware");
574
808
  }
575
809
  return async function middleware(req) {
576
810
  const cookieName = options.cookiePrefix ? `${options.cookiePrefix}.session-token` : "next-auth.session-token";
@@ -581,22 +815,41 @@ function withOAuth42Auth(options = {}) {
581
815
  });
582
816
  const pathname = req.nextUrl.pathname;
583
817
  if (options.publicPaths?.some((path) => pathname.startsWith(path))) {
584
- return import_server.NextResponse.next();
818
+ return import_server.NextResponse.next({ request: { headers: forwardedHeaders(req) } });
585
819
  }
586
820
  const needsProtection = options.protectedPaths ? options.protectedPaths.some((path) => pathname.startsWith(path)) : true;
587
821
  if (!needsProtection) {
588
- return import_server.NextResponse.next();
822
+ return import_server.NextResponse.next({ request: { headers: forwardedHeaders(req) } });
589
823
  }
590
- if (!token) {
824
+ if (!token || token.error) {
591
825
  const signInUrl = options.pages?.signIn || "/auth/signin";
592
826
  const url = new URL(signInUrl, req.url);
593
827
  url.searchParams.set("callbackUrl", pathname);
594
828
  return import_server.NextResponse.redirect(url);
595
829
  }
596
830
  const refreshConfig = resolveRefreshConfig(options, token);
831
+ const denied = () => {
832
+ const url = new URL(options.pages?.signIn || "/auth/signin", req.url);
833
+ url.searchParams.set("callbackUrl", pathname);
834
+ const response = import_server.NextResponse.redirect(url);
835
+ for (const cookie of req.cookies.getAll()) {
836
+ if (cookie.name === cookieName || cookie.name.startsWith(`${cookieName}.`)) response.cookies.delete(cookie.name);
837
+ }
838
+ return response;
839
+ };
840
+ const verifyAndAuthorize = async (current, resolved) => {
841
+ const user = resolved === void 0 ? await verifiedUserInfo(refreshConfig.issuer, current.accessToken, token.sub, options.dpop) : resolved;
842
+ if (!user) return false;
843
+ current.groups = user.groups ?? [];
844
+ current.email = user.email;
845
+ current.name = user.name;
846
+ current.username = user.preferred_username ?? user.username;
847
+ current.emailVerified = user.email_verified;
848
+ return options.callbacks?.authorized ? options.callbacks.authorized({ token: current, req }) : true;
849
+ };
597
850
  if (isBackendSessionMode(options.sessionStore)) {
598
851
  const sessionId = token.oauth42SessionId;
599
- if (!sessionId || !refreshConfig.clientId || !refreshConfig.clientSecret) {
852
+ if (!sessionId || !refreshConfig.clientId || !refreshConfig.clientSecret && !options.clientAssertionKey) {
600
853
  const signInUrl = options.pages?.signIn || "/auth/signin";
601
854
  const url = new URL(signInUrl, req.url);
602
855
  url.searchParams.set("callbackUrl", pathname);
@@ -605,9 +858,11 @@ function withOAuth42Auth(options = {}) {
605
858
  return response;
606
859
  }
607
860
  const backendSession = await resolveBackendSession({
861
+ dpop: options.dpop,
862
+ clientAssertionKey: options.clientAssertionKey,
608
863
  issuer: refreshConfig.issuer,
609
864
  clientId: refreshConfig.clientId,
610
- clientSecret: refreshConfig.clientSecret,
865
+ clientSecret: refreshConfig.clientSecret || "",
611
866
  sessionId
612
867
  }).catch((error) => {
613
868
  console.error("[OAuth42 Middleware] Backend session resolve failed:", error);
@@ -621,8 +876,9 @@ function withOAuth42Auth(options = {}) {
621
876
  response.cookies.delete(cookieName);
622
877
  return response;
623
878
  }
624
- const requestHeaders = new Headers(req.headers);
625
- requestHeaders.set("x-oauth42-refreshed-token", backendSession.access_token);
879
+ const verified = await verifiedBackendSessionUserInfo(refreshConfig.issuer, backendSession, token.sub, options.dpop);
880
+ if (!await verifyAndAuthorize({ ...token, accessToken: backendSession.access_token }, verified)) return denied();
881
+ const requestHeaders = forwardedHeaders(req);
626
882
  return import_server.NextResponse.next({
627
883
  request: {
628
884
  headers: requestHeaders
@@ -638,13 +894,12 @@ function withOAuth42Auth(options = {}) {
638
894
  expiresAt,
639
895
  now,
640
896
  expiredAgo: expiresAt ? now - expiresAt : "n/a",
641
- accessTokenPrefix: token.accessToken?.substring(0, 20),
642
897
  path: pathname
643
898
  });
644
899
  }
645
- if (needsRefresh && token.refreshToken && refreshConfig.clientId && refreshConfig.clientSecret) {
646
- const userId = token.sub;
647
- let refreshPromise = pendingRefreshes.get(userId);
900
+ if (needsRefresh && token.refreshToken && refreshConfig.clientId && (refreshConfig.clientSecret || options.clientAssertionKey)) {
901
+ const grantKey = JSON.stringify([refreshConfig.issuer, refreshConfig.clientId, refreshConfig.clientSecret, token.sub, token.refreshToken, options.dpop?.thumbprint, options.clientAssertionKey?.thumbprint, options.clientAssertionKey?.kid]);
902
+ let refreshPromise = pendingRefreshes.get(grantKey);
648
903
  if (refreshPromise) {
649
904
  console.log("[OAuth42 Middleware] Waiting for in-flight refresh...");
650
905
  } else {
@@ -652,15 +907,17 @@ function withOAuth42Auth(options = {}) {
652
907
  refreshPromise = refreshTokens(
653
908
  token.refreshToken,
654
909
  refreshConfig.clientId,
655
- refreshConfig.clientSecret,
656
- refreshConfig.issuer
910
+ refreshConfig.clientSecret || "",
911
+ refreshConfig.issuer,
912
+ options.dpop,
913
+ options.clientAssertionKey
657
914
  ).finally(() => {
658
- const cleanupTimer = setTimeout(() => pendingRefreshes.delete(userId), 3e4);
915
+ const cleanupTimer = setTimeout(() => pendingRefreshes.delete(grantKey), 3e4);
659
916
  if (typeof cleanupTimer === "object" && "unref" in cleanupTimer) {
660
917
  cleanupTimer.unref();
661
918
  }
662
919
  });
663
- pendingRefreshes.set(userId, refreshPromise);
920
+ pendingRefreshes.set(grantKey, refreshPromise);
664
921
  }
665
922
  const refreshed = await refreshPromise;
666
923
  if (refreshed.success && refreshed.accessToken && refreshed.refreshToken) {
@@ -670,12 +927,13 @@ function withOAuth42Auth(options = {}) {
670
927
  refreshToken: refreshed.refreshToken,
671
928
  expiresAt: refreshed.expiresAt
672
929
  };
930
+ if (options.clientAssertionKey) delete updatedToken.clientSecret;
931
+ if (!await verifyAndAuthorize(updatedToken)) return denied();
673
932
  const newJwt = await (0, import_jwt.encode)({
674
933
  token: updatedToken,
675
934
  secret
676
935
  });
677
- const requestHeaders = new Headers(req.headers);
678
- requestHeaders.set("x-oauth42-refreshed-token", refreshed.accessToken);
936
+ const requestHeaders = forwardedHeaders(req, cookieName, newJwt);
679
937
  const response = import_server.NextResponse.next({
680
938
  request: {
681
939
  headers: requestHeaders
@@ -728,28 +986,11 @@ function withOAuth42Auth(options = {}) {
728
986
  chunked: newJwt.length > CHUNK_SIZE,
729
987
  existingBaseCooke: req.cookies.has(cookieName),
730
988
  existingChunks: existingChunks.length > 0 ? existingChunks : "none",
731
- newAccessTokenPrefix: refreshed.accessToken?.substring(0, 20),
732
989
  newExpiresAt: refreshed.expiresAt,
733
990
  cookieName
734
991
  });
735
992
  return response;
736
993
  } else {
737
- if (shouldPreserveSessionOnRefreshFailure({
738
- error: refreshed.error,
739
- expiresAt,
740
- now
741
- })) {
742
- console.warn(
743
- "[OAuth42 Middleware] Refresh failed with invalid_grant for a recently expired token; preserving session cookie for likely parallel refresh race",
744
- {
745
- expiresAt,
746
- now,
747
- expiredAgo: now - expiresAt,
748
- path: pathname
749
- }
750
- );
751
- return import_server.NextResponse.next();
752
- }
753
994
  console.log("[OAuth42 Middleware] Refresh failed, clearing session");
754
995
  const signInUrl = options.pages?.signIn || "/auth/signin";
755
996
  const url = new URL(signInUrl, req.url);
@@ -767,16 +1008,13 @@ function withOAuth42Auth(options = {}) {
767
1008
  return response;
768
1009
  }
769
1010
  }
770
- if (options.callbacks?.authorized) {
771
- const isAuthorized = await options.callbacks.authorized({ token, req });
772
- if (!isAuthorized) {
773
- const signInUrl = options.pages?.signIn || "/auth/signin";
774
- const url = new URL(signInUrl, req.url);
775
- url.searchParams.set("callbackUrl", pathname);
776
- return import_server.NextResponse.redirect(url);
777
- }
1011
+ if (!Number.isFinite(expiresAt) || now >= expiresAt || !token.accessToken) {
1012
+ const url = new URL(options.pages?.signIn || "/auth/signin", req.url);
1013
+ url.searchParams.set("callbackUrl", pathname);
1014
+ return import_server.NextResponse.redirect(url);
778
1015
  }
779
- return import_server.NextResponse.next();
1016
+ if (!await verifyAndAuthorize(token)) return denied();
1017
+ return import_server.NextResponse.next({ request: { headers: forwardedHeaders(req) } });
780
1018
  };
781
1019
  }
782
1020
  function createMiddlewareConfig(protectedPaths = ["/protected"], publicPaths = ["/auth", "/api/auth"]) {
@@ -795,16 +1033,88 @@ function createMiddlewareConfig(protectedPaths = ["/protected"], publicPaths = [
795
1033
  publicPaths
796
1034
  };
797
1035
  }
1036
+
1037
+ // src/server/dpop.ts
1038
+ var import_node_crypto2 = require("crypto");
1039
+ var OAuth42DPoP = class _OAuth42DPoP {
1040
+ constructor(key) {
1041
+ this.nonces = /* @__PURE__ */ new Map();
1042
+ if (key.type !== "private" || key.asymmetricKeyType !== "ec" || key.asymmetricKeyDetails?.namedCurve !== "prime256v1") {
1043
+ throw new Error("DPoP requires a private P-256 key");
1044
+ }
1045
+ this.key = key;
1046
+ const publicKey = (0, import_node_crypto2.createPublicKey)(key).export({ format: "jwk" });
1047
+ this.jwk = { crv: "P-256", kty: "EC", x: publicKey.x, y: publicKey.y };
1048
+ this.thumbprint = (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(this.jwk)).digest("base64url");
1049
+ }
1050
+ /** Persist keys in protected server storage; regeneration cannot recover a public refresh grant. */
1051
+ static generate() {
1052
+ return new _OAuth42DPoP((0, import_node_crypto2.generateKeyPairSync)("ec", { namedCurve: "prime256v1" }).privateKey);
1053
+ }
1054
+ /** Passed directly to openid-client so its callback keeps OIDC issuer/nonce/signature validation. */
1055
+ get oidcKey() {
1056
+ return this.key;
1057
+ }
1058
+ proof(method, target, nonce, accessToken) {
1059
+ const url = new URL(target);
1060
+ if (!["http:", "https:"].includes(url.protocol) || url.protocol !== "https:" && !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)) {
1061
+ throw new Error("DPoP requires HTTPS outside loopback development");
1062
+ }
1063
+ if (url.username || url.password) throw new Error("DPoP endpoint must not contain credentials");
1064
+ url.search = "";
1065
+ url.hash = "";
1066
+ const header = { typ: "dpop+jwt", alg: "ES256", jwk: this.jwk };
1067
+ const claims = {
1068
+ htm: method.toUpperCase(),
1069
+ htu: url.href,
1070
+ iat: Math.floor(Date.now() / 1e3),
1071
+ jti: (0, import_node_crypto2.randomBytes)(32).toString("base64url"),
1072
+ ...nonce ? { nonce } : {},
1073
+ ...accessToken !== void 0 ? { ath: (0, import_node_crypto2.createHash)("sha256").update(accessToken).digest("base64url") } : {}
1074
+ };
1075
+ const input = `${Buffer.from(JSON.stringify(header)).toString("base64url")}.${Buffer.from(JSON.stringify(claims)).toString("base64url")}`;
1076
+ return `${input}.${(0, import_node_crypto2.sign)("sha256", Buffer.from(input), { key: this.key, dsaEncoding: "ieee-p1363" }).toString("base64url")}`;
1077
+ }
1078
+ assertTokenResponse(tokens) {
1079
+ if (typeof tokens.token_type !== "string" || tokens.token_type.toLowerCase() !== "dpop") {
1080
+ throw new Error("Server did not issue the requested DPoP-bound token");
1081
+ }
1082
+ }
1083
+ async fetch(target, init = {}, accessToken, prepare) {
1084
+ if (init.body instanceof ReadableStream) throw new Error("DPoP nonce retries require a replayable request body");
1085
+ const cacheKey = `${new URL(target).origin}:${accessToken !== void 0 ? "resource" : "token"}`;
1086
+ for (let attempt = 0; attempt < 2; attempt++) {
1087
+ const request = prepare ? prepare() : init;
1088
+ const headers = new Headers(request.headers);
1089
+ headers.set("DPoP", this.proof(request.method || "GET", target, this.nonces.get(cacheKey), accessToken));
1090
+ if (accessToken !== void 0) headers.set("Authorization", `DPoP ${accessToken}`);
1091
+ const response = await fetch(target, { ...request, headers, redirect: "error", cache: "no-store", signal: request.signal || AbortSignal.timeout(3e4) });
1092
+ const nonce = response.headers.get("DPoP-Nonce");
1093
+ if (nonce && /^[\x21-\x7e]{1,128}$/.test(nonce)) {
1094
+ if (this.nonces.size >= 256) this.nonces.clear();
1095
+ this.nonces.set(cacheKey, nonce);
1096
+ } else return response;
1097
+ if (attempt || ![400, 401].includes(response.status)) return response;
1098
+ const error = await response.clone().json().catch(() => null);
1099
+ if (error?.error !== "use_dpop_nonce") return response;
1100
+ }
1101
+ throw new Error("DPoP nonce negotiation failed");
1102
+ }
1103
+ };
798
1104
  // Annotate the CommonJS export names for ESM import in node:
799
1105
  0 && (module.exports = {
1106
+ OAuth42ClientAssertion,
1107
+ OAuth42DPoP,
800
1108
  OAuth42Provider,
801
1109
  createAuth,
802
1110
  createMiddlewareConfig,
803
1111
  getOAuth42Session,
804
1112
  getServerSession,
1113
+ pushAuthorizationURL,
805
1114
  refreshAccessToken,
806
1115
  withOAuth42Auth,
807
1116
  withOAuth42ServerSideProps,
808
- withOAuth42Session
1117
+ withOAuth42Session,
1118
+ withPushedAuthorization
809
1119
  });
810
1120
  //# sourceMappingURL=index.js.map