@iqauth/sdk 2.5.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
  }
@@ -2374,7 +2376,19 @@ function useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo) {
2374
2376
  let cancelled = false;
2375
2377
  setLoading(true);
2376
2378
  const url2 = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/public/apps/${encodeURIComponent(appKey)}/sign-in-context?return_to=${encodeURIComponent(returnTo)}`;
2377
- fetch(url2, { credentials: "include" }).then((r) => r.json()).then((payload) => {
2379
+ fetch(url2, { credentials: "include" }).then(async (r) => {
2380
+ const contentType = r.headers.get("content-type") || "";
2381
+ if (!r.ok || !contentType.includes("json")) {
2382
+ const bodyPreview = await r.text().then((t2) => t2.slice(0, 160)).catch(() => "");
2383
+ console.error(
2384
+ `[IQAuth] sign-in-context request failed: ${r.status} ${r.statusText} (content-type: ${contentType || "\u2014"}). URL: ${url2}. Common causes: (1) iqAuthBaseUrl points at the wrong host (it should be your IQAuth issuer, e.g. https://auth.dispositioniq.com \u2014 NOT your own app's domain); (2) the app key "${appKey}" is wrong or revoked; (3) CORS preflight is blocked because this origin isn't in the app's allowed-origins list. Response body preview: ${bodyPreview}`
2385
+ );
2386
+ throw new Error(
2387
+ r.status >= 500 ? "Failed to load sign-in context (server error)" : `Failed to load sign-in context (HTTP ${r.status})`
2388
+ );
2389
+ }
2390
+ return r.json();
2391
+ }).then((payload) => {
2378
2392
  if (cancelled) return;
2379
2393
  if (payload?.success === false) throw new Error(payload?.error?.message || "Failed to load sign-in context");
2380
2394
  setCtx(payload.data);
@@ -2397,11 +2411,25 @@ var SHELL_CSS = `
2397
2411
  grid-template-columns: 1fr;
2398
2412
  background: var(--brand-bg, #f7f7f6);
2399
2413
  color: var(--brand-text, #0f172a);
2414
+ /* Container queries so the two-pane layout responds to the SDK's
2415
+ RENDERED width, not the viewport. This keeps the form usable when
2416
+ a host app embeds <SignIn/> inside a narrower card or sidebar
2417
+ instead of full-screen \u2014 the previous viewport @media query would
2418
+ happily render the side-by-side hero+form into a 200px container
2419
+ and wrap text one character per line. */
2420
+ container-type: inline-size;
2421
+ container-name: iqauth-sdk;
2400
2422
  }
2401
2423
  .iqauth-sdk-hero { display: none; }
2402
2424
  .iqauth-sdk-pane {
2403
2425
  display: flex; flex-direction: column; align-items: center; justify-content: center;
2404
- 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;
2405
2433
  }
2406
2434
  .iqauth-sdk-card {
2407
2435
  width: 100%; max-width: 460px;
@@ -2446,8 +2474,11 @@ var SHELL_CSS = `
2446
2474
  .iqauth-sdk-google-btn:hover { background: #f8fafc; border-color: rgba(15,23,42,0.28); }
2447
2475
  .iqauth-sdk-google-btn[disabled] { opacity: 0.6; cursor: not-allowed; }
2448
2476
 
2449
- @media (min-width: 768px) {
2477
+ @container iqauth-sdk (min-width: 768px) {
2450
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; }
2451
2482
  .iqauth-sdk-hero {
2452
2483
  display: flex; flex-direction: column; justify-content: space-between;
2453
2484
  padding: clamp(32px, 4vw, 56px); color: #ffffff;
@@ -2467,7 +2498,7 @@ var SHELL_CSS = `
2467
2498
  .iqauth-sdk-hero-foot { font-size: 12px; opacity: 0.7; }
2468
2499
  .iqauth-sdk-mobile-brand { display: none; }
2469
2500
  }
2470
- @media (min-width: 1280px) {
2501
+ @container iqauth-sdk (min-width: 1280px) {
2471
2502
  .iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }
2472
2503
  }
2473
2504
  `;
@@ -2773,7 +2804,8 @@ function SignIn(props) {
2773
2804
  const iqAuthBaseUrl = props.iqAuthBaseUrl ?? providerCtx?.manager.issuerUrl ?? "";
2774
2805
  const appKey = props.appKey ?? providerCtx?.manager.appKey ?? "";
2775
2806
  const returnTo = props.returnTo ?? (typeof window !== "undefined" ? `${window.location.origin}/api/iqauth/callback` : "");
2776
- const { onRedirect, className, prompt, appearance: instanceAppearance } = props;
2807
+ const { onRedirect, className, prompt, appearance: instanceAppearance, silentSso: instanceSilentSso } = props;
2808
+ const silentSsoEnabled = instanceSilentSso ?? providerCtx?.silentSso ?? false;
2777
2809
  const appearance = instanceAppearance && providerCtx?.appearance ? { elements: { ...providerCtx.appearance.elements, ...instanceAppearance.elements } } : instanceAppearance ?? providerCtx?.appearance ?? null;
2778
2810
  if (!iqAuthBaseUrl || !appKey) {
2779
2811
  console.error(
@@ -2803,6 +2835,7 @@ function SignIn(props) {
2803
2835
  const [forcePrompt, setForcePrompt] = (0, import_react.useState)(false);
2804
2836
  const effectivePrompt = (0, import_react.useMemo)(() => {
2805
2837
  if (prompt === "login" || forcePrompt) return "login";
2838
+ if (!silentSsoEnabled) return "login";
2806
2839
  if (typeof window !== "undefined") {
2807
2840
  try {
2808
2841
  if (new URLSearchParams(window.location.search).get("prompt") === "login") return "login";
@@ -2810,7 +2843,7 @@ function SignIn(props) {
2810
2843
  }
2811
2844
  }
2812
2845
  return void 0;
2813
- }, [prompt, forcePrompt]);
2846
+ }, [prompt, forcePrompt, silentSsoEnabled]);
2814
2847
  const oidcPayload = () => ({
2815
2848
  client_id: ctx?.app.defaultClientId,
2816
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
  }
@@ -629,7 +631,19 @@ function useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo) {
629
631
  let cancelled = false;
630
632
  setLoading(true);
631
633
  const url = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/public/apps/${encodeURIComponent(appKey)}/sign-in-context?return_to=${encodeURIComponent(returnTo)}`;
632
- fetch(url, { credentials: "include" }).then((r) => r.json()).then((payload) => {
634
+ fetch(url, { credentials: "include" }).then(async (r) => {
635
+ const contentType = r.headers.get("content-type") || "";
636
+ if (!r.ok || !contentType.includes("json")) {
637
+ const bodyPreview = await r.text().then((t2) => t2.slice(0, 160)).catch(() => "");
638
+ console.error(
639
+ `[IQAuth] sign-in-context request failed: ${r.status} ${r.statusText} (content-type: ${contentType || "\u2014"}). URL: ${url}. Common causes: (1) iqAuthBaseUrl points at the wrong host (it should be your IQAuth issuer, e.g. https://auth.dispositioniq.com \u2014 NOT your own app's domain); (2) the app key "${appKey}" is wrong or revoked; (3) CORS preflight is blocked because this origin isn't in the app's allowed-origins list. Response body preview: ${bodyPreview}`
640
+ );
641
+ throw new Error(
642
+ r.status >= 500 ? "Failed to load sign-in context (server error)" : `Failed to load sign-in context (HTTP ${r.status})`
643
+ );
644
+ }
645
+ return r.json();
646
+ }).then((payload) => {
633
647
  if (cancelled) return;
634
648
  if (payload?.success === false) throw new Error(payload?.error?.message || "Failed to load sign-in context");
635
649
  setCtx(payload.data);
@@ -652,11 +666,25 @@ var SHELL_CSS = `
652
666
  grid-template-columns: 1fr;
653
667
  background: var(--brand-bg, #f7f7f6);
654
668
  color: var(--brand-text, #0f172a);
669
+ /* Container queries so the two-pane layout responds to the SDK's
670
+ RENDERED width, not the viewport. This keeps the form usable when
671
+ a host app embeds <SignIn/> inside a narrower card or sidebar
672
+ instead of full-screen \u2014 the previous viewport @media query would
673
+ happily render the side-by-side hero+form into a 200px container
674
+ and wrap text one character per line. */
675
+ container-type: inline-size;
676
+ container-name: iqauth-sdk;
655
677
  }
656
678
  .iqauth-sdk-hero { display: none; }
657
679
  .iqauth-sdk-pane {
658
680
  display: flex; flex-direction: column; align-items: center; justify-content: center;
659
- 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;
660
688
  }
661
689
  .iqauth-sdk-card {
662
690
  width: 100%; max-width: 460px;
@@ -701,8 +729,11 @@ var SHELL_CSS = `
701
729
  .iqauth-sdk-google-btn:hover { background: #f8fafc; border-color: rgba(15,23,42,0.28); }
702
730
  .iqauth-sdk-google-btn[disabled] { opacity: 0.6; cursor: not-allowed; }
703
731
 
704
- @media (min-width: 768px) {
732
+ @container iqauth-sdk (min-width: 768px) {
705
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; }
706
737
  .iqauth-sdk-hero {
707
738
  display: flex; flex-direction: column; justify-content: space-between;
708
739
  padding: clamp(32px, 4vw, 56px); color: #ffffff;
@@ -722,7 +753,7 @@ var SHELL_CSS = `
722
753
  .iqauth-sdk-hero-foot { font-size: 12px; opacity: 0.7; }
723
754
  .iqauth-sdk-mobile-brand { display: none; }
724
755
  }
725
- @media (min-width: 1280px) {
756
+ @container iqauth-sdk (min-width: 1280px) {
726
757
  .iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }
727
758
  }
728
759
  `;
@@ -1028,7 +1059,8 @@ function SignIn(props) {
1028
1059
  const iqAuthBaseUrl = props.iqAuthBaseUrl ?? providerCtx?.manager.issuerUrl ?? "";
1029
1060
  const appKey = props.appKey ?? providerCtx?.manager.appKey ?? "";
1030
1061
  const returnTo = props.returnTo ?? (typeof window !== "undefined" ? `${window.location.origin}/api/iqauth/callback` : "");
1031
- const { onRedirect, className, prompt, appearance: instanceAppearance } = props;
1062
+ const { onRedirect, className, prompt, appearance: instanceAppearance, silentSso: instanceSilentSso } = props;
1063
+ const silentSsoEnabled = instanceSilentSso ?? providerCtx?.silentSso ?? false;
1032
1064
  const appearance = instanceAppearance && providerCtx?.appearance ? { elements: { ...providerCtx.appearance.elements, ...instanceAppearance.elements } } : instanceAppearance ?? providerCtx?.appearance ?? null;
1033
1065
  if (!iqAuthBaseUrl || !appKey) {
1034
1066
  console.error(
@@ -1058,6 +1090,7 @@ function SignIn(props) {
1058
1090
  const [forcePrompt, setForcePrompt] = useState(false);
1059
1091
  const effectivePrompt = useMemo(() => {
1060
1092
  if (prompt === "login" || forcePrompt) return "login";
1093
+ if (!silentSsoEnabled) return "login";
1061
1094
  if (typeof window !== "undefined") {
1062
1095
  try {
1063
1096
  if (new URLSearchParams(window.location.search).get("prompt") === "login") return "login";
@@ -1065,7 +1098,7 @@ function SignIn(props) {
1065
1098
  }
1066
1099
  }
1067
1100
  return void 0;
1068
- }, [prompt, forcePrompt]);
1101
+ }, [prompt, forcePrompt, silentSsoEnabled]);
1069
1102
  const oidcPayload = () => ({
1070
1103
  client_id: ctx?.app.defaultClientId,
1071
1104
  redirect_uri: returnTo,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iqauth/sdk",
3
- "version": "2.5.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",