@tdacorp/identity-client 0.2.5 → 0.2.6

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/README.md CHANGED
@@ -302,6 +302,26 @@ breaking rename (`middleware.ts` → `proxy.ts`) to keep working on Next.js
302
302
  package ships Route Handlers a consuming app mounts wherever it wants
303
303
  instead.
304
304
 
305
+ Both factories' `redirectUri` accepts a function as well as a plain string —
306
+ `(request) => string`, useful when the app is reachable at more than one
307
+ origin (a preview deployment per branch, say):
308
+
309
+ ```ts
310
+ redirectUri: (request) => `${request.nextUrl.origin}/api/auth/callback`,
311
+ ```
312
+
313
+ The resolved value must be identical on both the login and callback route's
314
+ own `redirectUri`, since the token endpoint checks it against what the
315
+ authorization request used (RFC 6749 §4.1.3) — true automatically for
316
+ `request.nextUrl.origin`, since one browser session's login and callback
317
+ requests share a domain.
318
+
319
+ `createCallbackRoute` also accepts `clientAuthMethod`, the same option
320
+ `exchangeAuthorizationCode` takes directly (see "Token exchange, refresh,
321
+ and client credentials" above) — set it to `'client_secret_post'` if your
322
+ relying party is registered on the identity platform for that method
323
+ instead of the default `client_secret_basic`.
324
+
305
325
  ## Limitations in v0.1
306
326
 
307
327
  - **No built-in fallback for an `indeterminate` `permits()` result.** That is
package/dist/next.cjs CHANGED
@@ -420,6 +420,9 @@ function sanitizeReturnTo(returnTo) {
420
420
  }
421
421
  return returnTo.startsWith("/") ? returnTo : void 0;
422
422
  }
