@dloizides/auth-web 1.7.0 → 1.9.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.8.0 (2026-07-09)
4
+
5
+ Additive release for the Move 2b passkey in-app rebuild — the passkey ceremony
6
+ now runs **in-page** (`navigator.credentials.*` against the BFF's same-origin
7
+ `/bff/passkey/*` fetch endpoints) instead of a full-page redirect to Keycloak.
8
+ **Inert by default / no behaviour change until the BFF's WebAuthn RP is
9
+ configured:** when the RP is off the options endpoint returns 501 and the hook
10
+ transparently falls back to the legacy redirect. No breaking changes; every
11
+ 1.7.x export is unchanged and the legacy `startPasskeyLogin` /
12
+ `startPasskeyRegistration` navigation helpers stay exported (they ARE the
13
+ fallback).
14
+
15
+ ### Added
16
+
17
+ - **`usePasskeyCeremony({ mode, returnUrl, onSuccess })`** — the react-query-free
18
+ in-page ceremony hook: options fetch → `navigator.credentials.create/get` →
19
+ verify fetch. Returns `{ status, error, isSupported, start, reset }`. Falls
20
+ back to the legacy Keycloak redirect on a 501 (RP not configured), any non-2xx
21
+ options response, or an unsupported browser — so nothing regresses until the
22
+ owner flips `Bff:Passkey`. A cancelled/timed-out ceremony (`NotAllowedError` /
23
+ `AbortError`) surfaces as `'cancelled'`, anything else as `'failed'`.
24
+ - **`PasskeyCeremonyMode`** (`Login` / `Register`) and **`PasskeyCeremonyStatus`**
25
+ (`Idle` / `Running` / `Success` / `Error`) `const enum`s.
26
+
27
+ ### Changed
28
+
29
+ - **`<PasskeyLoginButton>`** and **`<PasskeySettingsCard>`** now drive
30
+ `usePasskeyCeremony` instead of `window.location.assign`. Same exported props,
31
+ same testIDs — a drop-in bump for consumers; the in-page path only activates
32
+ once the BFF RP is configured, otherwise they behave exactly as before.
33
+
3
34
  ## 1.7.0 (2026-07-07)
4
35
 
5
36
  Additive release for the Kefi multi-role overhaul — the shared machinery for a
package/dist/index.d.mts CHANGED
@@ -345,6 +345,13 @@ interface PasskeyLoginLabels {
345
345
  errorCancelled: string;
346
346
  /** Error banner when the ceremony failed. */
347
347
  errorFailed: string;
348
+ /**
349
+ * The "try another way" affordance shown after an in-page ceremony error — it
350
+ * hands off to the Keycloak-hosted passkey/password flow. This is the seam that
351
+ * rescues a user whose credential is only enrolled in Keycloak (not the in-page
352
+ * RP store): the in-page ceremony can't see it, so they fall back to KC.
353
+ */
354
+ tryAnotherWay: string;
348
355
  }
349
356
  /** Strings rendered by `<PasskeySettingsCard>` — the account passkey card. */
