Move Passkey Allowed Origin Configuration to Application-Level Configuration#4331
Move Passkey Allowed Origin Configuration to Application-Level Configuration#4331NutharaNR wants to merge 1 commit into
Conversation
9c46d44 to
2d8c0d0
Compare
📝 WalkthroughWalkthroughApplication-level passkey origins are added to backend contracts and persistence, synchronized with writable CORS settings, propagated through WebAuthn flows, and managed through new console controls with validation and localization. ChangesApplication-level passkey origin configuration
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Console
participant ApplicationAPI
participant ApplicationService
participant PasskeyExecutor
participant PasskeyService
Console->>ApplicationAPI: submit PasskeyAllowedOrigins
ApplicationAPI->>ApplicationService: create or update application
ApplicationService->>ApplicationService: persist origins and sync CORS
PasskeyExecutor->>PasskeyService: passkey request with AllowedOrigins
PasskeyService->>PasskeyService: resolve request or configured origins
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/EditAdvancedSettings.tsxESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/PasskeysSection.tsxESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox. frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/__tests__/EditAdvancedSettings.test.tsxESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
backend/pkg/thunderidengine/providers/model.go (1)
1008-1027: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win🔴 Documentation Required
This PR introduces user-facing changes that are not covered by documentation updates underdocs/.
Please update the relevant documentation before merging.Missing documentation:
- Application-level passkey origins: document the new
passkeyAllowedOriginsfield onInboundAuthProfileindocs/content/apis.mdxor a configuration guide.- Console Passkey Allowed Origins UI: document the advanced-settings app-level origin list and its CORS writable-origin auto-sync in
docs/content/guides/.Note: this cohort only includes a subset of the PR's files, so docs may already be added elsewhere in the full PR — please confirm.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/pkg/thunderidengine/providers/model.go` around lines 1008 - 1027, Update the relevant documentation to describe InboundAuthProfile.passkeyAllowedOrigins, including its optional application-level override behavior for passkey/WebAuthn origins. Add a console configuration guide covering the advanced-settings app-level origin list and its automatic synchronization with CORS writable origins, while preserving any existing server-level configuration documentation.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/internal/application/service.go`:
- Around line 2117-2165: Update the CORS synchronization flow around
GetWritableConfig and SetConfig to use an atomic server-config merge or
versioned compare-and-retry update, re-reading and merging on write conflicts so
concurrent origin additions are preserved. Keep deduplication of literal origins
intact, and add a test that exercises concurrent writes and verifies no origin
is lost.
In
`@frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/PasskeysSection.tsx`:
- Around line 25-38: Update PasskeysSectionProps and PasskeysSection to accept
onValidationChange and report whether errors is non-empty whenever errors
changes. In
frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/EditAdvancedSettings.tsx#L200-L204,
add passkeysInvalid state, pass it to PasskeysSection, and include it in the
combined validation callback alongside identityAssertionsInvalid and
attestationInvalid.
---
Outside diff comments:
In `@backend/pkg/thunderidengine/providers/model.go`:
- Around line 1008-1027: Update the relevant documentation to describe
InboundAuthProfile.passkeyAllowedOrigins, including its optional
application-level override behavior for passkey/WebAuthn origins. Add a console
configuration guide covering the advanced-settings app-level origin list and its
automatic synchronization with CORS writable origins, while preserving any
existing server-level configuration documentation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 80869c92-a7d6-44fe-9912-74184f619eb6
📒 Files selected for processing (22)
backend/cmd/server/servicemanager.gobackend/internal/application/handler.gobackend/internal/application/handler_test.gobackend/internal/application/init.gobackend/internal/application/init_test.gobackend/internal/application/service.gobackend/internal/application/service_test.gobackend/internal/authn/passkey/model.gobackend/internal/authn/passkey/service.gobackend/internal/authn/passkey/utils.gobackend/internal/authn/passkey/utils_test.gobackend/internal/flow/executor/passkey_executor.gobackend/internal/flow/executor/passkey_executor_test.gobackend/internal/inboundclient/store.gobackend/internal/inboundclient/store_test.gobackend/pkg/thunderidengine/providers/model.gofrontend/apps/console/src/features/applications/components/edit-application/advanced-settings/EditAdvancedSettings.tsxfrontend/apps/console/src/features/applications/components/edit-application/advanced-settings/PasskeysSection.tsxfrontend/apps/console/src/features/applications/components/edit-application/advanced-settings/__tests__/EditAdvancedSettings.test.tsxfrontend/apps/console/src/features/applications/components/edit-application/advanced-settings/__tests__/PasskeysSection.test.tsxfrontend/apps/console/src/features/applications/models/application.tsfrontend/packages/i18n/src/locales/en-US.ts
2d8c0d0 to
f5f7001
Compare
f5f7001 to
cbd966e
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/__tests__/EditAdvancedSettings.test.tsx`:
- Around line 455-469: Update the read-only assertion in the test using the same
key-based accessible-name matcher as the click test above, targeting the
`applications:edit.advanced.passkeys.allowedOrigins.addOrigin` translation key
instead of the literal “Add Origin” text. Keep the expectation that this button
is absent when `application.isReadOnly` is true.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 91c58572-a1e9-47ff-a7d1-103776552788
📒 Files selected for processing (23)
backend/cmd/server/servicemanager.gobackend/internal/actorprovider/utils.gobackend/internal/application/handler.gobackend/internal/application/handler_test.gobackend/internal/application/init.gobackend/internal/application/init_test.gobackend/internal/application/service.gobackend/internal/application/service_test.gobackend/internal/authn/passkey/model.gobackend/internal/authn/passkey/service.gobackend/internal/authn/passkey/utils.gobackend/internal/authn/passkey/utils_test.gobackend/internal/flow/executor/passkey_executor.gobackend/internal/flow/executor/passkey_executor_test.gobackend/internal/inboundclient/store.gobackend/internal/inboundclient/store_test.gobackend/pkg/thunderidengine/providers/model.gofrontend/apps/console/src/features/applications/components/edit-application/advanced-settings/EditAdvancedSettings.tsxfrontend/apps/console/src/features/applications/components/edit-application/advanced-settings/PasskeysSection.tsxfrontend/apps/console/src/features/applications/components/edit-application/advanced-settings/__tests__/EditAdvancedSettings.test.tsxfrontend/apps/console/src/features/applications/components/edit-application/advanced-settings/__tests__/PasskeysSection.test.tsxfrontend/apps/console/src/features/applications/models/application.tsfrontend/packages/i18n/src/locales/en-US.ts
🚧 Files skipped from review as they are similar to previous changes (20)
- frontend/apps/console/src/features/applications/models/application.ts
- backend/pkg/thunderidengine/providers/model.go
- backend/cmd/server/servicemanager.go
- backend/internal/authn/passkey/utils.go
- frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/PasskeysSection.tsx
- backend/internal/authn/passkey/utils_test.go
- backend/internal/application/handler_test.go
- frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/EditAdvancedSettings.tsx
- backend/internal/flow/executor/passkey_executor.go
- backend/internal/application/init.go
- frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/tests/PasskeysSection.test.tsx
- backend/internal/inboundclient/store.go
- backend/internal/flow/executor/passkey_executor_test.go
- backend/internal/authn/passkey/model.go
- frontend/packages/i18n/src/locales/en-US.ts
- backend/internal/application/init_test.go
- backend/internal/application/handler.go
- backend/internal/inboundclient/store_test.go
- backend/internal/authn/passkey/service.go
- backend/internal/application/service.go
| it('passes disabled=true to PasskeysSection when the application is read-only', () => { | ||
| const readOnlyApp: Application = {...mockApplication, isReadOnly: true}; | ||
|
|
||
| render( | ||
| <EditAdvancedSettings | ||
| application={readOnlyApp} | ||
| editedApp={{}} | ||
| oauth2Config={mockOAuth2Config} | ||
| onFieldChange={mockOnFieldChange} | ||
| />, | ||
| ); | ||
|
|
||
| // In read-only mode the Add Origin button should not be present | ||
| expect(screen.queryByRole('button', {name: /Add Origin/i})).not.toBeInTheDocument(); | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Read-only assertion never exercises the intended behavior.
The mocked t() returns the raw key (applications:edit.advanced.passkeys.allowedOrigins.addOrigin), which contains no literal "Add Origin" substring. The regex /Add Origin/i therefore never matches the button's accessible name whether or not the app is read-only, so this test passes trivially and won't catch a regression that re-enables the button in read-only mode. Use the same key-based matcher as the click test above (line 449).
🐛 Proposed fix
- // In read-only mode the Add Origin button should not be present
- expect(screen.queryByRole('button', {name: /Add Origin/i})).not.toBeInTheDocument();
+ // In read-only mode the Add Origin button should not be present
+ expect(
+ screen.queryByRole('button', {
+ name: /applications:edit.advanced.passkeys.allowedOrigins.addOrigin/i,
+ }),
+ ).not.toBeInTheDocument();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| it('passes disabled=true to PasskeysSection when the application is read-only', () => { | |
| const readOnlyApp: Application = {...mockApplication, isReadOnly: true}; | |
| render( | |
| <EditAdvancedSettings | |
| application={readOnlyApp} | |
| editedApp={{}} | |
| oauth2Config={mockOAuth2Config} | |
| onFieldChange={mockOnFieldChange} | |
| />, | |
| ); | |
| // In read-only mode the Add Origin button should not be present | |
| expect(screen.queryByRole('button', {name: /Add Origin/i})).not.toBeInTheDocument(); | |
| }); | |
| it('passes disabled=true to PasskeysSection when the application is read-only', () => { | |
| const readOnlyApp: Application = {...mockApplication, isReadOnly: true}; | |
| render( | |
| <EditAdvancedSettings | |
| application={readOnlyApp} | |
| editedApp={{}} | |
| oauth2Config={mockOAuth2Config} | |
| onFieldChange={mockOnFieldChange} | |
| />, | |
| ); | |
| // In read-only mode the Add Origin button should not be present | |
| expect( | |
| screen.queryByRole('button', { | |
| name: /applications:edit.advanced.passkeys.allowedOrigins.addOrigin/i, | |
| }), | |
| ).not.toBeInTheDocument(); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/__tests__/EditAdvancedSettings.test.tsx`
around lines 455 - 469, Update the read-only assertion in the test using the
same key-based accessible-name matcher as the click test above, targeting the
`applications:edit.advanced.passkeys.allowedOrigins.addOrigin` translation key
instead of the literal “Add Origin” text. Keep the expectation that this button
is absent when `application.isReadOnly` is true.
Purpose
Passkey allowed origins are currently configured only at the server level (
passkey.allowed_originsindeployment.toml). This means all applications share the same set of origins, which is too restrictive when different applications are hosted on different domains.This PR adds support for configuring passkey allowed origins per application. When an application has origins configured, those origins are used for WebAuthn challenge generation and verification for flows through that application. Applications without per-app origins fall back to the server-level configuration automatically.
Limitation: Adding origins to an application also adds them to the server CORS allowed origins list. There is currently no delete path for CORS origins, so removing an origin from an application does not automatically remove it from the server CORS configuration. Those must be removed separately.
Fixes: #1646
Approach
Backend
The new
passkeyAllowedOriginsfield is stored in the existingPROPERTIESJSONB column of theINBOUND_CLIENTtable via theinboundClientJSONBlobserialization struct. No DB schema migration is needed.Data flows through the stack as follows:
providers.InboundClientandproviders.InboundAuthProfileeach gain aPasskeyAllowedOrigins []stringfield.handler.go) forwards the field from the incoming request into theApplicationDTOfor both create and update paths.buildBaseApplicationProcessedDTOandbuildReturnApplicationDTOinservice.gopropagate the field so it is written to the store and returned in create/update responses.inboundclientstore marshals/unmarshals the field via the JSONB blob.For the passkey service, a
resolveAllowedOrigins(overrideOrigins []string) []stringhelper is added toutils.go. All four passkey operations (StartRegistration,FinishRegistration,StartAuthentication,FinishAuthentication) replace their hardcodedgetConfiguredOrigins()call withresolveAllowedOrigins(req.AllowedOrigins). WhenAllowedOriginsis empty (as in atomic API calls), it falls back to the server config unchanged.The flow executor reads
ctx.Application.PasskeyAllowedOriginsand sets it on all four passkey request types. Atomic API handlers make no changes; emptyAllowedOriginstriggers the fallback automatically.When an application is created or updated with passkeyAllowedOrigins, those origins are automatically merged into the server CORS allowed origins (writable layer) via syncPasskeyOriginsToCORS() in service.go. This sync is additive: origins already present are skipped, and origins removed from an application or deleted applications are not pruned from CORS. Removing stale origins from CORS requires manual intervention.
Frontend
A new

PasskeysSectioncomponent is added under Application Advanced Settings, following the existingRedirectURIsSectionpattern. It renders a list of origin text fields with add/delete controls and URL validation on blur. The section title is "Passkey Allowed Origins" with a hint clarifying server-level fallback behavior. TheEditAdvancedSettingscomponent wires this in viaonFieldChange('passkeyAllowedOrigins', ...).Related Issues
Related PRs
Checklist
breaking changelabel added.Security checks
Summary by CodeRabbit
New Features
Bug Fixes