423
+ function resolveRedirectUri(redirectUri, request) {
424
+ return typeof redirectUri === "function" ? redirectUri(request) : redirectUri;
425
+ }
423
426
  function transactionCookieOptions(maxAge) {
424
427
  return {
425
428
  httpOnly: true,
@@ -445,7 +448,7 @@ function createLoginRoute(config) {
445
448
  const discovery = await fetchDiscovery(config.issuer);
446
449
  const authorizeUrl = new URL(discovery.authorization_endpoint);
447
450
  authorizeUrl.searchParams.set("client_id", config.clientId);
448
- authorizeUrl.searchParams.set("redirect_uri", config.redirectUri);
451
+ authorizeUrl.searchParams.set("redirect_uri", resolveRedirectUri(config.redirectUri, request));
449
452
  authorizeUrl.searchParams.set("response_type", "code");
450
453
  authorizeUrl.searchParams.set("scope", config.scope ?? "openid profile email roles");
451
454
  authorizeUrl.searchParams.set("state", state);
@@ -499,8 +502,9 @@ function createCallbackRoute(config) {
499
502
  issuer: config.issuer,
500
503
  clientId: config.clientId,
501
504
  clientSecret: config.clientSecret,
505
+ clientAuthMethod: config.clientAuthMethod,
502
506
  code,
503
- redirectUri: config.redirectUri,
507
+ redirectUri: resolveRedirectUri(config.redirectUri, request),
504
508
  codeVerifier: transaction.codeVerifier
505
509
  });
506
510
  } catch (error) {
package/dist/next.d.cts CHANGED
@@ -1,13 +1,32 @@
1
- import { NextResponse, NextRequest } from 'next/server';
2
- import { d as TokenSet, I as IdTokenClaims } from './verify-ggSzUrdC.cjs';
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { T as TokenEndpointAuthMethod, d as TokenSet, I as IdTokenClaims } from './verify-ggSzUrdC.cjs';
3
3
  import { SealSecret } from './sealed.cjs';
4
4
  import '@tdacorp/identity-authz';
5
5
  import 'jose';
6
6
 
7
+ /**
8
+ * A `redirect_uri` (RFC 6749 §3.1.2), either fixed or derived from the
9
+ * incoming request (e.g. `(request) => new URL('/api/auth/callback',
10
+ * request.nextUrl.origin).toString()`, for a deployment reachable at more
11
+ * than one origin -- a preview URL per branch, say).
12
+ *
13
+ * The resolved value MUST be identical on both the `createLoginRoute` call
14
+ * that starts a login and the `createCallbackRoute` call that finishes it
15
+ * (RFC 6749 §4.1.3 requires the token endpoint to see the same
16
+ * `redirect_uri` the authorization request used, and the identity server
17
+ * enforces this). A function form is only safe when it derives the same
18
+ * result from equivalent requests -- ordinarily true for `request.nextUrl
19
+ * .origin`, since the login and callback requests share one browser
20
+ * session and one domain, but not for anything that varies within that,
21
+ * e.g. a path segment.
22
+ */
23
+ type RedirectUriConfig = string | ((request: NextRequest) => string);
7
24
  interface CreateLoginRouteConfig {
8
25
  issuer: string;
9
26
  clientId: string;
10
- redirectUri: string;
27
+ /** See `RedirectUriConfig` -- must resolve to the same value
28
+ * `createCallbackRoute`'s own `redirectUri` does. */
29
+ redirectUri: RedirectUriConfig;
11
30
  /** Space-separated OAuth scopes. Defaults to `"openid profile email
12
31
  * roles"` -- `roles` is included in the default because without it the
13
32
  * minted token carries no `roles` claim, and every `permits()` call
@@ -63,7 +82,19 @@ interface CreateCallbackRouteConfig {
63
82
  clientId: string;
64
83
  /** Omit for a public client authenticating via PKCE alone. */
65
84
  clientSecret?: string;
66
- redirectUri: string;
85
+ /** How to send `clientSecret` to the token endpoint. Defaults to
86
+ * `'client_secret_basic'`, matching `exchangeAuthorizationCode`'s own
87
+ * default -- omitting this field changes nothing for an existing
88
+ * caller. Set it to `'client_secret_post'` if the relying party is
89
+ * registered on the identity platform for that method instead: sending
90
+ * Basic auth to a `client_secret_post`-only client gets `invalid_client`
91
+ * back, since the token endpoint never receives credentials in the form
92
+ * it expects. See `exchangeAuthorizationCode`'s own `clientAuthMethod`
93
+ * docs for the full reasoning. */
94
+ clientAuthMethod?: TokenEndpointAuthMethod;
95
+ /** See `RedirectUriConfig` -- must resolve to the same value
96
+ * `createLoginRoute`'s own `redirectUri` did. */
97
+ redirectUri: RedirectUriConfig;
67
98
  cookieSecret: SealSecret;
68
99
  transactionCookieName?: string;
69
100
  /** Defaults to `clientId`, correct for a standard login where this app is
@@ -92,4 +123,4 @@ interface CreateCallbackRouteConfig {
92
123
  * package stops there. */
93
124
  declare function createCallbackRoute(config: CreateCallbackRouteConfig): (request: NextRequest) => Promise<NextResponse>;
94
125
 
95
- export { type CallbackError, type CreateCallbackRouteConfig, type CreateLoginRouteConfig, createCallbackRoute, createLoginRoute };
126
+ export { type CallbackError, type CreateCallbackRouteConfig, type CreateLoginRouteConfig, type RedirectUriConfig, createCallbackRoute, createLoginRoute };
package/dist/next.d.ts CHANGED
@@ -1,13 +1,32 @@
1
- import { NextResponse, NextRequest } from 'next/server';
2
- import { d as TokenSet, I as IdTokenClaims } from './verify-ggSzUrdC.js';
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { T as TokenEndpointAuthMethod, d as TokenSet, I as IdTokenClaims } from './verify-ggSzUrdC.js';
3
3
  import { SealSecret } from './sealed.js';
4
4
  import '@tdacorp/identity-authz';
5
5
  import 'jose';
6
6
 
7
+ /**
8
+ * A `redirect_uri` (RFC 6749 §3.1.2), either fixed or derived from the
9
+ * incoming request (e.g. `(request) => new URL('/api/auth/callback',
10
+ * request.nextUrl.origin).toString()`, for a deployment reachable at more
11
+ * than one origin -- a preview URL per branch, say).
12
+ *
13
+ * The resolved value MUST be identical on both the `createLoginRoute` call
14
+ * that starts a login and the `createCallbackRoute` call that finishes it
15
+ * (RFC 6749 §4.1.3 requires the token endpoint to see the same
16
+ * `redirect_uri` the authorization request used, and the identity server
17
+ * enforces this). A function form is only safe when it derives the same
18
+ * result from equivalent requests -- ordinarily true for `request.nextUrl
19
+ * .origin`, since the login and callback requests share one browser
20
+ * session and one domain, but not for anything that varies within that,
21
+ * e.g. a path segment.
22
+ */
23
+ type RedirectUriConfig = string | ((request: NextRequest) => string);
7
24
  interface CreateLoginRouteConfig {
8
25
  issuer: string;
9
26
  clientId: string;
10
- redirectUri: string;
27
+ /** See `RedirectUriConfig` -- must resolve to the same value
28
+ * `createCallbackRoute`'s own `redirectUri` does. */
29
+ redirectUri: RedirectUriConfig;
11
30
  /** Space-separated OAuth scopes. Defaults to `"openid profile email
12
31
  * roles"` -- `roles` is included in the default because without it the
13
32
  * minted token carries no `roles` claim, and every `permits()` call
@@ -63,7 +82,19 @@ interface CreateCallbackRouteConfig {
63
82
  clientId: string;
64
83
  /** Omit for a public client authenticating via PKCE alone. */
65
84
  clientSecret?: string;
66
- redirectUri: string;
85
+ /** How to send `clientSecret` to the token endpoint. Defaults to
86
+ * `'client_secret_basic'`, matching `exchangeAuthorizationCode`'s own
87
+ * default -- omitting this field changes nothing for an existing
88
+ * caller. Set it to `'client_secret_post'` if the relying party is
89
+ * registered on the identity platform for that method instead: sending
90
+ * Basic auth to a `client_secret_post`-only client gets `invalid_client`
91
+ * back, since the token endpoint never receives credentials in the form
92
+ * it expects. See `exchangeAuthorizationCode`'s own `clientAuthMethod`
93
+ * docs for the full reasoning. */
94
+ clientAuthMethod?: TokenEndpointAuthMethod;
95
+ /** See `RedirectUriConfig` -- must resolve to the same value
96
+ * `createLoginRoute`'s own `redirectUri` did. */
97
+ redirectUri: RedirectUriConfig;
67
98
  cookieSecret: SealSecret;
68
99
  transactionCookieName?: string;
69
100
  /** Defaults to `clientId`, correct for a standard login where this app is
@@ -92,4 +123,4 @@ interface CreateCallbackRouteConfig {
92
123
  * package stops there. */
93
124
  declare function createCallbackRoute(config: CreateCallbackRouteConfig): (request: NextRequest) => Promise<NextResponse>;
94
125
 
95
- export { type CallbackError, type CreateCallbackRouteConfig, type CreateLoginRouteConfig, createCallbackRoute, createLoginRoute };
126
+ export { type CallbackError, type CreateCallbackRouteConfig, type CreateLoginRouteConfig, type RedirectUriConfig, createCallbackRoute, createLoginRoute };
package/dist/next.js CHANGED
@@ -27,6 +27,9 @@ function sanitizeReturnTo(returnTo) {
27
27
  }
28
28
  return returnTo.startsWith("/") ? returnTo : void 0;
29
29
  }
30
+ function resolveRedirectUri(redirectUri, request) {
31
+ return typeof redirectUri === "function" ? redirectUri(request) : redirectUri;
32
+ }
30
33
  function transactionCookieOptions(maxAge) {
31
34
  return {
32
35
  httpOnly: true,
@@ -52,7 +55,7 @@ function createLoginRoute(config) {
52
55
  const discovery = await fetchDiscovery(config.issuer);
53
56
  const authorizeUrl = new URL(discovery.authorization_endpoint);
54
57
  authorizeUrl.searchParams.set("client_id", config.clientId);
55
- authorizeUrl.searchParams.set("redirect_uri", config.redirectUri);
58
+ authorizeUrl.searchParams.set("redirect_uri", resolveRedirectUri(config.redirectUri, request));
56
59
  authorizeUrl.searchParams.set("response_type", "code");
57
60
  authorizeUrl.searchParams.set("scope", config.scope ?? "openid profile email roles");
58
61
  authorizeUrl.searchParams.set("state", state);
@@ -106,8 +109,9 @@ function createCallbackRoute(config) {
106
109
  issuer: config.issuer,
107
110
  clientId: config.clientId,
108
111
  clientSecret: config.clientSecret,
112
+ clientAuthMethod: config.clientAuthMethod,
109
113
  code,
110
- redirectUri: config.redirectUri,
114
+ redirectUri: resolveRedirectUri(config.redirectUri, request),
111
115
  codeVerifier: transaction.codeVerifier
112
116
  });
113
117
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tdacorp/identity-client",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "OIDC relying-party client for TDACorp Identity: discovery, PKCE, token exchange, token verification and Next.js route helpers.",
5
5
  "license": "MIT",
6
6
  "bugs": {