Skip to content

Move Passkey Allowed Origin Configuration to Application-Level Configuration#4331

Open
NutharaNR wants to merge 1 commit into
thunder-id:mainfrom
NutharaNR:ease-configuration-passkey-origins
Open

Move Passkey Allowed Origin Configuration to Application-Level Configuration#4331
NutharaNR wants to merge 1 commit into
thunder-id:mainfrom
NutharaNR:ease-configuration-passkey-origins

Conversation

@NutharaNR

@NutharaNR NutharaNR commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Purpose

Passkey allowed origins are currently configured only at the server level (passkey.allowed_origins in deployment.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 passkeyAllowedOrigins field is stored in the existing PROPERTIES JSONB column of the INBOUND_CLIENT table via the inboundClientJSONBlob serialization struct. No DB schema migration is needed.

Data flows through the stack as follows:

  • providers.InboundClient and providers.InboundAuthProfile each gain a PasskeyAllowedOrigins []string field.
  • The application handler (handler.go) forwards the field from the incoming request into the ApplicationDTO for both create and update paths.
  • buildBaseApplicationProcessedDTO and buildReturnApplicationDTO in service.go propagate the field so it is written to the store and returned in create/update responses.
  • The inboundclient store marshals/unmarshals the field via the JSONB blob.

For the passkey service, a resolveAllowedOrigins(overrideOrigins []string) []string helper is added to utils.go. All four passkey operations (StartRegistration, FinishRegistration, StartAuthentication, FinishAuthentication) replace their hardcoded getConfiguredOrigins() call with resolveAllowedOrigins(req.AllowedOrigins). When AllowedOrigins is empty (as in atomic API calls), it falls back to the server config unchanged.

The flow executor reads ctx.Application.PasskeyAllowedOrigins and sets it on all four passkey request types. Atomic API handlers make no changes; empty AllowedOrigins triggers 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 PasskeysSection component is added under Application Advanced Settings, following the existing RedirectURIsSection pattern. 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. The EditAdvancedSettings component wires this in via onFieldChange('passkeyAllowedOrigins', ...).
image

Related Issues

Related PRs

  • N/A

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Add links if there are any)
    • Ran Vale and fixed all errors and warnings
  • Tests provided. (Add links if there are any)
    • Unit Tests
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.

Security checks

  • Followed secure coding standards in WSO2 Secure Coding Guidelines
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Summary by CodeRabbit

  • New Features

    • Added per-application passkey allowed origins configuration.
    • Passkey authentication and registration now use application-specific origins when provided.
    • Added console controls to view, add, edit, and remove allowed origins with URL validation.
    • Application settings automatically synchronize configured passkey origins with server CORS settings.
  • Bug Fixes

    • Preserved passkey origin settings across application creation, updates, and retrieval.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Application-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.

Changes

Application-level passkey origin configuration