350
357
  interface PasskeySettingsLabels {
@@ -1022,16 +1029,18 @@ declare function DevicePinSettingsCard({ client, initialHasPin, theme: themeProp
1022
1029
  * The "Sign in with a passkey" button rendered on the unified login surface when
1023
1030
  * the BFF advertises the `passkey` method.
1024
1031
  *
1025
- * A passkey login is a full-page BROWSER NAVIGATION, not a fetch: tapping the
1026
- * button hands off to `/bff/passkey/login`, which drives the WebAuthn ceremony
1027
- * through Keycloak and returns the browser to `returnUrl` (default `'/'`, which
1028
- * routes an authenticated user to their dashboard) with a session cookie set. On
1029
- * a cancelled / failed ceremony the BFF bounces back to `?passkeyError=…`; we
1030
- * surface that as an inline error banner above the button.
1032
+ * The passkey login runs IN-PAGE via `usePasskeyCeremony`
1033
+ * (`navigator.credentials.get` against the same-origin `/bff/passkey/login/*`
1034
+ * fetch endpoints), landing the authenticated user on `returnUrl` (default `'/'`)
1035
+ * with a session cookie set. When the BFF's WebAuthn RP is not configured the
1036
+ * options endpoint returns 501 and the hook transparently falls back to the
1037
+ * legacy Keycloak redirect — so nothing regresses until the flag flips. A
1038
+ * cancelled / failed in-page ceremony surfaces as an inline error banner; a prior
1039
+ * redirect round-trip's `?passkeyError=…` is still honoured too.
1031
1040
  *
1032
- * react-query-free: it is pure `window.location` navigation (see
1033
- * `passkeyNavigation`), so it is safe to mount inside the login route group which
1034
- * has no QueryClient provider.
1041
+ * react-query-free: the hook uses plain `fetch` + `useState` (see
1042
+ * `usePasskeyCeremony`), so it is safe to mount inside the login route group
1043
+ * which has no QueryClient provider.
1035
1044
  */
1036
1045
 
1037
1046
  interface PasskeyLoginButtonProps {
@@ -1051,14 +1060,16 @@ declare function PasskeyLoginButton({ returnUrl, theme: themeProp, labels: label
1051
1060
  * The authenticated "Passkeys" settings card.
1052
1061
  *
1053
1062
  * Mount on an authenticated settings / account surface (alongside the device-PIN
1054
- * card). Adding a passkey is a full-page BROWSER NAVIGATION to
1055
- * `/bff/passkey/register`: Keycloak re-authenticates the user (password) then
1056
- * runs the WebAuthn credential-creation ceremony, returning the browser to the
1057
- * SAME settings page with `?passkey=registered` appended. On mount we read that
1058
- * param and, when present, show a success line.
1063
+ * card). Adding a passkey runs IN-PAGE via `usePasskeyCeremony`
1064
+ * (`navigator.credentials.create` against the same-origin
1065
+ * `/bff/passkey/register/*` fetch endpoints, session-cookie authenticated); on
1066
+ * success we show an inline success line — no navigation. When the BFF's WebAuthn
1067
+ * RP is not configured the options endpoint returns 501 and the hook falls back
1068
+ * to the legacy Keycloak redirect, which returns here with `?passkey=registered`;
1069
+ * that param is still honoured on mount, so nothing regresses until the flag flips.
1059
1070
  *
1060
- * react-query-free: the add action is pure `window.location` navigation (see
1061
- * `passkeyNavigation`); there is no fetch here. Mirrors `DevicePinSettingsCard`.
1071
+ * react-query-free: the hook uses plain `fetch` + `useState` (see
1072
+ * `usePasskeyCeremony`). Mirrors `DevicePinSettingsCard`.
1062
1073
  */
1063
1074
 
1064
1075
  interface PasskeySettingsCardProps {
@@ -1297,6 +1308,7 @@ declare const AuthTestIds: {
1297
1308
  readonly passkeyLogin: "auth-passkey-login";
1298
1309
  readonly passkeyLoginButton: "auth-passkey-login-button";
1299
1310
  readonly passkeyLoginError: "auth-passkey-login-error";
1311
+ readonly passkeyLoginFallback: "auth-passkey-login-fallback";
1300
1312
  readonly passkeySettings: "auth-passkey-settings";
1301
1313
  readonly passkeySettingsAdd: "auth-passkey-settings-add";
1302
1314
  readonly passkeySettingsSuccess: "auth-passkey-settings-success";
@@ -1644,6 +1656,44 @@ declare const DEVICE_PIN_DEFAULT_DIGITS = 4;
1644
1656
  /** Guard: is `value` one of the BFF-accepted PIN lengths? */
1645
1657
  declare function isAllowedPinDigits(value: number): boolean;
1646
1658
 
1659
+ /**
1660
+ * Which WebAuthn ceremony {@link usePasskeyCeremony} drives.
1661
+ *
1662
+ * `Login` runs a usernameless/discoverable assertion
1663
+ * (`navigator.credentials.get`) from the login surface; `Register` runs an
1664
+ * attestation (`navigator.credentials.create`) from an authenticated settings
1665
+ * surface. The mode selects the `/bff/passkey/{login,register}/*` endpoint pair
1666
+ * and the matching redirect fallback.
1667
+ */
1668
+ declare const enum PasskeyCeremonyMode {
1669
+ /** Assertion ceremony — sign in with an existing passkey. */
1670
+ Login = "login",
1671
+ /** Attestation ceremony — add a passkey to the signed-in account. */
1672
+ Register = "register"
1673
+ }
1674
+
1675
+ /**
1676
+ * The lifecycle state of an in-page passkey ceremony.
1677
+ *
1678
+ * `Idle` before the user acts; `Running` while options are fetched, the
1679
+ * authenticator prompts, and the assertion/attestation is verified; `Success`
1680
+ * once the BFF confirms (a login has a session cookie set, a registration has a
1681
+ * stored credential); `Error` on a genuine in-page failure (see
1682
+ * {@link PasskeyErrorCode}). A redirect fallback (RP not configured / unsupported
1683
+ * browser) navigates the page away, so the hook never lands in a terminal state
1684
+ * for that path.
1685
+ */
1686
+ declare const enum PasskeyCeremonyStatus {
1687
+ /** No ceremony has started. */
1688
+ Idle = "idle",
1689
+ /** A ceremony is in flight (fetch → authenticator → verify). */
1690
+ Running = "running",
1691
+ /** The ceremony completed and the BFF confirmed it. */
1692
+ Success = "success",
1693
+ /** The ceremony failed in-page (cancelled or failed). */
1694
+ Error = "error"
1695
+ }
1696
+
1647
1697
  /**
1648
1698
  * Navigation helpers for the passkey (WebAuthn) BFF endpoints.
1649
1699
  *
@@ -1700,6 +1750,44 @@ declare function readPasskeyError(): PasskeyErrorCode | null;
1700
1750
  */
1701
1751
  declare function readPasskeyRegistered(): boolean;
1702
1752
 
1753
+ /** Options for {@link usePasskeyCeremony}. */
1754
+ interface UsePasskeyCeremonyOptions {
1755
+ /** Which ceremony to run — login (assertion) or register (attestation). */
1756
+ mode: PasskeyCeremonyMode;
1757
+ /**
1758
+ * Relative app path used two ways: the redirect fallback returns here, and (for
1759
+ * login) a successful in-page ceremony navigates here unless `onSuccess` is set.
1760
+ */
1761
+ returnUrl: string;
1762
+ /**
1763
+ * Called on a successful in-page ceremony. For login it receives the signed-in
1764
+ * `BffUser`; for register it receives `undefined`. When omitted, a login
1765
+ * navigates to `returnUrl` and a register just resolves to `Success`.
1766
+ */
1767
+ onSuccess?: (user: BffUser | undefined) => void;
1768
+ }
1769
+ /** The state + controls {@link usePasskeyCeremony} returns. */
1770
+ interface UsePasskeyCeremonyResult {
1771
+ /** Current lifecycle state. */
1772
+ status: PasskeyCeremonyStatus;
1773
+ /** The in-page error code, or `null`. */
1774
+ error: PasskeyErrorCode | null;
1775
+ /** `true` when the browser can run the in-page ceremony (else it redirects). */
1776
+ isSupported: boolean;
1777
+ /** Start the ceremony (or redirect-fallback when the RP is off / unsupported). */
1778
+ start: () => void;
1779
+ /**
1780
+ * Hand off to the legacy Keycloak redirect explicitly. Wired to the "try
1781
+ * another way" affordance a surface shows after an in-page error — the escape
1782
+ * hatch for a credential enrolled only in Keycloak (invisible to the in-page
1783
+ * RP store), which the in-page ceremony cannot use.
1784
+ */
1785
+ fallbackToRedirect: () => void;
1786
+ /** Clear a terminal state so the affordance can be retried. */
1787
+ reset: () => void;
1788
+ }
1789
+ declare function usePasskeyCeremony(optionsArg: UsePasskeyCeremonyOptions): UsePasskeyCeremonyResult;
1790
+
1703
1791
  /**
1704
1792
  * Device-local cache of the user's preferred login method (unified-login D5).
1705
1793
  *
@@ -1906,4 +1994,4 @@ declare function validatePasswordPolicy(password: string): PasswordPolicyError[]
1906
1994
  /** `true` when the password satisfies every policy rule. */
1907
1995
  declare function isPasswordValid(password: string): boolean;
1908
1996
 
1909
- export { AuthScreen, type AuthScreenProps, AuthTestIds, type AuthTheme, type AuthThemeColors, AuthThemeProvider, type AuthThemeProviderProps, type AuthThemeRadii, type AuthThemeSpacing, type AuthThemeTypography, BffAuthStatus, type BffDeviceState, type BffLoginConfig, type CreateBffAuthClientOptions, DEFAULT_AUTH_CARD_MAX_WIDTH, DEFAULT_DEVICE_PIN_ENROLL_LABELS, DEFAULT_DEVICE_PIN_SETTINGS_LABELS, DEFAULT_DEVICE_PIN_UNLOCK_LABELS, DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS, DEFAULT_FORGOT_PASSWORD_LABELS, DEFAULT_LOGIN_LABELS, DEFAULT_OTP_LABELS, DEFAULT_PASSKEY_LOGIN_LABELS, DEFAULT_PASSKEY_SETTINGS_LABELS, DEFAULT_PIN_LABELS, DEFAULT_PREFERRED_METHOD_HINT_LABELS, DEFAULT_PREFERRED_METHOD_SETTINGS_LABELS, DEFAULT_RESET_PASSWORD_LABELS, DEVICE_PIN_ALLOWED_DIGITS, DEVICE_PIN_DEFAULT_DIGITS, type DevicePinCapableClient, type DevicePinDisableCapableClient, type DevicePinEnrollCapableClient, DevicePinEnrollErrorKey, DevicePinEnrollForm, type DevicePinEnrollFormProps, type DevicePinEnrollLabels, type DevicePinEnrollResult, DevicePinErrorKey, DevicePinInput, type DevicePinInputProps, DevicePinLengthPicker, type DevicePinLengthPickerProps, DevicePinOffer, type DevicePinOfferProps, DevicePinSettingsCard, type DevicePinSettingsCardProps, type DevicePinSettingsLabels, type DevicePinUnlockCapableClient, type DevicePinUnlockLabels, type DevicePinUnlockResult, DevicePinUnlockScreen, type DevicePinUnlockScreenProps, type DevicePinUnlockedUser, ForgotPasswordFields, type ForgotPasswordFieldsLabels, type ForgotPasswordFieldsProps, ForgotPasswordForm, type ForgotPasswordFormLabels, type ForgotPasswordFormProps, type LoginConfigCapableClient, LoginForm, type LoginFormLabels, type LoginFormProps, OtpForm, type OtpFormLabels, type OtpFormProps, OtpLoginStep, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, type PasskeyErrorCode, PasskeyLoginButton, type PasskeyLoginButtonProps, type PasskeyLoginLabels, PasskeySettingsCard, type PasskeySettingsCardProps, type PasskeySettingsLabels, PasswordPolicyError, PinForm, type PinFormLabels, type PinFormProps, type PreferencesClientOptions, type PreferredMethodClient, PreferredMethodHint, type PreferredMethodHintLabels, type PreferredMethodHintProps, PreferredMethodSettingsCard, type PreferredMethodSettingsCardProps, type PreferredMethodSettingsLabels, ResetPasswordError, ResetPasswordForm, type ResetPasswordFormLabels, type ResetPasswordFormProps, type RoleRoute, type RoleRouteTable, type UseBffAuthOptions, type UseBffAuthResult, type UseBffForgotPasswordOptions, type UseBffLoginConfigResult, type UseBffResetPasswordOptions, type UseDevicePinDisableArgs, type UseDevicePinDisableResult, type UseDevicePinEnrollArgs, type UseDevicePinEnrollResult, type UseDevicePinUnlockArgs, type UseDevicePinUnlockResult, type UseForgotPasswordSubmitArgs, type UseForgotPasswordSubmitResult, type UseOtpLoginOptions, type UseOtpLoginResult, type UsePinLoginOptions, type UsePinLoginResult, type UsePreferredMethodSettingArgs, type UsePreferredMethodSettingResult, type UseResetPasswordFormArgs, type UseResetPasswordFormResult, cachePreferredMethod, clearCachedPreferredMethod, collectUserRoles, createBffAuthClient, createPreferencesClient, defaultAuthTheme, isAllowedPinDigits, isPasswordValid, isValidForgotPasswordEmail, readCachedPreferredMethod, readPasskeyError, readPasskeyRegistered, resolveAccessibleRoutes, resolvePostLoginRoute, startPasskeyLogin, startPasskeyRegistration, syncPreferredMethodFromServer, useAuthTheme, useBffAuth, useBffForgotPassword, useBffLoginConfig, useBffResetPassword, useDevicePinDisable, useDevicePinEnroll, useDevicePinUnlock, useForgotPasswordSubmit, useOtpLogin, usePinLogin, usePreferredMethodSetting, useResetPasswordForm, validatePasswordPolicy, withTestIdPrefix };
1997
+ export { AuthScreen, type AuthScreenProps, AuthTestIds, type AuthTheme, type AuthThemeColors, AuthThemeProvider, type AuthThemeProviderProps, type AuthThemeRadii, type AuthThemeSpacing, type AuthThemeTypography, BffAuthStatus, type BffDeviceState, type BffLoginConfig, type CreateBffAuthClientOptions, DEFAULT_AUTH_CARD_MAX_WIDTH, DEFAULT_DEVICE_PIN_ENROLL_LABELS, DEFAULT_DEVICE_PIN_SETTINGS_LABELS, DEFAULT_DEVICE_PIN_UNLOCK_LABELS, DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS, DEFAULT_FORGOT_PASSWORD_LABELS, DEFAULT_LOGIN_LABELS, DEFAULT_OTP_LABELS, DEFAULT_PASSKEY_LOGIN_LABELS, DEFAULT_PASSKEY_SETTINGS_LABELS, DEFAULT_PIN_LABELS, DEFAULT_PREFERRED_METHOD_HINT_LABELS, DEFAULT_PREFERRED_METHOD_SETTINGS_LABELS, DEFAULT_RESET_PASSWORD_LABELS, DEVICE_PIN_ALLOWED_DIGITS, DEVICE_PIN_DEFAULT_DIGITS, type DevicePinCapableClient, type DevicePinDisableCapableClient, type DevicePinEnrollCapableClient, DevicePinEnrollErrorKey, DevicePinEnrollForm, type DevicePinEnrollFormProps, type DevicePinEnrollLabels, type DevicePinEnrollResult, DevicePinErrorKey, DevicePinInput, type DevicePinInputProps, DevicePinLengthPicker, type DevicePinLengthPickerProps, DevicePinOffer, type DevicePinOfferProps, DevicePinSettingsCard, type DevicePinSettingsCardProps, type DevicePinSettingsLabels, type DevicePinUnlockCapableClient, type DevicePinUnlockLabels, type DevicePinUnlockResult, DevicePinUnlockScreen, type DevicePinUnlockScreenProps, type DevicePinUnlockedUser, ForgotPasswordFields, type ForgotPasswordFieldsLabels, type ForgotPasswordFieldsProps, ForgotPasswordForm, type ForgotPasswordFormLabels, type ForgotPasswordFormProps, type LoginConfigCapableClient, LoginForm, type LoginFormLabels, type LoginFormProps, OtpForm, type OtpFormLabels, type OtpFormProps, OtpLoginStep, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, PasskeyCeremonyMode, PasskeyCeremonyStatus, type PasskeyErrorCode, PasskeyLoginButton, type PasskeyLoginButtonProps, type PasskeyLoginLabels, PasskeySettingsCard, type PasskeySettingsCardProps, type PasskeySettingsLabels, PasswordPolicyError, PinForm, type PinFormLabels, type PinFormProps, type PreferencesClientOptions, type PreferredMethodClient, PreferredMethodHint, type PreferredMethodHintLabels, type PreferredMethodHintProps, PreferredMethodSettingsCard, type PreferredMethodSettingsCardProps, type PreferredMethodSettingsLabels, ResetPasswordError, ResetPasswordForm, type ResetPasswordFormLabels, type ResetPasswordFormProps, type RoleRoute, type RoleRouteTable, type UseBffAuthOptions, type UseBffAuthResult, type UseBffForgotPasswordOptions, type UseBffLoginConfigResult, type UseBffResetPasswordOptions, type UseDevicePinDisableArgs, type UseDevicePinDisableResult, type UseDevicePinEnrollArgs, type UseDevicePinEnrollResult, type UseDevicePinUnlockArgs, type UseDevicePinUnlockResult, type UseForgotPasswordSubmitArgs, type UseForgotPasswordSubmitResult, type UseOtpLoginOptions, type UseOtpLoginResult, type UsePasskeyCeremonyOptions, type UsePasskeyCeremonyResult, type UsePinLoginOptions, type UsePinLoginResult, type UsePreferredMethodSettingArgs, type UsePreferredMethodSettingResult, type UseResetPasswordFormArgs, type UseResetPasswordFormResult, cachePreferredMethod, clearCachedPreferredMethod, collectUserRoles, createBffAuthClient, createPreferencesClient, defaultAuthTheme, isAllowedPinDigits, isPasswordValid, isValidForgotPasswordEmail, readCachedPreferredMethod, readPasskeyError, readPasskeyRegistered, resolveAccessibleRoutes, resolvePostLoginRoute, startPasskeyLogin, startPasskeyRegistration, syncPreferredMethodFromServer, useAuthTheme, useBffAuth, useBffForgotPassword, useBffLoginConfig, useBffResetPassword, useDevicePinDisable, useDevicePinEnroll, useDevicePinUnlock, useForgotPasswordSubmit, useOtpLogin, usePasskeyCeremony, usePinLogin, usePreferredMethodSetting, useResetPasswordForm, validatePasswordPolicy, withTestIdPrefix };
package/dist/index.d.ts CHANGED
@@ -345,6 +345,13 @@ interface PasskeyLoginLabels {
345
345
  errorCancelled: string;
346
346
  /** Error banner when the ceremony failed. */
347
347
  errorFailed: string;
348
+ /**
349
+ * The "try another way" affordance shown after an in-page ceremony error — it
350
+ * hands off to the Keycloak-hosted passkey/password flow. This is the seam that
351
+ * rescues a user whose credential is only enrolled in Keycloak (not the in-page
352
+ * RP store): the in-page ceremony can't see it, so they fall back to KC.
353
+ */
354
+ tryAnotherWay: string;
348
355
  }
349
356
  /** Strings rendered by `<PasskeySettingsCard>` — the account passkey card. */
350
357
  interface PasskeySettingsLabels {
@@ -1022,16 +1029,18 @@ declare function DevicePinSettingsCard({ client, initialHasPin, theme: themeProp
1022
1029
  * The "Sign in with a passkey" button rendered on the unified login surface when
1023
1030
  * the BFF advertises the `passkey` method.
1024
1031
  *
1025
- * A passkey login is a full-page BROWSER NAVIGATION, not a fetch: tapping the
1026
- * button hands off to `/bff/passkey/login`, which drives the WebAuthn ceremony
1027
- * through Keycloak and returns the browser to `returnUrl` (default `'/'`, which
1028
- * routes an authenticated user to their dashboard) with a session cookie set. On
1029
- * a cancelled / failed ceremony the BFF bounces back to `?passkeyError=…`; we
1030
- * surface that as an inline error banner above the button.
1032
+ * The passkey login runs IN-PAGE via `usePasskeyCeremony`
1033
+ * (`navigator.credentials.get` against the same-origin `/bff/passkey/login/*`
1034
+ * fetch endpoints), landing the authenticated user on `returnUrl` (default `'/'`)
1035
+ * with a session cookie set. When the BFF's WebAuthn RP is not configured the
1036
+ * options endpoint returns 501 and the hook transparently falls back to the
1037
+ * legacy Keycloak redirect — so nothing regresses until the flag flips. A
1038
+ * cancelled / failed in-page ceremony surfaces as an inline error banner; a prior
1039
+ * redirect round-trip's `?passkeyError=…` is still honoured too.
1031
1040
  *
1032
- * react-query-free: it is pure `window.location` navigation (see
1033
- * `passkeyNavigation`), so it is safe to mount inside the login route group which
1034
- * has no QueryClient provider.
1041
+ * react-query-free: the hook uses plain `fetch` + `useState` (see
1042
+ * `usePasskeyCeremony`), so it is safe to mount inside the login route group
1043
+ * which has no QueryClient provider.
1035
1044
  */
1036
1045
 
1037
1046
  interface PasskeyLoginButtonProps {
@@ -1051,14 +1060,16 @@ declare function PasskeyLoginButton({ returnUrl, theme: themeProp, labels: label
1051
1060
  * The authenticated "Passkeys" settings card.
1052
1061
  *
1053
1062
  * Mount on an authenticated settings / account surface (alongside the device-PIN
1054
- * card). Adding a passkey is a full-page BROWSER NAVIGATION to
1055
- * `/bff/passkey/register`: Keycloak re-authenticates the user (password) then
1056
- * runs the WebAuthn credential-creation ceremony, returning the browser to the
1057
- * SAME settings page with `?passkey=registered` appended. On mount we read that
1058
- * param and, when present, show a success line.
1063
+ * card). Adding a passkey runs IN-PAGE via `usePasskeyCeremony`
1064
+ * (`navigator.credentials.create` against the same-origin
1065
+ * `/bff/passkey/register/*` fetch endpoints, session-cookie authenticated); on
1066
+ * success we show an inline success line — no navigation. When the BFF's WebAuthn
1067
+ * RP is not configured the options endpoint returns 501 and the hook falls back
1068
+ * to the legacy Keycloak redirect, which returns here with `?passkey=registered`;
1069
+ * that param is still honoured on mount, so nothing regresses until the flag flips.
1059
1070
  *
1060
- * react-query-free: the add action is pure `window.location` navigation (see
1061
- * `passkeyNavigation`); there is no fetch here. Mirrors `DevicePinSettingsCard`.
1071
+ * react-query-free: the hook uses plain `fetch` + `useState` (see
1072
+ * `usePasskeyCeremony`). Mirrors `DevicePinSettingsCard`.
1062
1073
  */
1063
1074
 
1064
1075
  interface PasskeySettingsCardProps {
@@ -1297,6 +1308,7 @@ declare const AuthTestIds: {
1297
1308
  readonly passkeyLogin: "auth-passkey-login";
1298
1309
  readonly passkeyLoginButton: "auth-passkey-login-button";
1299
1310
  readonly passkeyLoginError: "auth-passkey-login-error";
1311
+ readonly passkeyLoginFallback: "auth-passkey-login-fallback";
1300
1312
  readonly passkeySettings: "auth-passkey-settings";
1301
1313
  readonly passkeySettingsAdd: "auth-passkey-settings-add";
1302
1314
  readonly passkeySettingsSuccess: "auth-passkey-settings-success";
@@ -1644,6 +1656,44 @@ declare const DEVICE_PIN_DEFAULT_DIGITS = 4;
1644
1656
  /** Guard: is `value` one of the BFF-accepted PIN lengths? */
1645
1657
  declare function isAllowedPinDigits(value: number): boolean;
1646
1658
 
1659
+ /**
1660
+ * Which WebAuthn ceremony {@link usePasskeyCeremony} drives.
1661
+ *
1662
+ * `Login` runs a usernameless/discoverable assertion
1663
+ * (`navigator.credentials.get`) from the login surface; `Register` runs an
1664
+ * attestation (`navigator.credentials.create`) from an authenticated settings
1665
+ * surface. The mode selects the `/bff/passkey/{login,register}/*` endpoint pair
1666
+ * and the matching redirect fallback.
1667
+ */
1668
+ declare const enum PasskeyCeremonyMode {
1669
+ /** Assertion ceremony — sign in with an existing passkey. */
1670
+ Login = "login",
1671
+ /** Attestation ceremony — add a passkey to the signed-in account. */
1672
+ Register = "register"
1673
+ }
1674
+
1675
+ /**
1676
+ * The lifecycle state of an in-page passkey ceremony.
1677
+ *
1678
+ * `Idle` before the user acts; `Running` while options are fetched, the
1679
+ * authenticator prompts, and the assertion/attestation is verified; `Success`
1680
+ * once the BFF confirms (a login has a session cookie set, a registration has a
1681
+ * stored credential); `Error` on a genuine in-page failure (see
1682
+ * {@link PasskeyErrorCode}). A redirect fallback (RP not configured / unsupported
1683
+ * browser) navigates the page away, so the hook never lands in a terminal state
1684
+ * for that path.
1685
+ */
1686
+ declare const enum PasskeyCeremonyStatus {
1687
+ /** No ceremony has started. */
1688
+ Idle = "idle",
1689
+ /** A ceremony is in flight (fetch → authenticator → verify). */
1690
+ Running = "running",
1691
+ /** The ceremony completed and the BFF confirmed it. */
1692
+ Success = "success",
1693
+ /** The ceremony failed in-page (cancelled or failed). */
1694
+ Error = "error"
1695
+ }
1696
+
1647
1697
  /**
1648
1698
  * Navigation helpers for the passkey (WebAuthn) BFF endpoints.
1649
1699
  *
@@ -1700,6 +1750,44 @@ declare function readPasskeyError(): PasskeyErrorCode | null;
1700
1750
  */
1701
1751
  declare function readPasskeyRegistered(): boolean;
1702
1752
 
1753
+ /** Options for {@link usePasskeyCeremony}. */
1754
+ interface UsePasskeyCeremonyOptions {
1755
+ /** Which ceremony to run — login (assertion) or register (attestation). */
1756
+ mode: PasskeyCeremonyMode;
1757
+ /**
1758
+ * Relative app path used two ways: the redirect fallback returns here, and (for
1759
+ * login) a successful in-page ceremony navigates here unless `onSuccess` is set.
1760
+ */
1761
+ returnUrl: string;
1762
+ /**
1763
+ * Called on a successful in-page ceremony. For login it receives the signed-in
1764
+ * `BffUser`; for register it receives `undefined`. When omitted, a login
1765
+ * navigates to `returnUrl` and a register just resolves to `Success`.
1766
+ */
1767
+ onSuccess?: (user: BffUser | undefined) => void;
1768
+ }
1769
+ /** The state + controls {@link usePasskeyCeremony} returns. */
1770
+ interface UsePasskeyCeremonyResult {
1771
+ /** Current lifecycle state. */
1772
+ status: PasskeyCeremonyStatus;
1773
+ /** The in-page error code, or `null`. */
1774
+ error: PasskeyErrorCode | null;
1775
+ /** `true` when the browser can run the in-page ceremony (else it redirects). */
1776
+ isSupported: boolean;
1777
+ /** Start the ceremony (or redirect-fallback when the RP is off / unsupported). */
1778
+ start: () => void;
1779
+ /**
1780
+ * Hand off to the legacy Keycloak redirect explicitly. Wired to the "try
1781
+ * another way" affordance a surface shows after an in-page error — the escape
1782
+ * hatch for a credential enrolled only in Keycloak (invisible to the in-page
1783
+ * RP store), which the in-page ceremony cannot use.
1784
+ */
1785
+ fallbackToRedirect: () => void;
1786
+ /** Clear a terminal state so the affordance can be retried. */
1787
+ reset: () => void;
1788
+ }
1789
+ declare function usePasskeyCeremony(optionsArg: UsePasskeyCeremonyOptions): UsePasskeyCeremonyResult;
1790
+
1703
1791
  /**
1704
1792
  * Device-local cache of the user's preferred login method (unified-login D5).
1705
1793
  *
@@ -1906,4 +1994,4 @@ declare function validatePasswordPolicy(password: string): PasswordPolicyError[]
1906
1994
  /** `true` when the password satisfies every policy rule. */
1907
1995
  declare function isPasswordValid(password: string): boolean;
1908
1996
 
1909
- export { AuthScreen, type AuthScreenProps, AuthTestIds, type AuthTheme, type AuthThemeColors, AuthThemeProvider, type AuthThemeProviderProps, type AuthThemeRadii, type AuthThemeSpacing, type AuthThemeTypography, BffAuthStatus, type BffDeviceState, type BffLoginConfig, type CreateBffAuthClientOptions, DEFAULT_AUTH_CARD_MAX_WIDTH, DEFAULT_DEVICE_PIN_ENROLL_LABELS, DEFAULT_DEVICE_PIN_SETTINGS_LABELS, DEFAULT_DEVICE_PIN_UNLOCK_LABELS, DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS, DEFAULT_FORGOT_PASSWORD_LABELS, DEFAULT_LOGIN_LABELS, DEFAULT_OTP_LABELS, DEFAULT_PASSKEY_LOGIN_LABELS, DEFAULT_PASSKEY_SETTINGS_LABELS, DEFAULT_PIN_LABELS, DEFAULT_PREFERRED_METHOD_HINT_LABELS, DEFAULT_PREFERRED_METHOD_SETTINGS_LABELS, DEFAULT_RESET_PASSWORD_LABELS, DEVICE_PIN_ALLOWED_DIGITS, DEVICE_PIN_DEFAULT_DIGITS, type DevicePinCapableClient, type DevicePinDisableCapableClient, type DevicePinEnrollCapableClient, DevicePinEnrollErrorKey, DevicePinEnrollForm, type DevicePinEnrollFormProps, type DevicePinEnrollLabels, type DevicePinEnrollResult, DevicePinErrorKey, DevicePinInput, type DevicePinInputProps, DevicePinLengthPicker, type DevicePinLengthPickerProps, DevicePinOffer, type DevicePinOfferProps, DevicePinSettingsCard, type DevicePinSettingsCardProps, type DevicePinSettingsLabels, type DevicePinUnlockCapableClient, type DevicePinUnlockLabels, type DevicePinUnlockResult, DevicePinUnlockScreen, type DevicePinUnlockScreenProps, type DevicePinUnlockedUser, ForgotPasswordFields, type ForgotPasswordFieldsLabels, type ForgotPasswordFieldsProps, ForgotPasswordForm, type ForgotPasswordFormLabels, type ForgotPasswordFormProps, type LoginConfigCapableClient, LoginForm, type LoginFormLabels, type LoginFormProps, OtpForm, type OtpFormLabels, type OtpFormProps, OtpLoginStep, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, type PasskeyErrorCode, PasskeyLoginButton, type PasskeyLoginButtonProps, type PasskeyLoginLabels, PasskeySettingsCard, type PasskeySettingsCardProps, type PasskeySettingsLabels, PasswordPolicyError, PinForm, type PinFormLabels, type PinFormProps, type PreferencesClientOptions, type PreferredMethodClient, PreferredMethodHint, type PreferredMethodHintLabels, type PreferredMethodHintProps, PreferredMethodSettingsCard, type PreferredMethodSettingsCardProps, type PreferredMethodSettingsLabels, ResetPasswordError, ResetPasswordForm, type ResetPasswordFormLabels, type ResetPasswordFormProps, type RoleRoute, type RoleRouteTable, type UseBffAuthOptions, type UseBffAuthResult, type UseBffForgotPasswordOptions, type UseBffLoginConfigResult, type UseBffResetPasswordOptions, type UseDevicePinDisableArgs, type UseDevicePinDisableResult, type UseDevicePinEnrollArgs, type UseDevicePinEnrollResult, type UseDevicePinUnlockArgs, type UseDevicePinUnlockResult, type UseForgotPasswordSubmitArgs, type UseForgotPasswordSubmitResult, type UseOtpLoginOptions, type UseOtpLoginResult, type UsePinLoginOptions, type UsePinLoginResult, type UsePreferredMethodSettingArgs, type UsePreferredMethodSettingResult, type UseResetPasswordFormArgs, type UseResetPasswordFormResult, cachePreferredMethod, clearCachedPreferredMethod, collectUserRoles, createBffAuthClient, createPreferencesClient, defaultAuthTheme, isAllowedPinDigits, isPasswordValid, isValidForgotPasswordEmail, readCachedPreferredMethod, readPasskeyError, readPasskeyRegistered, resolveAccessibleRoutes, resolvePostLoginRoute, startPasskeyLogin, startPasskeyRegistration, syncPreferredMethodFromServer, useAuthTheme, useBffAuth, useBffForgotPassword, useBffLoginConfig, useBffResetPassword, useDevicePinDisable, useDevicePinEnroll, useDevicePinUnlock, useForgotPasswordSubmit, useOtpLogin, usePinLogin, usePreferredMethodSetting, useResetPasswordForm, validatePasswordPolicy, withTestIdPrefix };
1997
+ export { AuthScreen, type AuthScreenProps, AuthTestIds, type AuthTheme, type AuthThemeColors, AuthThemeProvider, type AuthThemeProviderProps, type AuthThemeRadii, type AuthThemeSpacing, type AuthThemeTypography, BffAuthStatus, type BffDeviceState, type BffLoginConfig, type CreateBffAuthClientOptions, DEFAULT_AUTH_CARD_MAX_WIDTH, DEFAULT_DEVICE_PIN_ENROLL_LABELS, DEFAULT_DEVICE_PIN_SETTINGS_LABELS, DEFAULT_DEVICE_PIN_UNLOCK_LABELS, DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS, DEFAULT_FORGOT_PASSWORD_LABELS, DEFAULT_LOGIN_LABELS, DEFAULT_OTP_LABELS, DEFAULT_PASSKEY_LOGIN_LABELS, DEFAULT_PASSKEY_SETTINGS_LABELS, DEFAULT_PIN_LABELS, DEFAULT_PREFERRED_METHOD_HINT_LABELS, DEFAULT_PREFERRED_METHOD_SETTINGS_LABELS, DEFAULT_RESET_PASSWORD_LABELS, DEVICE_PIN_ALLOWED_DIGITS, DEVICE_PIN_DEFAULT_DIGITS, type DevicePinCapableClient, type DevicePinDisableCapableClient, type DevicePinEnrollCapableClient, DevicePinEnrollErrorKey, DevicePinEnrollForm, type DevicePinEnrollFormProps, type DevicePinEnrollLabels, type DevicePinEnrollResult, DevicePinErrorKey, DevicePinInput, type DevicePinInputProps, DevicePinLengthPicker, type DevicePinLengthPickerProps, DevicePinOffer, type DevicePinOfferProps, DevicePinSettingsCard, type DevicePinSettingsCardProps, type DevicePinSettingsLabels, type DevicePinUnlockCapableClient, type DevicePinUnlockLabels, type DevicePinUnlockResult, DevicePinUnlockScreen, type DevicePinUnlockScreenProps, type DevicePinUnlockedUser, ForgotPasswordFields, type ForgotPasswordFieldsLabels, type ForgotPasswordFieldsProps, ForgotPasswordForm, type ForgotPasswordFormLabels, type ForgotPasswordFormProps, type LoginConfigCapableClient, LoginForm, type LoginFormLabels, type LoginFormProps, OtpForm, type OtpFormLabels, type OtpFormProps, OtpLoginStep, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, PasskeyCeremonyMode, PasskeyCeremonyStatus, type PasskeyErrorCode, PasskeyLoginButton, type PasskeyLoginButtonProps, type PasskeyLoginLabels, PasskeySettingsCard, type PasskeySettingsCardProps, type PasskeySettingsLabels, PasswordPolicyError, PinForm, type PinFormLabels, type PinFormProps, type PreferencesClientOptions, type PreferredMethodClient, PreferredMethodHint, type PreferredMethodHintLabels, type PreferredMethodHintProps, PreferredMethodSettingsCard, type PreferredMethodSettingsCardProps, type PreferredMethodSettingsLabels, ResetPasswordError, ResetPasswordForm, type ResetPasswordFormLabels, type ResetPasswordFormProps, type RoleRoute, type RoleRouteTable, type UseBffAuthOptions, type UseBffAuthResult, type UseBffForgotPasswordOptions, type UseBffLoginConfigResult, type UseBffResetPasswordOptions, type UseDevicePinDisableArgs, type UseDevicePinDisableResult, type UseDevicePinEnrollArgs, type UseDevicePinEnrollResult, type UseDevicePinUnlockArgs, type UseDevicePinUnlockResult, type UseForgotPasswordSubmitArgs, type UseForgotPasswordSubmitResult, type UseOtpLoginOptions, type UseOtpLoginResult, type UsePasskeyCeremonyOptions, type UsePasskeyCeremonyResult, type UsePinLoginOptions, type UsePinLoginResult, type UsePreferredMethodSettingArgs, type UsePreferredMethodSettingResult, type UseResetPasswordFormArgs, type UseResetPasswordFormResult, cachePreferredMethod, clearCachedPreferredMethod, collectUserRoles, createBffAuthClient, createPreferencesClient, defaultAuthTheme, isAllowedPinDigits, isPasswordValid, isValidForgotPasswordEmail, readCachedPreferredMethod, readPasskeyError, readPasskeyRegistered, resolveAccessibleRoutes, resolvePostLoginRoute, startPasskeyLogin, startPasskeyRegistration, syncPreferredMethodFromServer, useAuthTheme, useBffAuth, useBffForgotPassword, useBffLoginConfig, useBffResetPassword, useDevicePinDisable, useDevicePinEnroll, useDevicePinUnlock, useForgotPasswordSubmit, useOtpLogin, usePasskeyCeremony, usePinLogin, usePreferredMethodSetting, useResetPasswordForm, validatePasswordPolicy, withTestIdPrefix };