@iqauth/sdk 2.6.0 → 2.6.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/README.md CHANGED
@@ -17,6 +17,7 @@ The canonical TypeScript SDK for **IQAuthService** — DispositionIQ's multi-ten
17
17
  - [Install](#install)
18
18
  - [Five-line integration](#five-line-integration)
19
19
  - [Pick your environment](#pick-your-environment)
20
+ - [What's new in 2.6.1](#whats-new-in-261)
20
21
  - [What's new in 2.0.3](#whats-new-in-203)
21
22
  - [Browser apps with a backend (recommended)](#browser-apps-with-a-backend-recommended)
22
23
  - [Server reference (Express, Fastify, Hono, Next.js)](#server-reference)
@@ -89,6 +90,13 @@ gap, mirroring Clerk's `<ClerkLoading/>` / `<ClerkLoaded/>`.
89
90
  Available hooks: `useUser()`, `useSession()`, `useAuth()`, `useOrganization()`. Each returns `{ data, isLoading, error }`.
90
91
  Drop-in components: `<SignIn/>`, `<SignUp/>`, `<UserButton/>`, `<UserProfile/>`, `<OrganizationSwitcher/>`, `<AuthCallback/>`.
91
92
 
93
+ > **Silent SSO is opt-in as of 2.6.1.** `<SignIn/>` always renders the
94
+ > form on first paint by default — even for returning users with an active
95
+ > issuer-side `iq_sso` session. To restore the old auto-resume behavior,
96
+ > add `silentSso` to the provider or a specific `<SignIn/>` instance:
97
+ > `<IQAuthProvider publishableKey={…} silentSso>` or `<SignIn silentSso />`.
98
+ > See "What's new in 2.6.1" below.
99
+
92
100
  ### Express
93
101
 
94
102
  ```ts
@@ -165,6 +173,55 @@ The SDK is not "one auth model for every environment". The safe pattern depends
165
173
 
166
174
  ---
167
175
 
176
+ ## What's new in 2.6.1
177
+
178
+ ### 1. Silent SSO is now opt-in (default off)
179
+
180
+ Previously, `<SignIn/>` would detect an active issuer-side `iq_sso` session
181
+ on mount and silently redirect through `/oidc/sso-resume` — users never saw
182
+ the form, never clicked anything, and were transparently signed back in.
183
+ That was surprising for embedded use cases and made it impossible to switch
184
+ accounts without reaching for `?prompt=login`. As of 2.6.1, silent SSO is
185
+ **disabled by default** and must be explicitly enabled:
186
+
187
+ ```tsx
188
+ // Provider-wide opt-in (covers every <SignIn/> below it)
189
+ <IQAuthProvider publishableKey={…} silentSso>
190
+
191
+ </IQAuthProvider>
192
+
193
+ // Per-instance opt-in (overrides the provider value)
194
+ <SignIn silentSso />
195
+ ```
196
+
197
+ When silent SSO is off (default), `<SignIn/>` always renders the form on
198
+ first paint regardless of `iq_sso` cookie state. `prompt="login"` and
199
+ `?prompt=login` continue to work as additional force-form switches.
200
+
201
+ ### 2. Embedded card no longer forces full-viewport height
202
+
203
+ The internal `.iqauth-sdk-pane` declared `min-height: 100vh` unconditionally,
204
+ which worked for hosted full-page sign-in but broke when `<SignIn/>` was
205
+ embedded in a card, modal, or sidebar — pushing content below the fold and
206
+ creating large empty areas. The 100vh height now applies only inside the
207
+ wide side-by-side layout (`@container iqauth-sdk (min-width: 768px)`) where
208
+ the hero pane needs height parity. Narrow embeds size naturally to their
209
+ content.
210
+
211
+ ### 3. Better error reporting on misconfigured `<SignIn/>`
212
+
213
+ - `useIQAuthSignInContext` now detects HTML responses (CORS preflight, wrong
214
+ `iqAuthBaseUrl`, wrong `appKey`) and prints an actionable `console.error`
215
+ naming the three likely causes — instead of the cryptic
216
+ `Unexpected token '<' in JSON`.
217
+ - The "returnTo not in allowed origins" console error now also lists the
218
+ app's actual `allowedOrigins`, so the diff with the rejected `returnTo`
219
+ is visible at a glance instead of requiring a Network-tab spelunk.
220
+
221
+ For older versions see [`CHANGELOG.md`](./CHANGELOG.md).
222
+
223
+ ---
224
+
168
225
  ## What's new in 2.0.3
169
226
 
170
227
  ### 1. `serverManagedSession: true` for `SessionManager` / `IQAuthProvider`
package/dist/react.d.mts CHANGED
@@ -116,6 +116,13 @@ interface IQAuthContextValue {
116
116
  roleMapper: ((claims: JwtClaims | null) => string | null) | null;
117
117
  /** Task #95 — fully resolved localization bundle (default + override). */
118
118
  localization: IQAuthLocaleBundle;
119
+ /**
120
+ * 2.6.1 — Whether `<SignIn/>` is allowed to silently resume an active SSO
121
+ * session and redirect the user without showing the form. Opt-in. Default
122
+ * `false` so consumers always see the sign-in UI on first paint unless they
123
+ * explicitly enable it. Per-instance `<SignIn silentSso>` overrides this.
124
+ */
125
+ silentSso: boolean;
119
126
  }
120
127
  interface IQAuthProviderProps {
121
128
  publishableKey: string;
@@ -154,6 +161,14 @@ interface IQAuthProviderProps {
154
161
  * default `enUS` strings.
155
162
  */
156
163
  localization?: IQAuthLocaleBundle | IQAuthLocaleOverride;
164
+ /**
165
+ * 2.6.1 — Opt into silent SSO resume in `<SignIn/>` (default `false`).
166
+ * When `true`, `<SignIn/>` will detect an active issuer-side `iq_sso`
167
+ * session and redirect through `/oidc/sso-resume` without rendering the
168
+ * form. When `false` (default) the form always renders and users must
169
+ * click to continue. Per-instance `<SignIn silentSso>` overrides this.
170
+ */
171
+ silentSso?: boolean;
157
172
  children?: ReactNode;
158
173
  }
159
174
  /**
@@ -162,7 +177,7 @@ interface IQAuthProviderProps {
162
177
  * safe — a single SessionManager instance is created per provider and reused
163
178
  * across remounts.
164
179
  */
165
- declare function IQAuthProvider({ publishableKey, issuer, channelName, proactiveRefresh, manager: externalManager, allowedReturnOrigins, appearance, roleMapper, cookieNames, localization, children, }: IQAuthProviderProps): React.FunctionComponentElement<React.ProviderProps<IQAuthContextValue | null>>;
180
+ declare function IQAuthProvider({ publishableKey, issuer, channelName, proactiveRefresh, manager: externalManager, allowedReturnOrigins, appearance, roleMapper, cookieNames, localization, silentSso, children, }: IQAuthProviderProps): React.FunctionComponentElement<React.ProviderProps<IQAuthContextValue | null>>;
166
181
  /**
167
182
  * Task #95 — Returns the active localization bundle resolved from the
168
183
  * `localization` prop on `<IQAuthProvider>` (merged on top of `enUS`). Safe
@@ -578,6 +593,13 @@ interface SignInProps extends Partial<SharedComponentProps> {
578
593
  prompt?: "login";
579
594
  /** F11 — Per-instance appearance overrides; merged on top of provider-level appearance. */
580
595
  appearance?: IQAuthAppearance;
596
+ /**
597
+ * 2.6.1 — Per-instance opt-in to silent SSO resume. Overrides the
598
+ * `<IQAuthProvider silentSso>` value when supplied. Default (when both are
599
+ * unset): `false` — the form always renders and the user must click to
600
+ * continue.
601
+ */
602
+ silentSso?: boolean;
581
603
  }
582
604
  /**
583
605
  * Pure render-decision helper. When this returns `true`, `<SignIn/>` MUST
package/dist/react.d.ts CHANGED
@@ -116,6 +116,13 @@ interface IQAuthContextValue {
116
116
  roleMapper: ((claims: JwtClaims | null) => string | null) | null;
117
117
  /** Task #95 — fully resolved localization bundle (default + override). */
118
118
  localization: IQAuthLocaleBundle;
119
+ /**
120
+ * 2.6.1 — Whether `<SignIn/>` is allowed to silently resume an active SSO
121
+ * session and redirect the user without showing the form. Opt-in. Default
122
+ * `false` so consumers always see the sign-in UI on first paint unless they
123
+ * explicitly enable it. Per-instance `<SignIn silentSso>` overrides this.
124
+ */
125
+ silentSso: boolean;
119
126
  }
120
127
  interface IQAuthProviderProps {
121
128
  publishableKey: string;
@@ -154,6 +161,14 @@ interface IQAuthProviderProps {
154
161
  * default `enUS` strings.
155
162
  */
156
163
  localization?: IQAuthLocaleBundle | IQAuthLocaleOverride;
164
+ /**
165
+ * 2.6.1 — Opt into silent SSO resume in `<SignIn/>` (default `false`).
166
+ * When `true`, `<SignIn/>` will detect an active issuer-side `iq_sso`
167
+ * session and redirect through `/oidc/sso-resume` without rendering the
168
+ * form. When `false` (default) the form always renders and users must
169
+ * click to continue. Per-instance `<SignIn silentSso>` overrides this.
170
+ */
171
+ silentSso?: boolean;
157
172
  children?: ReactNode;
158
173
  }
159
174
  /**
@@ -162,7 +177,7 @@ interface IQAuthProviderProps {
162
177
  * safe — a single SessionManager instance is created per provider and reused
163
178
  * across remounts.
164
179
  */
165
- declare function IQAuthProvider({ publishableKey, issuer, channelName, proactiveRefresh, manager: externalManager, allowedReturnOrigins, appearance, roleMapper, cookieNames, localization, children, }: IQAuthProviderProps): React.FunctionComponentElement<React.ProviderProps<IQAuthContextValue | null>>;
180
+ declare function IQAuthProvider({ publishableKey, issuer, channelName, proactiveRefresh, manager: externalManager, allowedReturnOrigins, appearance, roleMapper, cookieNames, localization, silentSso, children, }: IQAuthProviderProps): React.FunctionComponentElement<React.ProviderProps<IQAuthContextValue | null>>;
166
181
  /**
167
182
  * Task #95 — Returns the active localization bundle resolved from the
168
183
  * `localization` prop on `<IQAuthProvider>` (merged on top of `enUS`). Safe
@@ -578,6 +593,13 @@ interface SignInProps extends Partial<SharedComponentProps> {
578
593
  prompt?: "login";
579
594
  /** F11 — Per-instance appearance overrides; merged on top of provider-level appearance. */
580
595
  appearance?: IQAuthAppearance;
596
+ /**
597
+ * 2.6.1 — Per-instance opt-in to silent SSO resume. Overrides the
598
+ * `<IQAuthProvider silentSso>` value when supplied. Default (when both are
599
+ * unset): `false` — the form always renders and the user must click to
600
+ * continue.
601
+ */
602
+ silentSso?: boolean;
581
603
  }
582
604
  /**
583
605
  * Pure render-decision helper. When this returns `true`, `<SignIn/>` MUST
package/dist/react.js CHANGED
@@ -1847,6 +1847,7 @@ function IQAuthProvider({
1847
1847
  roleMapper,
1848
1848
  cookieNames,
1849
1849
  localization,
1850
+ silentSso,
1850
1851
  children
1851
1852
  }) {
1852
1853
  const managerRef = (0, import_react.useRef)(null);
@@ -1895,9 +1896,10 @@ function IQAuthProvider({
1895
1896
  allowedReturnOrigins: allowedReturnOrigins ?? [],
1896
1897
  appearance: appearance ?? null,
1897
1898
  roleMapper: roleMapper ?? null,
1898
- localization: resolvedLocalization
1899
+ localization: resolvedLocalization,
1900
+ silentSso: silentSso ?? false
1899
1901
  }),
1900
- [manager, snapshot, allowedReturnOrigins, appearance, roleMapper, resolvedLocalization]
1902
+ [manager, snapshot, allowedReturnOrigins, appearance, roleMapper, resolvedLocalization, silentSso]
1901
1903
  );
1902
1904
  return (0, import_react.createElement)(IQAuthContext.Provider, { value }, children);
1903
1905
  }
@@ -2421,7 +2423,13 @@ var SHELL_CSS = `
2421
2423
  .iqauth-sdk-hero { display: none; }
2422
2424
  .iqauth-sdk-pane {
2423
2425
  display: flex; flex-direction: column; align-items: center; justify-content: center;
2424
- padding: 48px 24px; min-height: 100vh;
2426
+ padding: 32px 20px;
2427
+ /* 2.6.1 \u2014 Default to natural height so embedded usage (inside a card,
2428
+ modal, or sidebar) sizes to its content instead of forcing a full
2429
+ viewport. The hosted/full-page layout re-introduces 100vh below the
2430
+ 768px container-query threshold for the side-by-side hero variant. */
2431
+ min-height: auto;
2432
+ box-sizing: border-box;
2425
2433
  }
2426
2434
  .iqauth-sdk-card {
2427
2435
  width: 100%; max-width: 460px;
@@ -2468,6 +2476,9 @@ var SHELL_CSS = `
2468
2476
 
2469
2477
  @container iqauth-sdk (min-width: 768px) {
2470
2478
  .iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
2479
+ /* 2.6.1 \u2014 Restore 100vh ONLY for the wide side-by-side layout where
2480
+ hero+pane need height parity. Embedded narrow uses keep natural height. */
2481
+ .iqauth-sdk-pane { padding: 48px 24px; min-height: 100vh; }
2471
2482
  .iqauth-sdk-hero {
2472
2483
  display: flex; flex-direction: column; justify-content: space-between;
2473
2484
  padding: clamp(32px, 4vw, 56px); color: #ffffff;
@@ -2793,7 +2804,8 @@ function SignIn(props) {
2793
2804
  const iqAuthBaseUrl = props.iqAuthBaseUrl ?? providerCtx?.manager.issuerUrl ?? "";
2794
2805
  const appKey = props.appKey ?? providerCtx?.manager.appKey ?? "";
2795
2806
  const returnTo = props.returnTo ?? (typeof window !== "undefined" ? `${window.location.origin}/api/iqauth/callback` : "");
2796
- const { onRedirect, className, prompt, appearance: instanceAppearance } = props;
2807
+ const { onRedirect, className, prompt, appearance: instanceAppearance, silentSso: instanceSilentSso } = props;
2808
+ const silentSsoEnabled = instanceSilentSso ?? providerCtx?.silentSso ?? false;
2797
2809
  const appearance = instanceAppearance && providerCtx?.appearance ? { elements: { ...providerCtx.appearance.elements, ...instanceAppearance.elements } } : instanceAppearance ?? providerCtx?.appearance ?? null;
2798
2810
  if (!iqAuthBaseUrl || !appKey) {
2799
2811
  console.error(
@@ -2823,6 +2835,7 @@ function SignIn(props) {
2823
2835
  const [forcePrompt, setForcePrompt] = (0, import_react.useState)(false);
2824
2836
  const effectivePrompt = (0, import_react.useMemo)(() => {
2825
2837
  if (prompt === "login" || forcePrompt) return "login";
2838
+ if (!silentSsoEnabled) return "login";
2826
2839
  if (typeof window !== "undefined") {
2827
2840
  try {
2828
2841
  if (new URLSearchParams(window.location.search).get("prompt") === "login") return "login";
@@ -2830,7 +2843,7 @@ function SignIn(props) {
2830
2843
  }
2831
2844
  }
2832
2845
  return void 0;
2833
- }, [prompt, forcePrompt]);
2846
+ }, [prompt, forcePrompt, silentSsoEnabled]);
2834
2847
  const oidcPayload = () => ({
2835
2848
  client_id: ctx?.app.defaultClientId,
2836
2849
  redirect_uri: returnTo,
package/dist/react.mjs CHANGED
@@ -102,6 +102,7 @@ function IQAuthProvider({
102
102
  roleMapper,
103
103
  cookieNames,
104
104
  localization,
105
+ silentSso,
105
106
  children
106
107
  }) {
107
108
  const managerRef = useRef(null);
@@ -150,9 +151,10 @@ function IQAuthProvider({
150
151
  allowedReturnOrigins: allowedReturnOrigins ?? [],
151
152
  appearance: appearance ?? null,
152
153
  roleMapper: roleMapper ?? null,
153
- localization: resolvedLocalization
154
+ localization: resolvedLocalization,
155
+ silentSso: silentSso ?? false
154
156
  }),
155
- [manager, snapshot, allowedReturnOrigins, appearance, roleMapper, resolvedLocalization]
157
+ [manager, snapshot, allowedReturnOrigins, appearance, roleMapper, resolvedLocalization, silentSso]
156
158
  );
157
159
  return createElement(IQAuthContext.Provider, { value }, children);
158
160
  }
@@ -676,7 +678,13 @@ var SHELL_CSS = `
676
678
  .iqauth-sdk-hero { display: none; }
677
679
  .iqauth-sdk-pane {
678
680
  display: flex; flex-direction: column; align-items: center; justify-content: center;
679
- padding: 48px 24px; min-height: 100vh;
681
+ padding: 32px 20px;
682
+ /* 2.6.1 \u2014 Default to natural height so embedded usage (inside a card,
683
+ modal, or sidebar) sizes to its content instead of forcing a full
684
+ viewport. The hosted/full-page layout re-introduces 100vh below the
685
+ 768px container-query threshold for the side-by-side hero variant. */
686
+ min-height: auto;
687
+ box-sizing: border-box;
680
688
  }
681
689
  .iqauth-sdk-card {
682
690
  width: 100%; max-width: 460px;
@@ -723,6 +731,9 @@ var SHELL_CSS = `
723
731
 
724
732
  @container iqauth-sdk (min-width: 768px) {
725
733
  .iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
734
+ /* 2.6.1 \u2014 Restore 100vh ONLY for the wide side-by-side layout where
735
+ hero+pane need height parity. Embedded narrow uses keep natural height. */
736
+ .iqauth-sdk-pane { padding: 48px 24px; min-height: 100vh; }
726
737
  .iqauth-sdk-hero {
727
738
  display: flex; flex-direction: column; justify-content: space-between;
728
739
  padding: clamp(32px, 4vw, 56px); color: #ffffff;
@@ -1048,7 +1059,8 @@ function SignIn(props) {
1048
1059
  const iqAuthBaseUrl = props.iqAuthBaseUrl ?? providerCtx?.manager.issuerUrl ?? "";
1049
1060
  const appKey = props.appKey ?? providerCtx?.manager.appKey ?? "";
1050
1061
  const returnTo = props.returnTo ?? (typeof window !== "undefined" ? `${window.location.origin}/api/iqauth/callback` : "");
1051
- const { onRedirect, className, prompt, appearance: instanceAppearance } = props;
1062
+ const { onRedirect, className, prompt, appearance: instanceAppearance, silentSso: instanceSilentSso } = props;
1063
+ const silentSsoEnabled = instanceSilentSso ?? providerCtx?.silentSso ?? false;
1052
1064
  const appearance = instanceAppearance && providerCtx?.appearance ? { elements: { ...providerCtx.appearance.elements, ...instanceAppearance.elements } } : instanceAppearance ?? providerCtx?.appearance ?? null;
1053
1065
  if (!iqAuthBaseUrl || !appKey) {
1054
1066
  console.error(
@@ -1078,6 +1090,7 @@ function SignIn(props) {
1078
1090
  const [forcePrompt, setForcePrompt] = useState(false);
1079
1091
  const effectivePrompt = useMemo(() => {
1080
1092
  if (prompt === "login" || forcePrompt) return "login";
1093
+ if (!silentSsoEnabled) return "login";
1081
1094
  if (typeof window !== "undefined") {
1082
1095
  try {
1083
1096
  if (new URLSearchParams(window.location.search).get("prompt") === "login") return "login";
@@ -1085,7 +1098,7 @@ function SignIn(props) {
1085
1098
  }
1086
1099
  }
1087
1100
  return void 0;
1088
- }, [prompt, forcePrompt]);
1101
+ }, [prompt, forcePrompt, silentSsoEnabled]);
1089
1102
  const oidcPayload = () => ({
1090
1103
  client_id: ctx?.app.defaultClientId,
1091
1104
  redirect_uri: returnTo,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iqauth/sdk",
3
- "version": "2.6.0",
3
+ "version": "2.6.1",
4
4
  "description": "TypeScript SDK for IQAuth — the canonical way for all IQ projects to integrate with IQAuthService",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",