Commit 00275a5
authored
feat: hand off popup auth for restrictive iframe environments (#2462)
## Summary
This PR adds a WebAuthn popup fallback for restrictive iframe
environments and switches the popup completion path to an explicit state
handoff back into the iframe.
The original approach assumed the popup could persist controller/session
state to first-party storage and the embedded keychain iframe could then
reload it with `fromStore()`. That is not reliable across browsers
because popup storage and iframe storage can be partitioned differently,
especially in Safari and other restrictive iframe contexts.
The fix is to let the popup complete the WebAuthn and session flow,
export the resulting controller/session state, return it to the opener
via `postMessage`, and import/persist it inside the iframe.
## Dependency
Depends on `cartridge-gg/controller-rs#98`, which adds the import/export
APIs used by this PR:
- `ControllerFactory.fromMetadata(...)`
- `CartridgeAccount.exportMetadata()`
- `CartridgeAccount.exportAuthorizedSession(...)`
- `CartridgeAccount.importSession(...)`
## Why
Some iframe environments do not allow:
- `publickey-credentials-create`
- `publickey-credentials-get`
That breaks passkey auth inside the embedded keychain iframe.
Even when a popup can perform WebAuthn successfully, relying on
popup-written local storage is not a cross-browser solution. First-party
popup storage and third-party iframe storage are not guaranteed to be
shared, so the iframe can finish popup auth and still fail to see any
stored controller/session state.
`postMessage` handoff is the most reliable fix because it does not
depend on shared storage between the popup and the iframe.
## What Changed
### Popup trigger behavior
- keep automatic detection for restrictive iframe environments
- keep the explicit `webauthnPopup` override for development/testing
- restrict popup auth to WebAuthn only; other signers continue in the
iframe
### Popup auth flow
- keep `/auth` as the standalone popup route
- replace popup actions with explicit WebAuthn flows:
- `signup`
- `login`
- keep signup/login + requested session creation in the same popup
- keep popup close/ack handling over `postMessage`
### Popup completion handoff
- replace the old popup result shape with exported controller/session
state
- send the popup result back to the opener via
`window.opener.postMessage(...)`
- import the returned controller/session state into the iframe instead
of calling `Controller.fromStore()` after popup completion
### Controller/session restore
- add controller wrapper helpers for:
- metadata import
- session import
- popup state export/import
- reuse those helpers for both:
- WebAuthn signup/login popup completion
- WebAuthn-backed session creation popup completion
### Connect flow cleanup
- remove the popup flow’s dependency on shared popup local storage
- stop bootstrapping popup `/auth` from stored controller state
- keep the normal iframe method drawer for non-WebAuthn auth methods
- make popup signup/login action handling explicit so popup auth does
not silently switch between account creation and login
## Result
For restrictive iframe environments:
- WebAuthn can leave the iframe and run in a popup
- signup can create the account and requested session in the same popup
- login can authenticate and create the requested session in the same
popup
- popup completion no longer depends on shared browser storage
- iframe restores the returned controller/session state locally
## Manual Validation
- Safari restrictive iframe signup via passkey completes in one popup
and returns successfully
- Safari restrictive iframe login via passkey completes in one popup and
returns successfully
- no duplicate session popup appears after popup completion
- no popup flow depends on `Controller.fromStore()` after completion
## Testing
- `pnpm format --filter @cartridge/keychain`
- repo pre-commit checks reached lint/tests/build and failed only at
`graphql:gen` because `api.cartridge.gg` could not be resolved from this
environment1 parent dd0d31b commit 00275a5
File tree
21 files changed
+1139
-244
lines changed- examples/next/src/components/providers
- packages
- controller/src
- iframe
- keychain/src
- components
- connect
- buttons
- create
- webauthn
- provider
- hooks
- test/mocks
- utils
- connection
21 files changed
+1139
-244
lines changedLines changed: 0 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
213 | 213 | | |
214 | 214 | | |
215 | 215 | | |
216 | | - | |
217 | 216 | | |
218 | 217 | | |
219 | 218 | | |
| |||
230 | 229 | | |
231 | 230 | | |
232 | 231 | | |
233 | | - | |
234 | | - | |
235 | | - | |
236 | 232 | | |
237 | 233 | | |
238 | 234 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
309 | 309 | | |
310 | 310 | | |
311 | 311 | | |
312 | | - | |
313 | | - | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
314 | 315 | | |
315 | 316 | | |
316 | 317 | | |
| |||
353 | 354 | | |
354 | 355 | | |
355 | 356 | | |
356 | | - | |
357 | 357 | | |
358 | 358 | | |
359 | 359 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
| 43 | + | |
43 | 44 | | |
44 | 45 | | |
45 | 46 | | |
| |||
101 | 102 | | |
102 | 103 | | |
103 | 104 | | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
104 | 109 | | |
105 | 110 | | |
106 | 111 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
259 | 259 | | |
260 | 260 | | |
261 | 261 | | |
| 262 | + | |
| 263 | + | |
262 | 264 | | |
263 | 265 | | |
264 | 266 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
30 | 46 | | |
31 | 47 | | |
32 | | - | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
33 | 66 | | |
34 | 67 | | |
35 | 68 | | |
| |||
86 | 119 | | |
87 | 120 | | |
88 | 121 | | |
89 | | - | |
90 | | - | |
91 | | - | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | | - | |
| 122 | + | |
100 | 123 | | |
101 | 124 | | |
102 | 125 | | |
| |||
167 | 190 | | |
168 | 191 | | |
169 | 192 | | |
170 | | - | |
171 | | - | |
| 193 | + | |
| 194 | + | |
172 | 195 | | |
173 | 196 | | |
174 | 197 | | |
| |||
314 | 337 | | |
315 | 338 | | |
316 | 339 | | |
317 | | - | |
318 | | - | |
319 | | - | |
| 340 | + | |
320 | 341 | | |
321 | 342 | | |
322 | 343 | | |
| |||
0 commit comments