Layer / File(s) Summary
Origin contracts and persistence
backend/pkg/thunderidengine/providers/model.go, backend/internal/authn/passkey/model.go, backend/internal/inboundclient/*, frontend/apps/console/src/features/applications/models/application.ts
Passkey allowed origins are added to application, inbound-client, passkey request, and frontend application models, including JSON persistence and round-trip coverage.
Application API and CORS synchronization
backend/internal/application/*, backend/internal/actorprovider/utils.go, backend/cmd/server/servicemanager.go
Application handlers map origins on create, read, and update; the service persists them, synchronizes missing origins into writable CORS configuration, and receives the server configuration dependency.
WebAuthn origin resolution
backend/internal/flow/executor/passkey_executor.go, backend/internal/authn/passkey/*
All passkey request types receive application origins, and WebAuthn initialization prefers request origins while falling back to configured origins.
Console passkey settings UI
frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/*, frontend/packages/i18n/src/locales/en-US.ts
Advanced settings provide editable origin fields, URL validation, add/delete controls, read-only behavior, integration tests, component tests, and localized text.

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
Loading

Suggested reviewers: thiva-k, thamindudilshan, donomalvindula, madurangasiriwardena

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: moving passkey allowed origin configuration to the application level.
Description check ✅ Passed The description follows the template well and includes purpose, approach, related issues, related PRs, checklist, and security checks.
Linked Issues check ✅ Passed The changes satisfy #1646 by adding per-application passkey origins with console support and server-level fallback.
Out of Scope Changes check ✅ Passed The modified backend, frontend, tests, and wiring all support the passkey origin configuration feature and appear in scope.
Docstring Coverage ✅ Passed Docstring coverage is 91.67% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/EditAdvancedSettings.tsx

ESLint 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.tsx

ESLint 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.tsx

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

  • 3 others

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 under docs/.
Please update the relevant documentation before merging.

Missing documentation:

  • Application-level passkey origins: document the new passkeyAllowedOrigins field on InboundAuthProfile in docs/content/apis.mdx or 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

📥 Commits

Reviewing files that changed from the base of the PR and between 00091d3 and 2d8c0d0.

📒 Files selected for processing (22)
  • backend/cmd/server/servicemanager.go
  • backend/internal/application/handler.go
  • backend/internal/application/handler_test.go
  • backend/internal/application/init.go
  • backend/internal/application/init_test.go
  • backend/internal/application/service.go
  • backend/internal/application/service_test.go
  • backend/internal/authn/passkey/model.go
  • backend/internal/authn/passkey/service.go
  • backend/internal/authn/passkey/utils.go
  • backend/internal/authn/passkey/utils_test.go
  • backend/internal/flow/executor/passkey_executor.go
  • backend/internal/flow/executor/passkey_executor_test.go
  • backend/internal/inboundclient/store.go
  • backend/internal/inboundclient/store_test.go
  • backend/pkg/thunderidengine/providers/model.go
  • frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/EditAdvancedSettings.tsx
  • frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/PasskeysSection.tsx
  • frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/__tests__/EditAdvancedSettings.test.tsx
  • frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/__tests__/PasskeysSection.test.tsx
  • frontend/apps/console/src/features/applications/models/application.ts
  • frontend/packages/i18n/src/locales/en-US.ts

Comment thread backend/internal/application/service.go
@NutharaNR
NutharaNR force-pushed the ease-configuration-passkey-origins branch from 2d8c0d0 to f5f7001 Compare July 24, 2026 10:35
@NutharaNR
NutharaNR force-pushed the ease-configuration-passkey-origins branch from f5f7001 to cbd966e Compare July 24, 2026 10:40

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2d8c0d0 and cbd966e.

📒 Files selected for processing (23)
  • backend/cmd/server/servicemanager.go
  • backend/internal/actorprovider/utils.go
  • backend/internal/application/handler.go
  • backend/internal/application/handler_test.go
  • backend/internal/application/init.go
  • backend/internal/application/init_test.go
  • backend/internal/application/service.go
  • backend/internal/application/service_test.go
  • backend/internal/authn/passkey/model.go
  • backend/internal/authn/passkey/service.go
  • backend/internal/authn/passkey/utils.go
  • backend/internal/authn/passkey/utils_test.go
  • backend/internal/flow/executor/passkey_executor.go
  • backend/internal/flow/executor/passkey_executor_test.go
  • backend/internal/inboundclient/store.go
  • backend/internal/inboundclient/store_test.go
  • backend/pkg/thunderidengine/providers/model.go
  • frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/EditAdvancedSettings.tsx
  • frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/PasskeysSection.tsx
  • frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/__tests__/EditAdvancedSettings.test.tsx
  • frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/__tests__/PasskeysSection.test.tsx
  • frontend/apps/console/src/features/applications/models/application.ts
  • frontend/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

Comment on lines +455 to +469
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();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Move Passkey Allowed Origin Configuration to Application-Level Configuration

1 participant