IS-11252 WebAuthn client operations in the LWA#154
Conversation
Implements the webauthn-registration and webauthn-authentication HAAPI client operations, including any-device-mode that may offer the user a choice between platform and cross-platform credentials. The feature comes with a folder restructure: per-operation modules live under a new operations/ subfolder, with the previous client-operations.ts god-file slimmed to a thin dispatcher. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Any-device-mode `webauthn-registration` actions split into two siblings now read e.g. "Register new device (This device)" / "Register new device (Another device)" instead of just the device label, preserving the server-supplied original title. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds full HAAPI WebAuthn client-operation support (registration + authentication), including any-device mode that renders separate “platform” vs “cross-platform” choices, and refactors the client-operation implementation into per-operation modules.
Changes:
- Add WebAuthn registration/authentication runners, plus action-splitting for any-device registration so the UI can render one button per option.
- Introduce runtime capability gating in the default client-operation UI (disable WebAuthn actions when unsupported / when platform authenticator is unavailable for platform-only any-device registration).
- Refactor client-operation code into
feature/actions/client-operation/operations/*and update imports/exports/tests accordingly.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/login-web-app/src/haapi-stepper/feature/stepper/HaapiStepper.tsx | Updates client-operation import to the refactored operations entrypoint. |
| src/login-web-app/src/haapi-stepper/feature/stepper/HaapiStepper.spec.tsx | Updates BankID app opener mocking path after refactor. |
| src/login-web-app/src/haapi-stepper/feature/stepper/data-formatters/polling-step.ts | Uses new BankID operation exports for polling autostart behavior. |
| src/login-web-app/src/haapi-stepper/feature/stepper/data-formatters/format-next-step-data.ts | Adds WebAuthn registration action splitting to emit one action per credential option. |
| src/login-web-app/src/haapi-stepper/feature/index.ts | Re-exports refactored client-operations entrypoint. |
| src/login-web-app/src/haapi-stepper/feature/actions/client-operation/HaapiStepperClientOperationUI.tsx | Disables client-operation button based on runtime availability; removes render prop. |
| src/login-web-app/src/haapi-stepper/feature/actions/client-operation/HaapiStepperClientOperationUI.spec.tsx | Adds tests for default rendering and WebAuthn any-device split integration. |
| src/login-web-app/src/haapi-stepper/feature/actions/client-operation/useIsClientOperationAvailable.ts | New availability hook for capability-gating client-operation actions. |
| src/login-web-app/src/haapi-stepper/feature/actions/client-operation/operations/client-operations.ts | New dispatcher for client operations (BankID, external browser flow, WebAuthn). |
| src/login-web-app/src/haapi-stepper/feature/actions/client-operation/operations/external-browser-flow.ts | Extracted external-browser-flow runner and type guard. |
| src/login-web-app/src/haapi-stepper/feature/actions/client-operation/operations/bankid/* | Extracted BankID operation (runner, opener, type guard). |
| src/login-web-app/src/haapi-stepper/feature/actions/client-operation/operations/webauthn/* | Implements WebAuthn runners, action splitting helpers, and platform authenticator availability hook. |
| src/login-web-app/src/haapi-stepper/feature/actions/client-operation/openBankIdApp.ts | Removed old BankID opener (moved into operations/bankid). |
| src/login-web-app/src/haapi-stepper/feature/actions/client-operation/client-operations.ts | Removed monolithic client-operations implementation (replaced by per-operation modules). |
| src/login-web-app/src/haapi-stepper/data-access/types/haapi-action.types.ts | Refines WebAuthn registration typing and introduces selected-option enum. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Agent-Logs-Url: https://github.com/curityio/ui-kit/sessions/d01d151a-f43f-4115-bfa2-9a929963ff8a Co-authored-by: aleixsuau <25689432+aleixsuau@users.noreply.github.com>
|
Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details. Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
Extract `createMockExternalBrowserFlowAction`, `createMockBankIdAction`, `createMockWebAuthnRegistrationAction`, `createMockWebAuthnAnyDeviceBothOptionsAction`, and `createMockWebAuthnPlatformOnlyAnyDeviceAction` (plus their default-title constants) from the spec into the shared `util/tests/mocks.ts`, so future specs can reuse them. The spec drops 114 lines of local helpers and relies on imports. `stubPublicKeyCredential` stays in the spec — it's a global-API stub builder, not a HAAPI-data factory. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Consolidate the four WebAuthn tests (enables, disables on API absent,
disables on missing platform authenticator, splits into two buttons)
into one flat `describe('WebAuthn')` block. Each test sets up only what
it needs; a single `afterEach` cleans up `vi.stubGlobal` and the mocked
`useIsWebAuthnPlatformAuthenticatorAvailable` hook.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The JSDoc examples in `HaapiStepper.tsx` and `HaapiStepperSelectorUI.tsx`
referenced fields that don't exist on `dataHelpers` (`formActions`,
`selectorActions`, `clientOperationActions`). Updated to the real
`actions.{form,selector,clientOperation}` shape so consumers can
copy/paste them.
Also swapped the example `key` props from `action.kind` / `link.rel`
(neither unique) to `action.id` / `link.id`, matching the production
factory in `defaultHaapiStepperActionElementFactory.tsx`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
luisgoncalves
left a comment
There was a problem hiding this comment.
The approach in this PR is a nice trick, and I think we can stick with it for now.
However, I'm starting to wonder if we should treat each client operation as a specific thing, having it's own component. Almost as if we "flattened" client operations to the top level, having form, selector, webauthn, bankid.
The reason I'm saying this is twofold:
- The UI for a client operation can be very specific and it always requires some corresponding "backing logic". We currently have this logic inner in the code, instead of being already split from the component needing it.
- Client operations are an extension point, so, although unlikely, custom plugins could define new ones. In this case it doesn't make sense to show any UI by default, as we can't know what the operation means.
Splitting would probably allow removing a smell that we currently have in HaapiStepperClientOperationUI - showBankIdSessionTimeLeft, i.e. BankID-specifics.
Anyway, lets discuss this separately, if you think it's worth it.
--
I also smoke tested locally and it looks good 👍
I see what you mean, but I don't think it is an issue at this point: 1 - If the default UI doesn't match, the consumer can always use the That said, I don't see any arm in creating 2 - This seems an important point because we are throwing if the client operation is not one of the allowed options (bankid, webAuthn, and external browser flow). Should we handle this case as you said? It seems not showing any UI would break the current contract 🤔 |
I don't think it makes so much sense, but this isn't very important either.
It would allow removing the trick in this PR (flattening client operation), because the component for "webauthn-registration" would know how to display the different options. If we also do that, I agree with splitting and I think it would be a good thing. If not, than Idk if we need to do it now.
You're right, not showing anything (and not throwing) would most likely result (silently) in a weird UI. So, lets leave it as is. Interceptor can be used for this. |
The WebAuth Client Operation kind of "breaks" the contract because it might provide 2 "actions" the user needs to choose from (like a selector), while the rest of the client operations and form actions provide/represent 1. This seems a data "issue" to me, which is why I solved it at the data level. |
…ion.origin `new URL(action.model.arguments.href)` throws if `href` is ever relative. The server normally sends an absolute URL for external-browser-flow, but parsing with `window.location.origin` as the base keeps the runner safe against malformed payloads and makes test setups simpler. Suggested by Luis on Copilot review of PR #154. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The any-device-mode split was labelling the two buttons "This device" and "Another device", which reads as a phone/laptop distinction. The real distinction is between the platform authenticator (built-in biometric) and a cross-platform authenticator (FIDO2 security key) — matching the Velocity templates. Suggested by Luis on PR #154. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The action factory was keying form and selector elements by `action.model.href` and `action.title` respectively — neither is guaranteed unique. Switch both to `action.id` (the canonical unique identifier, already used for client-operation), matching what Luis suggested on PR #154. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…2/webauthn-client-operations
…ests Brings the feature branch up to date with `integration/IS-5161/login-web-app` (new viewnames system, step-element factories, isQrCodeLink utility, HaapiStepperStepUI refactor, README + spec updates). Also fixes a test-environment regression where `mocks.ts` and `bankid/open-bankid-app.ts` were importing from the `data-access` barrel, which transitively loads `bootstrap-configuration` and throws `'Configuration not set'` when `window.__CONFIG__` is unset. Switched both to deep imports from `data-access/types/...`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ca19214 to
3abff38
Compare
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Jira: https://curity.atlassian.net/browse/IS-11252
Adds full support for
webauthn-registrationandwebauthn-authenticationHAAPI client operations, including the any-device-mode where the user picks between platform and cross-platform credentials.Part 1 — WebAuthn pipeline
1. Format — split the action.
formatNextStepDataruns eachwebauthn-registrationaction throughsplitWebAuthnRegistrationAction. Any-device-mode with both options becomes two sibling actions, each titled "This device" / "Another device". Single-option and passkeys-mode pass through.2. Render — one button per option, capability-gated. The default rendering pipeline produces one button per emitted action.
useIsClientOperationAvailabledisables it when the WebAuthn API is missing, or — for platform-only any-device registration — when no user-verifying platform authenticator is available.3. Click — run the ceremony.
performClientOperationdispatches torunWebAuthnRegistration(callsnavigator.credentials.create(), serialises under the matchingcredential/platformCredential/crossPlatformCredentialpayload key) orrunWebAuthnAuthentication(callsnavigator.credentials.get(), serialises undercredential). Both honour theAbortSignaland resume the flow viacontinueActions[0].Part 2 — Folder refactor (no behaviour change)
feature/actions/client-operation/reorganized so each operation owns its own folder/file as webauthn.Test plan
webauthn-authentication— ceremony completesFollow-ups (separate PRs)
errorActionsor in-flow user feedback yet.bankIdAutostart: a config flag to start the ceremony immediately on step entry.