@iqauth/sdk 2.6.0 → 2.6.2

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,8 @@ 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.2](#whats-new-in-262)
21
+ - [What's new in 2.6.1](#whats-new-in-261)
20
22
  - [What's new in 2.0.3](#whats-new-in-203)
21
23
  - [Browser apps with a backend (recommended)](#browser-apps-with-a-backend-recommended)
22
24
  - [Server reference (Express, Fastify, Hono, Next.js)](#server-reference)
@@ -89,6 +91,13 @@ gap, mirroring Clerk's `<ClerkLoading/>` / `<ClerkLoaded/>`.
89
91
  Available hooks: `useUser()`, `useSession()`, `useAuth()`, `useOrganization()`. Each returns `{ data, isLoading, error }`.
90
92
  Drop-in components: `<SignIn/>`, `<SignUp/>`, `<UserButton/>`, `<UserProfile/>`, `<OrganizationSwitcher/>`, `<AuthCallback/>`.
91
93
 
94
+ > **Silent SSO is opt-in as of 2.6.1.** `<SignIn/>` always renders the
95
+ > form on first paint by default — even for returning users with an active
96
+ > issuer-side `iq_sso` session. To restore the old auto-resume behavior,
97
+ > add `silentSso` to the provider or a specific `<SignIn/>` instance:
98
+ > `<IQAuthProvider publishableKey={…} silentSso>` or `<SignIn silentSso />`.
99
+ > See "What's new in 2.6.1" below.
100
+
92
101
  ### Express
93
102
 
94
103
  ```ts
@@ -165,6 +174,74 @@ The SDK is not "one auth model for every environment". The safe pattern depends
165
174
 
166
175
  ---
167
176
 
177
+ ## What's new in 2.6.2
178
+
179
+ ### Card grows responsively on desktop (no more phone-sized form)
180
+
181
+ `.iqauth-sdk-card` used to cap at `max-width: 460px` at every viewport,
182
+ which meant a desktop user staring at a 1440px monitor saw a thin mobile
183
+ column floating inside a 720px-wide pane. The cap is now responsive:
184
+
185
+ - mobile (default): **480px**
186
+ - tablet / desktop (`@container ≥ 768px`): **540px**
187
+ - extra-wide (`@container ≥ 1280px`): **580px**
188
+
189
+ Inner header/body padding and the title size also bump up at the desktop
190
+ breakpoint so the form actually fills its half of the split layout. No
191
+ opt-in required — the change applies to every consumer of `<SignIn/>` and
192
+ `<SignUp/>` after upgrading.
193
+
194
+ ---
195
+
196
+ ## What's new in 2.6.1
197
+
198
+ ### 1. Silent SSO is now opt-in (default off)
199
+
200
+ Previously, `<SignIn/>` would detect an active issuer-side `iq_sso` session
201
+ on mount and silently redirect through `/oidc/sso-resume` — users never saw
202
+ the form, never clicked anything, and were transparently signed back in.
203
+ That was surprising for embedded use cases and made it impossible to switch
204
+ accounts without reaching for `?prompt=login`. As of 2.6.1, silent SSO is
205
+ **disabled by default** and must be explicitly enabled:
206
+
207
+ ```tsx
208
+ // Provider-wide opt-in (covers every <SignIn/> below it)
209
+ <IQAuthProvider publishableKey={…} silentSso>
210
+
211
+ </IQAuthProvider>
212
+
213
+ // Per-instance opt-in (overrides the provider value)
214
+ <SignIn silentSso />
215
+ ```
216
+
217
+ When silent SSO is off (default), `<SignIn/>` always renders the form on
218
+ first paint regardless of `iq_sso` cookie state. `prompt="login"` and
219
+ `?prompt=login` continue to work as additional force-form switches.
220
+
221
+ ### 2. Embedded card no longer forces full-viewport height
222
+
223
+ The internal `.iqauth-sdk-pane` declared `min-height: 100vh` unconditionally,
224
+ which worked for hosted full-page sign-in but broke when `<SignIn/>` was
225
+ embedded in a card, modal, or sidebar — pushing content below the fold and
226
+ creating large empty areas. The 100vh height now applies only inside the
227
+ wide side-by-side layout (`@container iqauth-sdk (min-width: 768px)`) where
228
+ the hero pane needs height parity. Narrow embeds size naturally to their
229
+ content.
230
+
231
+ ### 3. Better error reporting on misconfigured `<SignIn/>`
232
+
233
+ - `useIQAuthSignInContext` now detects HTML responses (CORS preflight, wrong
234
+ `iqAuthBaseUrl`, wrong `appKey`) and prints an actionable `console.error`
235
+ naming the three likely causes — instead of the cryptic
236
+ `Unexpected token '<' in JSON`.
237
+ - The "returnTo not in allowed origins" console error now also lists the
238
+ app's actual `allowedOrigins`, so the diff with the rejected `returnTo`
239
+ is visible at a glance instead of requiring a Network-tab spelunk.
240
+
241
+ For older versions see [`CHANGELOG.md`](./CHANGELOG.md).
242
+
243
+ ---
244
+
168
245
  ## What's new in 2.0.3
169
246
 
170
247
  ### 1. `serverManagedSession: true` for `SessionManager` / `IQAuthProvider`
@@ -547,6 +624,54 @@ Common codes you'll handle:
547
624
  | Cross-origin call to a sister IQAuth-protected app gets CORS-blocked | Add the caller's origin to the target app's `app_allowed_origins`. Allow up to 60s for the cache to refresh. |
548
625
  | `req.auth` is undefined under Passport | Verify you're reading `req.auth`, not `req.user`. The SDK deliberately uses `req.auth` to avoid Passport collision. |
549
626
  | Hosted sign-in page won't accept your `return_to` | The origin isn't in this app's allowlist. Add it in the admin dashboard. |
627
+ | Server logs `[AUTH-MW] Missing auth credentials` on `GET /api/v1/auth/me` at app boot — **once**, immediately followed by a successful `[OIDC] SSO resume issued code` AND the next `/auth/me` returns 200 | **Benign.** SDK's `bootstrap()` probe. See ["Why does the server log `Missing auth credentials` at startup?"](#why-does-the-server-log-missing-auth-credentials-at-startup) below. |
628
+ | `[AUTH-MW] Missing auth credentials` repeats in a **loop** (warn → resume → token → warn → resume → token …), user can't actually finish login | **NOT benign.** The SDK's callback handler on YOUR app's domain isn't completing the token exchange, so no cookie ever gets set. See ["When the bootstrap loop never ends"](#when-the-bootstrap-loop-never-ends) below — almost always a middleware-ordering bug on the consumer app. |
629
+ | Same `Missing auth credentials` warning on `/api/v1/auth/me` but **no** subsequent `SSO resume issued code` or `/oidc/token` for the same user within ~1s | Real problem — your fetch is dropping cookies. Check `credentials: "include"`, CORS `Access-Control-Allow-Credentials: true`, `SameSite`/`COOKIE_DOMAIN`. |
630
+
631
+ ### Why does the server log `Missing auth credentials` at startup?
632
+
633
+ When `<IQAuthProvider>` mounts, the SDK's `bootstrap()` runs `GET /api/v1/auth/me` once to ask the issuer "do I already have a session on this app?" That call is *defined* to be anonymous when the user has never signed in here yet — there is no `iqauth_at` cookie scoped to your origin yet to send.
634
+
635
+ So the very first request you'll see for a returning user is:
636
+
637
+ ```
638
+ WARN [AUTH-MW] Missing auth credentials GET /api/v1/auth/me origin=https://your-app.com
639
+ INFO [OIDC] SSO resume issued code userId=… clientId=iq_…
640
+ INFO [AuthMetrics] /oidc/token latency
641
+ ```
642
+
643
+ That sequence is the **happy path** for silent SSO: probe → no session here → resume from the issuer's `iq_sso` cookie → exchange code for tokens → cookie now set on your origin → subsequent requests succeed against the cookie. The warning was misleading log severity for a defined-anonymous endpoint, and as of the next IQAuth server release the bootstrap probe on `/auth/me` and `/auth/refresh` is logged at **`debug`** rather than `warn`. Other routes still log at `warn` — so seeing this message on, say, `GET /api/v1/users` remains a real signal that cookies aren't traveling.
644
+
645
+ **If you're on an older IQAuth server and want to silence the noise without an upgrade,** add a server-side log filter on the `[AUTH-MW] Missing auth credentials` line where `route` matches `/api/v1/auth/(me|refresh)`. Don't blanket-mute the message — you'll lose visibility on real CORS/cookie failures.
646
+
647
+ ### When the bootstrap loop never ends
648
+
649
+ If you see `Missing auth credentials` → `SSO resume issued code` → `/oidc/token` happen **on repeat** and the user never actually gets signed in, the issuer side is doing its job perfectly — the failure is on **your** server. The SDK's callback helper at `/api/iqauth/callback` is responsible for taking the `?code=` from the OAuth redirect, POSTing it to `/oidc/token`, and setting the `iqauth_at` cookie on your app's domain. If that handler doesn't run (or returns an error), no cookie ever gets set and the SDK's next bootstrap probe is anonymous again — forever.
650
+
651
+ The single most common cause: **your own auth middleware is intercepting `/api/iqauth/callback` before `attachHelpers()` can handle it.** The OAuth return trip is a fresh GET from the issuer with no `Authorization` header (correctly so — that's the whole point of the callback), so an `app.use(requireAuth)` mounted globally will reject it as 401.
652
+
653
+ Confirm with these signals — if you see *any* of them, this is your bug:
654
+
655
+ - Browser: `GET https://your-app.com/api/iqauth/callback?code=…` returns **401** (or any 4xx other than 302)
656
+ - Browser DevTools → Application → Cookies for your app's domain: **no `iqauth_at` cookie present** after a sign-in attempt
657
+ - Your app's server log shows your **own** auth middleware rejecting `path=/iqauth/callback` or `/api/iqauth/callback` with "missing authorization header"
658
+
659
+ **Fix — pick one:**
660
+
661
+ ```ts
662
+ // ✅ Option 1 (recommended) — mount SDK helpers BEFORE your auth middleware.
663
+ const auth = iqAuth({ publishableKey, secretKey });
664
+ auth.attachHelpers(app); // public: /api/iqauth/{callback,refresh,signout}
665
+ app.use(yourAuthMiddleware); // anything below here requires a session
666
+
667
+ // ✅ Option 2 — exempt /api/iqauth/* from your own auth gate.
668
+ app.use((req, res, next) => {
669
+ if (req.path.startsWith("/api/iqauth/")) return next();
670
+ return yourAuthMiddleware(req, res, next);
671
+ });
672
+ ```
673
+
674
+ After the fix you should see `/api/iqauth/callback` return **302**, a `Set-Cookie: iqauth_at=…` header on the response, the cookie appearing under your app's domain in DevTools, and the next `/api/v1/auth/me` returning 200. The `Missing auth credentials` warning drops to the one harmless boot probe per fresh visitor.
550
675
 
551
676
  ---
552
677
 
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,10 +2423,16 @@ 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
- width: 100%; max-width: 460px;
2435
+ width: 100%; max-width: 480px;
2428
2436
  background: var(--brand-surface, #ffffff);
2429
2437
  border: 1px solid rgba(15,23,42,0.08);
2430
2438
  border-radius: var(--brand-radius, 16px);
@@ -2468,6 +2476,16 @@ 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; }
2482
+ /* 2.6.2 \u2014 Let the card breathe on desktop. The 480px mobile cap looks
2483
+ phone-sized on a 720px+ pane; bump to 540px and grow the inner padding
2484
+ so the form fills its half of the split layout. */
2485
+ .iqauth-sdk-card { max-width: 540px; }
2486
+ .iqauth-sdk-card-header { padding: 40px 44px 0; }
2487
+ .iqauth-sdk-card-body { padding: 32px 44px 32px; }
2488
+ .iqauth-sdk-card-header h1 { font-size: 26px; }
2471
2489
  .iqauth-sdk-hero {
2472
2490
  display: flex; flex-direction: column; justify-content: space-between;
2473
2491
  padding: clamp(32px, 4vw, 56px); color: #ffffff;
@@ -2489,6 +2507,9 @@ var SHELL_CSS = `
2489
2507
  }
2490
2508
  @container iqauth-sdk (min-width: 1280px) {
2491
2509
  .iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }
2510
+ /* 2.6.2 \u2014 Extra-wide screens: card grows once more so the form keeps
2511
+ pace with the hero pane on 1440px+ monitors. */
2512
+ .iqauth-sdk-card { max-width: 580px; }
2492
2513
  }
2493
2514
  `;
2494
2515
  var sdkShellStylesInjected = false;
@@ -2793,7 +2814,8 @@ function SignIn(props) {
2793
2814
  const iqAuthBaseUrl = props.iqAuthBaseUrl ?? providerCtx?.manager.issuerUrl ?? "";
2794
2815
  const appKey = props.appKey ?? providerCtx?.manager.appKey ?? "";
2795
2816
  const returnTo = props.returnTo ?? (typeof window !== "undefined" ? `${window.location.origin}/api/iqauth/callback` : "");
2796
- const { onRedirect, className, prompt, appearance: instanceAppearance } = props;
2817
+ const { onRedirect, className, prompt, appearance: instanceAppearance, silentSso: instanceSilentSso } = props;
2818
+ const silentSsoEnabled = instanceSilentSso ?? providerCtx?.silentSso ?? false;
2797
2819
  const appearance = instanceAppearance && providerCtx?.appearance ? { elements: { ...providerCtx.appearance.elements, ...instanceAppearance.elements } } : instanceAppearance ?? providerCtx?.appearance ?? null;
2798
2820
  if (!iqAuthBaseUrl || !appKey) {
2799
2821
  console.error(
@@ -2823,6 +2845,7 @@ function SignIn(props) {
2823
2845
  const [forcePrompt, setForcePrompt] = (0, import_react.useState)(false);
2824
2846
  const effectivePrompt = (0, import_react.useMemo)(() => {
2825
2847
  if (prompt === "login" || forcePrompt) return "login";
2848
+ if (!silentSsoEnabled) return "login";
2826
2849
  if (typeof window !== "undefined") {
2827
2850
  try {
2828
2851
  if (new URLSearchParams(window.location.search).get("prompt") === "login") return "login";
@@ -2830,7 +2853,7 @@ function SignIn(props) {
2830
2853
  }
2831
2854
  }
2832
2855
  return void 0;
2833
- }, [prompt, forcePrompt]);
2856
+ }, [prompt, forcePrompt, silentSsoEnabled]);
2834
2857
  const oidcPayload = () => ({
2835
2858
  client_id: ctx?.app.defaultClientId,
2836
2859
  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,10 +678,16 @@ 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
- width: 100%; max-width: 460px;
690
+ width: 100%; max-width: 480px;
683
691
  background: var(--brand-surface, #ffffff);
684
692
  border: 1px solid rgba(15,23,42,0.08);
685
693
  border-radius: var(--brand-radius, 16px);
@@ -723,6 +731,16 @@ 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; }
737
+ /* 2.6.2 \u2014 Let the card breathe on desktop. The 480px mobile cap looks
738
+ phone-sized on a 720px+ pane; bump to 540px and grow the inner padding
739
+ so the form fills its half of the split layout. */
740
+ .iqauth-sdk-card { max-width: 540px; }
741
+ .iqauth-sdk-card-header { padding: 40px 44px 0; }
742
+ .iqauth-sdk-card-body { padding: 32px 44px 32px; }
743
+ .iqauth-sdk-card-header h1 { font-size: 26px; }
726
744
  .iqauth-sdk-hero {
727
745
  display: flex; flex-direction: column; justify-content: space-between;
728
746
  padding: clamp(32px, 4vw, 56px); color: #ffffff;
@@ -744,6 +762,9 @@ var SHELL_CSS = `
744
762
  }
745
763
  @container iqauth-sdk (min-width: 1280px) {
746
764
  .iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }
765
+ /* 2.6.2 \u2014 Extra-wide screens: card grows once more so the form keeps
766
+ pace with the hero pane on 1440px+ monitors. */
767
+ .iqauth-sdk-card { max-width: 580px; }
747
768
  }
748
769
  `;
749
770
  var sdkShellStylesInjected = false;
@@ -1048,7 +1069,8 @@ function SignIn(props) {
1048
1069
  const iqAuthBaseUrl = props.iqAuthBaseUrl ?? providerCtx?.manager.issuerUrl ?? "";
1049
1070
  const appKey = props.appKey ?? providerCtx?.manager.appKey ?? "";
1050
1071
  const returnTo = props.returnTo ?? (typeof window !== "undefined" ? `${window.location.origin}/api/iqauth/callback` : "");
1051
- const { onRedirect, className, prompt, appearance: instanceAppearance } = props;
1072
+ const { onRedirect, className, prompt, appearance: instanceAppearance, silentSso: instanceSilentSso } = props;
1073
+ const silentSsoEnabled = instanceSilentSso ?? providerCtx?.silentSso ?? false;
1052
1074
  const appearance = instanceAppearance && providerCtx?.appearance ? { elements: { ...providerCtx.appearance.elements, ...instanceAppearance.elements } } : instanceAppearance ?? providerCtx?.appearance ?? null;
1053
1075
  if (!iqAuthBaseUrl || !appKey) {
1054
1076
  console.error(
@@ -1078,6 +1100,7 @@ function SignIn(props) {
1078
1100
  const [forcePrompt, setForcePrompt] = useState(false);
1079
1101
  const effectivePrompt = useMemo(() => {
1080
1102
  if (prompt === "login" || forcePrompt) return "login";
1103
+ if (!silentSsoEnabled) return "login";
1081
1104
  if (typeof window !== "undefined") {
1082
1105
  try {
1083
1106
  if (new URLSearchParams(window.location.search).get("prompt") === "login") return "login";
@@ -1085,7 +1108,7 @@ function SignIn(props) {
1085
1108
  }
1086
1109
  }
1087
1110
  return void 0;
1088
- }, [prompt, forcePrompt]);
1111
+ }, [prompt, forcePrompt, silentSsoEnabled]);
1089
1112
  const oidcPayload = () => ({
1090
1113
  client_id: ctx?.app.defaultClientId,
1091
1114
  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.2",
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",